From 90072e69b3270409fc1caf988db774ab1f4e04a9 Mon Sep 17 00:00:00 2001 From: "kostja@bodhi.local" <> Date: Fri, 8 Dec 2006 02:20:09 +0300 Subject: [PATCH 1/2] A fix and test cases for Bug#4968 "Stored procedure crash if cursor opened on altered table" Bug#19733 "Repeated alter, or repeated create/drop, fails" Bug#19182 "CREATE TABLE bar (m INT) SELECT n FROM foo; doesn't work from stored procedure." Bug#6895 "Prepared Statements: ALTER TABLE DROP COLUMN does nothing" Bug#22060 "ALTER TABLE x AUTO_INCREMENT=y in SP crashes server" Test cases for bugs 4968, 19733, 6895 will be added in 5.0. Re-execution of CREATE DATABASE, CREATE TABLE and ALTER TABLE statements in stored routines or as prepared statements caused incorrect results (and crashes in versions prior to 5.0.25). In 5.1 the problem occured only for CREATE DATABASE, CREATE TABLE SELECT and CREATE TABLE with INDEX/DATA DIRECTOY options). The problem of bugs 4968, 19733, 19282 and 6895 was that functions mysql_prepare_table, mysql_create_table and mysql_alter_table were not re-execution friendly: during their operation they used to modify contents of LEX (members create_info, alter_info, key_list, create_list), thus making the LEX unusable for the next execution. In particular, these functions removed processed columns and keys from create_list, key_list and drop_list. Search the code in sql_table.cc for drop_it.remove() and similar patterns to find evidence. The fix is to supply to these functions a usable copy of each of the above structures at every re-execution of an SQL statement. To simplify memory management, LEX::key_list and LEX::create_list were added to LEX::alter_info, a fresh copy of which is created for every execution. The problem of crashing bug 22060 stemmed from the fact that the above metnioned functions were not only modifying HA_CREATE_INFO structure in LEX, but also were changing it to point to areas in volatile memory of the execution memory root. The patch solves this problem by creating and using an on-stack copy of HA_CREATE_INFO (note that code in 5.1 already creates and uses a copy of this structure in mysql_create_table()/alter_table(), but this approach didn't work well for CREATE TABLE SELECT statement). --- mysql-test/r/ps.result | 67 ++++++++ mysql-test/t/ps.test | 72 ++++++++ sql/mysql_priv.h | 19 +-- sql/sql_class.h | 19 ++- sql/sql_insert.cc | 2 +- sql/sql_lex.cc | 12 ++ sql/sql_lex.h | 56 ++++++- sql/sql_list.h | 74 ++++++-- sql/sql_parse.cc | 206 ++++++++++++----------- sql/sql_show.cc | 2 +- sql/sql_table.cc | 373 +++++++++++------------------------------ sql/sql_yacc.yy | 50 +++--- 12 files changed, 511 insertions(+), 441 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 94c51fdc18b..0d6b01f3481 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -1035,4 +1035,71 @@ EXECUTE stmt USING @a; 0 0 DEALLOCATE PREPARE stmt; DROP TABLE t1; +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 (i INT); +PREPARE st_19182 +FROM "CREATE TABLE t2 (i INT, j INT, KEY (i), KEY(j)) SELECT i FROM t1"; +EXECUTE st_19182; +DESC t2; +Field Type Null Key Default Extra +j int(11) YES MUL NULL +i int(11) YES MUL NULL +DROP TABLE t2; +EXECUTE st_19182; +DESC t2; +Field Type Null Key Default Extra +j int(11) YES MUL NULL +i int(11) YES MUL NULL +DEALLOCATE PREPARE st_19182; +DROP TABLE t2, t1; +drop database if exists mysqltest; +drop table if exists t1, t2; +create database mysqltest character set utf8; +prepare stmt1 from "create table mysqltest.t1 (c char(10))"; +prepare stmt2 from "create table mysqltest.t2 select 'test'"; +execute stmt1; +execute stmt2; +show create table mysqltest.t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +show create table mysqltest.t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `test` char(4) character set latin1 NOT NULL default '' +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +drop table mysqltest.t1; +drop table mysqltest.t2; +alter database mysqltest character set latin1; +execute stmt1; +execute stmt2; +show create table mysqltest.t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` char(10) character set utf8 default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +show create table mysqltest.t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `test` char(4) NOT NULL default '' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop database mysqltest; +deallocate prepare stmt1; +deallocate prepare stmt2; +execute stmt; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 DATA DIRECTORY='MYSQLTEST_VARDIR/tmp/' +drop table t1; +execute stmt; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 DATA DIRECTORY='MYSQLTEST_VARDIR/tmp/' +drop table t1; +deallocate prepare stmt; End of 4.1 tests. diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index fbeaaa494e0..cd6c0667898 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -1068,5 +1068,77 @@ EXECUTE stmt USING @a; DEALLOCATE PREPARE stmt; DROP TABLE t1; +# +# Bug#19182: CREATE TABLE bar (m INT) SELECT n FROM foo; doesn't work +# from stored procedure. +# +# The cause of a bug was that cached LEX::create_list was modified, +# and then together with LEX::key_list was reset. +# +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +--enable_warnings +CREATE TABLE t1 (i INT); + +PREPARE st_19182 +FROM "CREATE TABLE t2 (i INT, j INT, KEY (i), KEY(j)) SELECT i FROM t1"; + +EXECUTE st_19182; +DESC t2; + +DROP TABLE t2; + +# Check that on second execution we don't loose 'j' column and the keys +# on 'i' and 'j' columns. +EXECUTE st_19182; +DESC t2; + +DEALLOCATE PREPARE st_19182; +DROP TABLE t2, t1; + +# +# Bug #22060 "ALTER TABLE x AUTO_INCREMENT=y in SP crashes server" +# +# Code which implemented CREATE/ALTER TABLE and CREATE DATABASE +# statement modified HA_CREATE_INFO structure in LEX, making these +# statements PS/SP-unsafe (their re-execution might have resulted +# in incorrect results). +# +--disable_warnings +drop database if exists mysqltest; +drop table if exists t1, t2; +--enable_warnings +# CREATE TABLE and CREATE TABLE ... SELECT +create database mysqltest character set utf8; +prepare stmt1 from "create table mysqltest.t1 (c char(10))"; +prepare stmt2 from "create table mysqltest.t2 select 'test'"; +execute stmt1; +execute stmt2; +show create table mysqltest.t1; +show create table mysqltest.t2; +drop table mysqltest.t1; +drop table mysqltest.t2; +alter database mysqltest character set latin1; +execute stmt1; +execute stmt2; +show create table mysqltest.t1; +show create table mysqltest.t2; +drop database mysqltest; +deallocate prepare stmt1; +deallocate prepare stmt2; +# CREATE TABLE with DATA DIRECTORY option +--disable_query_log +eval prepare stmt from "create table t1 (c char(10)) data directory='$MYSQLTEST_VARDIR/tmp'"; +--enable_query_log +execute stmt; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +show create table t1; +drop table t1; +execute stmt; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +show create table t1; +drop table t1; +deallocate prepare stmt; +# --echo End of 4.1 tests. diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 8d6cdebe1f7..0a14f947554 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -563,25 +563,22 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, uint &key_count, int select_field_count); int mysql_create_table(THD *thd,const char *db, const char *table_name, HA_CREATE_INFO *create_info, - List &fields, List &keys, + Alter_info *alter_info, bool tmp_table, uint select_field_count); TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, const char *db, const char *name, - List *extra_fields, - List *keys, + Alter_info *alter_info, List *items, MYSQL_LOCK **lock); int mysql_alter_table(THD *thd, char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, - List &fields, - List &keys, + Alter_info *alter_info, uint order_num, ORDER *order, enum enum_duplicates handle_duplicates, - bool ignore, - ALTER_INFO *alter_info, bool do_send_ok=1); -int mysql_recreate_table(THD *thd, TABLE_LIST *table_list, bool do_send_ok); + bool ignore); +int mysql_recreate_table(THD *thd, TABLE_LIST *table_list); int mysql_create_like_table(THD *thd, TABLE_LIST *table, HA_CREATE_INFO *create_info, Table_ident *src_table); @@ -590,9 +587,6 @@ bool mysql_rename_table(enum db_type base, const char * old_name, const char *new_db, const char * new_name); -int mysql_create_index(THD *thd, TABLE_LIST *table_list, List &keys); -int mysql_drop_index(THD *thd, TABLE_LIST *table_list, - ALTER_INFO *alter_info); int mysql_prepare_update(THD *thd, TABLE_LIST *table_list, TABLE_LIST *update_table_list, Item **conds, uint order_num, ORDER *order); @@ -679,7 +673,8 @@ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); void mysqld_list_fields(THD *thd,TABLE_LIST *table, const char *wild); int mysqld_dump_create_info(THD *thd, TABLE *table, int fd = -1); int mysqld_show_create(THD *thd, TABLE_LIST *table_list); -int mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create); +int mysqld_show_create_db(THD *thd, char *dbname, + const HA_CREATE_INFO *create); void mysqld_list_processes(THD *thd,const char *user,bool verbose); int mysqld_show_status(THD *thd); diff --git a/sql/sql_class.h b/sql/sql_class.h index 16abfe2b6b6..8ecbaf7e5a7 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1293,20 +1293,21 @@ class select_create: public select_insert { ORDER *group; const char *db; const char *name; - List *extra_fields; - List *keys; HA_CREATE_INFO *create_info; + Alter_info *alter_info; MYSQL_LOCK *lock; Field **field; public: select_create(const char *db_name, const char *table_name, - HA_CREATE_INFO *create_info_par, - List &fields_par, - List &keys_par, - List &select_fields,enum_duplicates duplic, bool ignore) - :select_insert (NULL, &select_fields, duplic, ignore), db(db_name), - name(table_name), extra_fields(&fields_par),keys(&keys_par), - create_info(create_info_par), lock(0) + HA_CREATE_INFO *create_info_arg, + Alter_info *alter_info_arg, + List &select_fields, + enum_duplicates duplic, bool ignore) + :select_insert(NULL, &select_fields, duplic, ignore), + db(db_name), name(table_name), + create_info(create_info_arg), + alter_info(alter_info_arg), + lock(0) {} int prepare(List &list, SELECT_LEX_UNIT *u); void store_values(List &values); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 43c7e5d456f..394a80c4efb 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1808,7 +1808,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) unit= u; table= create_table_from_items(thd, create_info, db, name, - extra_fields, keys, &values, &lock); + alter_info, &values, &lock); if (!table) DBUG_RETURN(-1); // abort() deletes table diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index dfe406c351e..270fdb3f20a 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1015,6 +1015,18 @@ int yylex(void *arg, void *yythd) } } + +Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) + :drop_list(rhs.drop_list, mem_root), + alter_list(rhs.alter_list, mem_root), + key_list(rhs.key_list, mem_root), + create_list(rhs.create_list, mem_root), + flags(rhs.flags), + keys_onoff(rhs.keys_onoff), + tablespace_op(rhs.tablespace_op), + is_simple(rhs.is_simple) +{} + /* st_select_lex structures initialisations */ diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 12f89202e2d..7faeb8046c7 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -571,19 +571,61 @@ typedef class st_select_lex SELECT_LEX; #define ALTER_ORDER 64 #define ALTER_OPTIONS 128 -typedef struct st_alter_info +/** + @brief Parsing data for CREATE or ALTER TABLE. + + This structure contains a list of columns or indexes to be created, + altered or dropped. +*/ + +class Alter_info { +public: List drop_list; List alter_list; + List key_list; + List create_list; uint flags; enum enum_enable_or_disable keys_onoff; enum tablespace_op_type tablespace_op; bool is_simple; - st_alter_info(){clear();} - void clear(){keys_onoff= LEAVE_AS_IS;tablespace_op= NO_TABLESPACE_OP;} - void reset(){drop_list.empty();alter_list.empty();clear();} -} ALTER_INFO; + Alter_info() : + flags(0), + keys_onoff(LEAVE_AS_IS), + tablespace_op(NO_TABLESPACE_OP), + is_simple(1) + {} + void reset() + { + drop_list.empty(); + alter_list.empty(); + key_list.empty(); + create_list.empty(); + flags= 0; + keys_onoff= LEAVE_AS_IS; + tablespace_op= NO_TABLESPACE_OP; + is_simple= 1; + } + /** + Construct a copy of this object to be used for mysql_alter_table + and mysql_create_table. Historically, these two functions modify + their Alter_info arguments. This behaviour breaks re-execution of + prepared statements and stored procedures and is compensated by + always supplying a copy of Alter_info to these functions. + The constructed copy still shares key Key, Alter_drop, create_field + and Alter_column elements of the lists - these structures are not + modified and thus are not copied. + + @note You need to use check thd->is_fatal_error for out + of memory condition after calling this function. + */ + Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root); +private: + Alter_info &operator=(const Alter_info &rhs); // not implemented + Alter_info(const Alter_info &rhs); // not implemented +}; + /* The state of the lex parsing. This is saved in the THD struct */ @@ -620,8 +662,6 @@ typedef struct st_lex List interval_list; List users_list; List columns; - List key_list; - List create_list; List *insert_list,field_list,value_list,update_list; List many_values; List var_list; @@ -654,7 +694,7 @@ typedef struct st_lex bool derived_tables; bool safe_to_cache_query; bool subqueries, ignore; - ALTER_INFO alter_info; + Alter_info alter_info; /* Prepared statements SQL syntax:*/ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ /* diff --git a/sql/sql_list.h b/sql/sql_list.h index e799ecf3d6e..47379ab1ddf 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -66,21 +66,24 @@ public: pointer. */ -class list_node :public Sql_alloc + +/** + list_node - a node of a single-linked list. + @note We never call a destructor for instances of this class. +*/ + +struct list_node :public Sql_alloc { -public: list_node *next; void *info; list_node(void *info_par,list_node *next_par) :next(next_par),info(info_par) - {} + {} list_node() /* For end_of_list */ - { - info=0; - next= this; - } - friend class base_list; - friend class base_list_iterator; + { + info= 0; + next= this; + } }; @@ -96,11 +99,56 @@ public: inline void empty() { elements=0; first= &end_of_list; last=&first;} inline base_list() { empty(); } + /** + This is a shallow copy constructor that implicitly passes the ownership + from the source list to the new instance. The old instance is not + updated, so both objects end up sharing the same nodes. If one of + the instances then adds or removes a node, the other becomes out of + sync ('last' pointer), while still operational. Some old code uses and + relies on this behaviour. This logic is quite tricky: please do not use + it in any new code. + */ inline base_list(const base_list &tmp) :Sql_alloc() { - elements=tmp.elements; - first=tmp.first; - last=tmp.last; + elements= tmp.elements; + first= tmp.first; + last= elements ? tmp.last : &first; + } + /** + Construct a deep copy of the argument in memory root mem_root. + The elements themselves are copied by pointer. + */ + inline base_list(const base_list &rhs, MEM_ROOT *mem_root) + { + if (rhs.elements) + { + /* + It's okay to allocate an array of nodes at once: we never + call a destructor for list_node objects anyway. + */ + first= (list_node*) alloc_root(mem_root, + sizeof(list_node) * rhs.elements); + if (first) + { + elements= rhs.elements; + list_node *dst= first; + list_node *src= rhs.first; + for (; dst < first + elements - 1; dst++, src= src->next) + { + dst->info= src->info; + dst->next= dst + 1; + } + /* Copy the last node */ + dst->info= src->info; + dst->next= &end_of_list; + /* Setup 'last' member */ + last= &dst->next; + return; + } + } + elements= 0; + first= &end_of_list; + last= &first; } inline base_list(bool error) { } inline bool push_back(void *info) @@ -327,6 +375,8 @@ template class List :public base_list public: inline List() :base_list() {} inline List(const List &tmp) :base_list(tmp) {} + inline List(const List &tmp, MEM_ROOT *mem_root) : + base_list(tmp, mem_root) {} inline bool push_back(T *a) { return base_list::push_back(a); } inline bool push_front(T *a) { return base_list::push_front(a); } inline T* head() {return (T*) base_list::head(); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index cb2fa0f7014..426a65ed3df 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2478,6 +2478,22 @@ mysql_execute_command(THD *thd) } /* Skip first table, which is the table we are creating */ TABLE_LIST *create_table, *create_table_local; + /* + Code below (especially in mysql_create_table() and select_create + methods) may modify HA_CREATE_INFO structure in LEX, so we have to + use a copy of this structure to make execution prepared statement- + safe. A shallow copy is enough as this code won't modify any memory + referenced from this structure. + */ + HA_CREATE_INFO create_info(lex->create_info); + Alter_info alter_info(lex->alter_info, thd->mem_root); + + if (thd->is_fatal_error) + { + /* out of memory when creating a copy of alter_info */ + res= 1; + goto unsent_create_error; + } tables= lex->unlink_first_table(tables, &create_table, &create_table_local); @@ -2485,12 +2501,12 @@ mysql_execute_command(THD *thd) goto unsent_create_error; #ifndef HAVE_READLINK - lex->create_info.data_file_name=lex->create_info.index_file_name=0; + create_info.data_file_name= create_info.index_file_name= NULL; #else /* Fix names if symlinked tables */ - if (append_file_to_dir(thd, &lex->create_info.data_file_name, + if (append_file_to_dir(thd, &create_info.data_file_name, create_table->real_name) || - append_file_to_dir(thd,&lex->create_info.index_file_name, + append_file_to_dir(thd, &create_info.index_file_name, create_table->real_name)) { res=-1; @@ -2501,14 +2517,14 @@ mysql_execute_command(THD *thd) If we are using SET CHARSET without DEFAULT, add an implicite DEFAULT to not confuse old users. (This may change). */ - if ((lex->create_info.used_fields & + if ((create_info.used_fields & (HA_CREATE_USED_DEFAULT_CHARSET | HA_CREATE_USED_CHARSET)) == HA_CREATE_USED_CHARSET) { - lex->create_info.used_fields&= ~HA_CREATE_USED_CHARSET; - lex->create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; - lex->create_info.default_table_charset= lex->create_info.table_charset; - lex->create_info.table_charset= 0; + create_info.used_fields&= ~HA_CREATE_USED_CHARSET; + create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; + create_info.default_table_charset= create_info.table_charset; + create_info.table_charset= 0; } /* The create-select command will open and read-lock the select table @@ -2542,11 +2558,14 @@ mysql_execute_command(THD *thd) if (!(res=open_and_lock_tables(thd,tables))) { res= -1; // If error + /* + select_create is currently not re-execution friendly and + needs to be created for every execution of a PS/SP. + */ if ((result=new select_create(create_table->db, create_table->real_name, - &lex->create_info, - lex->create_list, - lex->key_list, + &create_info, + &alter_info, select_lex->item_list, lex->duplicates, lex->ignore))) { @@ -2558,22 +2577,18 @@ mysql_execute_command(THD *thd) res=handle_select(thd, lex, result); select_lex->resolve_mode= SELECT_LEX::NOMATTER_MODE; } - //reset for PS - lex->create_list.empty(); - lex->key_list.empty(); } } else // regular create { if (lex->name) - res= mysql_create_like_table(thd, create_table, &lex->create_info, + res= mysql_create_like_table(thd, create_table, &create_info, (Table_ident *)lex->name); else { - res= mysql_create_table(thd,create_table->db, - create_table->real_name, &lex->create_info, - lex->create_list, - lex->key_list,0,0); + res= mysql_create_table(thd, create_table->db, + create_table->real_name, &create_info, + &alter_info, 0, 0); } if (!res) send_ok(thd); @@ -2591,15 +2606,48 @@ unsent_create_error: break; } case SQLCOM_CREATE_INDEX: + /* Fall through */ + case SQLCOM_DROP_INDEX: + /* + CREATE INDEX and DROP INDEX are implemented by calling ALTER + TABLE with proper arguments. This isn't very fast but it + should work for most cases. + + In the future ALTER TABLE will notice that only added + indexes and create these one by one for the existing table + without having to do a full rebuild. + + One should normally create all indexes with CREATE TABLE or + ALTER TABLE. + */ + { + Alter_info alter_info(lex->alter_info, thd->mem_root); + HA_CREATE_INFO create_info; + + if (thd->is_fatal_error) /* out of memory creating a copy of alter_info*/ + goto error; + if (check_one_table_access(thd, INDEX_ACL, tables)) goto error; /* purecov: inspected */ thd->enable_slow_log= opt_log_slow_admin_statements; if (end_active_trans(thd)) - res= -1; - else - res = mysql_create_index(thd, tables, lex->key_list); - break; + goto error; + /* + Currently CREATE INDEX or DROP INDEX cause a full table rebuild + and thus classify as slow administrative statements just like + ALTER TABLE. + */ + thd->enable_slow_log= opt_log_slow_admin_statements; + bzero((char*) &create_info, sizeof(create_info)); + create_info.db_type= DB_TYPE_DEFAULT; + create_info.default_table_charset= thd->variables.collation_database; + + res= mysql_alter_table(thd, tables->db, tables->real_name, + &create_info, tables, &alter_info, + 0, (ORDER*)0, DUP_ERROR, 0); + break; + } #ifdef HAVE_REPLICATION case SQLCOM_SLAVE_START: { @@ -2642,6 +2690,17 @@ unsent_create_error: #else { ulong priv=0; + /* + Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, + so we have to use a copy of this structure to make execution + prepared statement- safe. A shallow copy is enough as no memory + referenced from this structure will be modified. + */ + HA_CREATE_INFO create_info(lex->create_info); + Alter_info alter_info(lex->alter_info, thd->mem_root); + + if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ + goto error; if (lex->name && (!lex->name[0] || strlen(lex->name) > NAME_LEN)) { net_printf(thd, ER_WRONG_TABLE_NAME, lex->name); @@ -2655,7 +2714,7 @@ unsent_create_error: default database if the new name is not explicitly qualified by a database. (Bug #11493) */ - if (lex->alter_info.flags & ALTER_RENAME) + if (alter_info.flags & ALTER_RENAME) { if (! thd->db) { @@ -2671,7 +2730,7 @@ unsent_create_error: check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv,0,0)|| check_merge_table_access(thd, tables->db, (TABLE_LIST *) - lex->create_info.merge_list.first)) + create_info.merge_list.first)) goto error; /* purecov: inspected */ if (grant_option) { @@ -2690,26 +2749,26 @@ unsent_create_error: } } /* Don't yet allow changing of symlinks with ALTER TABLE */ - if (lex->create_info.data_file_name) + if (create_info.data_file_name) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0, "DATA DIRECTORY option ignored"); - if (lex->create_info.index_file_name) + if (create_info.index_file_name) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, 0, "INDEX DIRECTORY option ignored"); - lex->create_info.data_file_name=lex->create_info.index_file_name=0; + create_info.data_file_name= create_info.index_file_name= NULL; /* ALTER TABLE ends previous transaction */ if (end_active_trans(thd)) res= -1; else { thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_alter_table(thd, select_lex->db, lex->name, - &lex->create_info, - tables, lex->create_list, - lex->key_list, - select_lex->order_list.elements, + res= mysql_alter_table(thd, select_lex->db, lex->name, + &create_info, + tables, + &alter_info, + select_lex->order_list.elements, (ORDER *) select_lex->order_list.first, - lex->duplicates, lex->ignore, &lex->alter_info); + lex->duplicates, lex->ignore); } break; } @@ -2846,7 +2905,7 @@ unsent_create_error: goto error; /* purecov: inspected */ thd->enable_slow_log= opt_log_slow_admin_statements; res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, tables, 1) : + mysql_recreate_table(thd, tables) : mysql_optimize_table(thd, tables, &lex->check_opt); /* ! we write after unlocking the table */ if (!res && !lex->no_write_to_binlog) @@ -3123,14 +3182,6 @@ unsent_create_error: res= mysql_rm_table(thd,tables,lex->drop_if_exists, lex->drop_temporary); } break; - case SQLCOM_DROP_INDEX: - if (check_one_table_access(thd, INDEX_ACL, tables)) - goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - res= -1; - else - res = mysql_drop_index(thd, tables, &lex->alter_info); - break; case SQLCOM_SHOW_DATABASES: #if defined(DONT_ALLOW_SHOW_COMMANDS) send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ @@ -3363,6 +3414,12 @@ purposes internal to the MySQL server", MYF(0)); break; case SQLCOM_CREATE_DB: { + /* + As mysql_create_db() may modify HA_CREATE_INFO structure passed to + it, we need to use a copy of LEX::create_info to make execution + prepared statement- safe. + */ + HA_CREATE_INFO create_info(lex->create_info); if (end_active_trans(thd)) { res= -1; @@ -3393,7 +3450,7 @@ purposes internal to the MySQL server", MYF(0)); if (check_access(thd,CREATE_ACL,lex->name,0,1,0)) break; res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name), - &lex->create_info, 0); + &create_info, 0); break; } case SQLCOM_DROP_DB: @@ -4443,15 +4500,17 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, if (type_modifier & PRI_KEY_FLAG) { lex->col_list.push_back(new key_part_spec(field_name,0)); - lex->key_list.push_back(new Key(Key::PRIMARY, NullS, HA_KEY_ALG_UNDEF, - 0, lex->col_list)); + lex->alter_info.key_list.push_back(new Key(Key::PRIMARY, NullS, + HA_KEY_ALG_UNDEF, 0, + lex->col_list)); lex->col_list.empty(); } if (type_modifier & (UNIQUE_FLAG | UNIQUE_KEY_FLAG)) { lex->col_list.push_back(new key_part_spec(field_name,0)); - lex->key_list.push_back(new Key(Key::UNIQUE, NullS, HA_KEY_ALG_UNDEF, 0, - lex->col_list)); + lex->alter_info.key_list.push_back(new Key(Key::UNIQUE, NullS, + HA_KEY_ALG_UNDEF, 0, + lex->col_list)); lex->col_list.empty(); } @@ -4778,7 +4837,7 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, new_field->sql_type, new_field->length); new_field->char_length= new_field->length; - lex->create_list.push_back(new_field); + lex->alter_info.create_list.push_back(new_field); lex->last_field=new_field; DBUG_RETURN(0); } @@ -5458,55 +5517,6 @@ Item * all_any_subquery_creator(Item *left_expr, } -/* - CREATE INDEX and DROP INDEX are implemented by calling ALTER TABLE with - the proper arguments. This isn't very fast but it should work for most - cases. - - In the future ALTER TABLE will notice that only added indexes - and create these one by one for the existing table without having to do - a full rebuild. - - One should normally create all indexes with CREATE TABLE or ALTER TABLE. -*/ - -int mysql_create_index(THD *thd, TABLE_LIST *table_list, List &keys) -{ - List fields; - ALTER_INFO alter_info; - alter_info.flags= ALTER_ADD_INDEX; - alter_info.is_simple= 0; - HA_CREATE_INFO create_info; - DBUG_ENTER("mysql_create_index"); - bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; - create_info.default_table_charset= thd->variables.collation_database; - DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, - &create_info, table_list, - fields, keys, 0, (ORDER*)0, - DUP_ERROR, 0, &alter_info)); -} - - -int mysql_drop_index(THD *thd, TABLE_LIST *table_list, ALTER_INFO *alter_info) -{ - List fields; - List keys; - HA_CREATE_INFO create_info; - DBUG_ENTER("mysql_drop_index"); - bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; - create_info.default_table_charset= thd->variables.collation_database; - alter_info->clear(); - alter_info->flags= ALTER_DROP_INDEX; - alter_info->is_simple= 0; - DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, - &create_info, table_list, - fields, keys, 0, (ORDER*)0, - DUP_ERROR, 0, alter_info)); -} - - /* Multi update query pre-check diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 41e145790e9..494c10f6e6b 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -845,7 +845,7 @@ mysqld_show_create(THD *thd, TABLE_LIST *table_list) } int mysqld_show_create_db(THD *thd, char *dbname, - HA_CREATE_INFO *create_info) + const HA_CREATE_INFO *create_info) { int length; char path[FN_REFLEN]; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index da66b556b5e..4a11cad0da8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -455,8 +455,7 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, mysql_prepare_table() thd Thread object create_info Create information (like MAX_ROWS) - fields List of fields to create - keys List of keys to create + alter_info List of columns and indexes to create DESCRIPTION Prepares the table and key structures for table creation. @@ -467,8 +466,8 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, */ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, - List &fields, - List &keys, bool tmp_table, uint &db_options, + Alter_info *alter_info, + bool tmp_table, uint &db_options, handler *file, KEY *&key_info_buffer, uint *key_count, int select_field_count) { @@ -481,10 +480,11 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, int timestamps= 0, timestamps_with_niladic= 0; int field_no,dup_no; int select_field_pos,auto_increment=0; + List_iterator it(alter_info->create_list); + List_iterator it2(alter_info->create_list); DBUG_ENTER("mysql_prepare_table"); - List_iterator it(fields),it2(fields); - select_field_pos=fields.elements - select_field_count; + select_field_pos= alter_info->create_list.elements - select_field_count; null_fields=blob_columns=0; for (field_no=0; (sql_field=it++) ; field_no++) @@ -882,7 +882,8 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, /* Create keys */ - List_iterator key_iterator(keys), key_iterator2(keys); + List_iterator key_iterator(alter_info->key_list); + List_iterator key_iterator2(alter_info->key_list); uint key_parts=0, fk_key_count=0; bool primary_key=0,unique_key=0; Key *key, *key2; @@ -1332,23 +1333,27 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, SYNOPSIS mysql_create_table() - thd Thread object - db Database - table_name Table name - create_info Create information (like MAX_ROWS) - fields List of fields to create - keys List of keys to create - tmp_table Set to 1 if this is an internal temporary table - (From ALTER TABLE) + thd Thread object + db Database + table_name Table name + create_info [in/out] Create information (like MAX_ROWS) + alter_info [in/out] List of columns and indexes to create + tmp_table Set to 1 if this is an internal temporary table + (From ALTER TABLE) DESCRIPTION - If one creates a temporary table, this is automaticly opened + If one creates a temporary table, this is automatically opened no_log is needed for the case of CREATE ... SELECT, as the logging will be done later in sql_insert.cc select_field_count is also used for CREATE ... SELECT, and must be zero for standard create of table. + Note that structures passed as 'create_info' and 'alter_info' parameters + may be modified by this function. It is responsibility of the caller to + make a copy of create_info in order to provide correct execution in + prepared statements/stored routines. + RETURN VALUES 0 ok -1 error @@ -1356,8 +1361,8 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, int mysql_create_table(THD *thd,const char *db, const char *table_name, HA_CREATE_INFO *create_info, - List &fields, - List &keys,bool tmp_table, + Alter_info *alter_info, + bool tmp_table, uint select_field_count) { char path[FN_REFLEN]; @@ -1370,7 +1375,7 @@ int mysql_create_table(THD *thd,const char *db, const char *table_name, DBUG_ENTER("mysql_create_table"); /* Check for duplicate fields and check type of table to create */ - if (!fields.elements) + if (!alter_info->create_list.elements) { my_error(ER_TABLE_MUST_HAVE_COLUMNS,MYF(0)); DBUG_RETURN(-1); @@ -1422,10 +1427,10 @@ int mysql_create_table(THD *thd,const char *db, const char *table_name, create_info->default_table_charset= db_info.default_table_charset; } - if (mysql_prepare_table(thd, create_info, fields, - keys, tmp_table, db_options, file, - key_info_buffer, &key_count, - select_field_count)) + if (mysql_prepare_table(thd, create_info, alter_info, tmp_table, + db_options, file, + key_info_buffer, &key_count, + select_field_count)) DBUG_RETURN(-1); /* Check if table exists */ @@ -1502,8 +1507,8 @@ int mysql_create_table(THD *thd,const char *db, const char *table_name, create_info->table_options=db_options; if (rea_create_table(thd, path, db, table_name, - create_info, fields, key_count, - key_info_buffer)) + create_info, alter_info->create_list, + key_count, key_info_buffer)) goto end; if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { @@ -1591,8 +1596,7 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end) TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, const char *db, const char *name, - List *extra_fields, - List *keys, + Alter_info *alter_info, List *items, MYSQL_LOCK **lock) { @@ -1626,7 +1630,7 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, ((Item_field *)item)->field : (Field*) 0)))) DBUG_RETURN(0); - extra_fields->push_back(cr_field); + alter_info->create_list.push_back(cr_field); } /* create and lock table */ /* QQ: create and open should be done atomic ! */ @@ -1640,8 +1644,8 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, open_table(). */ tmp_disable_binlog(thd); - if (!mysql_create_table(thd,db,name,create_info,*extra_fields, - *keys,0,select_field_count)) + if (!mysql_create_table(thd, db, name, create_info, alter_info, + 0, select_field_count)) { if (!(table=open_table(thd,db,name,name,(bool*) 0))) quick_rm_table(create_info->db_type,db,table_case_name(create_info,name)); @@ -2145,6 +2149,7 @@ send_result_message: case HA_ADMIN_TRY_ALTER: { + my_bool save_no_send_ok= thd->net.no_send_ok; /* This is currently used only by InnoDB. ha_innobase::optimize() answers "try with alter", so here we close the table, do an ALTER TABLE, @@ -2154,7 +2159,9 @@ send_result_message: TABLE_LIST *save_next= table->next; table->next= 0; tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table, 0); + thd->net.no_send_ok= TRUE; + result_code= mysql_recreate_table(thd, table); + thd->net.no_send_ok= save_no_send_ok; reenable_binlog(thd); close_thread_tables(thd); if (!result_code) // recreation went ok @@ -2634,219 +2641,25 @@ err: } -#ifdef NOT_USED -/* - CREATE INDEX and DROP INDEX are implemented by calling ALTER TABLE with - the proper arguments. This isn't very fast but it should work for most - cases. - One should normally create all indexes with CREATE TABLE or ALTER TABLE. -*/ - -int mysql_create_indexes(THD *thd, TABLE_LIST *table_list, List &keys) -{ - List fields; - List drop; - List alter; - HA_CREATE_INFO create_info; - int rc; - uint idx; - uint db_options; - uint key_count; - TABLE *table; - Field **f_ptr; - KEY *key_info_buffer; - char path[FN_REFLEN+1]; - DBUG_ENTER("mysql_create_index"); - - /* - Try to use online generation of index. - This requires that all indexes can be created online. - Otherwise, the old alter table procedure is executed. - - Open the table to have access to the correct table handler. - */ - if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) - DBUG_RETURN(-1); - - /* - The add_index method takes an array of KEY structs for the new indexes. - Preparing a new table structure generates this array. - It needs a list with all fields of the table, which does not need to - be correct in every respect. The field names are important. - */ - for (f_ptr= table->field; *f_ptr; f_ptr++) - { - create_field *c_fld= new create_field(*f_ptr, *f_ptr); - c_fld->unireg_check= Field::NONE; /*avoid multiple auto_increments*/ - fields.push_back(c_fld); - } - bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; - create_info.default_table_charset= thd->variables.collation_database; - db_options= 0; - if (mysql_prepare_table(thd, &create_info, fields, - keys, /*tmp_table*/ 0, db_options, table->file, - key_info_buffer, key_count, - /*select_field_count*/ 0)) - DBUG_RETURN(-1); - - /* - Check if all keys can be generated with the add_index method. - If anyone cannot, then take the old way. - */ - for (idx=0; idx< key_count; idx++) - { - DBUG_PRINT("info", ("creating index %s", key_info_buffer[idx].name)); - if (!(table->file->index_ddl_flags(key_info_buffer+idx)& - (HA_DDL_ONLINE| HA_DDL_WITH_LOCK))) - break ; - } - if ((idx < key_count)|| !key_count) - { - /* Re-initialize the create_info, which was changed by prepare table. */ - bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; - create_info.default_table_charset= thd->variables.collation_database; - /* Cleanup the fields list. We do not want to create existing fields. */ - fields.delete_elements(); - if (real_alter_table(thd, table_list->db, table_list->real_name, - &create_info, table_list, table, - fields, keys, drop, alter, 0, (ORDER*)0, - ALTER_ADD_INDEX, DUP_ERROR)) - /* Don't need to free((gptr) key_info_buffer);*/ - DBUG_RETURN(-1); - } - else - { - if (table->file->add_index(table, key_info_buffer, key_count)|| - build_table_path(path, sizeof(path), table_list->db, - (lower_case_table_names == 2) ? - table_list->alias : table_list->real_name, - reg_ext) == 0 || - mysql_create_frm(thd, path, &create_info, - fields, key_count, key_info_buffer, table->file)) - /* don't need to free((gptr) key_info_buffer);*/ - DBUG_RETURN(-1); - } - /* don't need to free((gptr) key_info_buffer);*/ - DBUG_RETURN(0); -} - - -int mysql_drop_indexes(THD *thd, TABLE_LIST *table_list, - List &drop) -{ - List fields; - List keys; - List alter; - HA_CREATE_INFO create_info; - uint idx; - uint db_options; - uint key_count; - uint *key_numbers; - TABLE *table; - Field **f_ptr; - KEY *key_info; - KEY *key_info_buffer; - char path[FN_REFLEN]; - DBUG_ENTER("mysql_drop_index"); - - /* - Try to use online generation of index. - This requires that all indexes can be created online. - Otherwise, the old alter table procedure is executed. - - Open the table to have access to the correct table handler. - */ - if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) - DBUG_RETURN(-1); - - /* - The drop_index method takes an array of key numbers. - It cannot get more entries than keys in the table. - */ - key_numbers= (uint*) thd->alloc(sizeof(uint*)*table->keys); - key_count= 0; - - /* - Get the number of each key and check if it can be created online. - */ - List_iterator drop_it(drop); - Alter_drop *drop_key; - while ((drop_key= drop_it++)) - { - /* Find the key in the table. */ - key_info=table->key_info; - for (idx=0; idx< table->keys; idx++, key_info++) - { - if (!my_strcasecmp(system_charset_info, key_info->name, drop_key->name)) - break; - } - if (idx>= table->keys) - { - my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop_key->name); - /*don't need to free((gptr) key_numbers);*/ - DBUG_RETURN(-1); - } - /* - Check if the key can be generated with the add_index method. - If anyone cannot, then take the old way. - */ - DBUG_PRINT("info", ("dropping index %s", table->key_info[idx].name)); - if (!(table->file->index_ddl_flags(table->key_info+idx)& - (HA_DDL_ONLINE| HA_DDL_WITH_LOCK))) - break ; - key_numbers[key_count++]= idx; - } - - bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; - create_info.default_table_charset= thd->variables.collation_database; - - if ((drop_key)|| (drop.elements<= 0)) - { - if (real_alter_table(thd, table_list->db, table_list->real_name, - &create_info, table_list, table, - fields, keys, drop, alter, 0, (ORDER*)0, - ALTER_DROP_INDEX, DUP_ERROR)) - /*don't need to free((gptr) key_numbers);*/ - DBUG_RETURN(-1); - } - else - { - db_options= 0; - if (table->file->drop_index(table, key_numbers, key_count)|| - mysql_prepare_table(thd, &create_info, fields, - keys, /*tmp_table*/ 0, db_options, table->file, - key_info_buffer, key_count, - /*select_field_count*/ 0)|| - build_table_path(path, sizeof(path), table_list->db, - (lower_case_table_names == 2) ? - table_list->alias : table_list->real_name, - reg_ext) == 0 || - mysql_create_frm(thd, path, &create_info, - fields, key_count, key_info_buffer, table->file)) - /*don't need to free((gptr) key_numbers);*/ - DBUG_RETURN(-1); - } - - /*don't need to free((gptr) key_numbers);*/ - DBUG_RETURN(0); -} -#endif /* NOT_USED */ /* Alter table + + + NOTE + The structures passed as 'create_info' and 'alter_info' parameters may + be modified by this function. It is responsibility of the caller to make + a copy of create_info in order to provide correct execution in prepared + statements/stored routines. */ int mysql_alter_table(THD *thd,char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, - List &fields, List &keys, + Alter_info *alter_info, uint order_num, ORDER *order, - enum enum_duplicates handle_duplicates, bool ignore, - ALTER_INFO *alter_info, bool do_send_ok) + enum enum_duplicates handle_duplicates, bool ignore) { TABLE *table,*new_table; int error; @@ -3008,8 +2821,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE); mysql_bin_log.write(&qinfo); } - if (do_send_ok) - send_ok(thd); + send_ok(thd); } else if (error > 0) { @@ -3035,10 +2847,9 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, restore_record(table,default_values); // Empty record for DEFAULT List_iterator drop_it(alter_info->drop_list); - List_iterator def_it(fields); + List_iterator def_it(alter_info->create_list); List_iterator alter_it(alter_info->alter_list); - List create_list; // Add new fields here - List key_list; // Add new keys here + Alter_info new_info; // Add new columns and indexes here create_field *def; /* @@ -3084,13 +2895,13 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, def->field=field; if (!def->after) { - create_list.push_back(def); + new_info.create_list.push_back(def); def_it.remove(); } } else { // Use old field value - create_list.push_back(def=new create_field(field,field)); + new_info.create_list.push_back(def= new create_field(field, field)); alter_it.rewind(); // Change default if ALTER Alter_column *alter; while ((alter=alter_it++)) @@ -3111,7 +2922,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } } def_it.rewind(); - List_iterator find_it(create_list); + List_iterator find_it(new_info.create_list); while ((def=def_it++)) // Add new columns { if (def->change && ! def->field) @@ -3120,9 +2931,9 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, DBUG_RETURN(-1); } if (!def->after) - create_list.push_back(def); + new_info.create_list.push_back(def); else if (def->after == first_keyword) - create_list.push_front(def); + new_info.create_list.push_front(def); else { create_field *find; @@ -3146,7 +2957,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, table_name); DBUG_RETURN(-1); } - if (!create_list.elements) + if (!new_info.create_list.elements) { my_error(ER_CANT_REMOVE_ALL_FIELDS,MYF(0)); DBUG_RETURN(-1); @@ -3157,8 +2968,8 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, for which some fields exists. */ - List_iterator key_it(keys); - List_iterator field_it(create_list); + List_iterator key_it(alter_info->key_list); + List_iterator field_it(new_info.create_list); List key_parts; KEY *key_info=table->key_info; @@ -3216,24 +3027,37 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, key_part_length)); } if (key_parts.elements) - key_list.push_back(new Key(key_info->flags & HA_SPATIAL ? Key::SPATIAL : - (key_info->flags & HA_NOSAME ? - (!my_strcasecmp(system_charset_info, - key_name, primary_key_name) ? - Key::PRIMARY : Key::UNIQUE) : - (key_info->flags & HA_FULLTEXT ? - Key::FULLTEXT : Key::MULTIPLE)), - key_name, - key_info->algorithm, - test(key_info->flags & HA_GENERATED_KEY), - key_parts)); + { + Key *key; + enum Key::Keytype key_type; + + if (key_info->flags & HA_SPATIAL) + key_type= Key::SPATIAL; + else if (key_info->flags & HA_NOSAME) + { + if (! my_strcasecmp(system_charset_info, key_name, primary_key_name)) + key_type= Key::PRIMARY; + else + key_type= Key::UNIQUE; + } + else if (key_info->flags & HA_FULLTEXT) + key_type= Key::FULLTEXT; + else + key_type= Key::MULTIPLE; + + key= new Key(key_type, key_name, + key_info->algorithm, + test(key_info->flags & HA_GENERATED_KEY), + key_parts); + new_info.key_list.push_back(key); + } } { Key *key; while ((key=key_it++)) // Add new keys { if (key->type != Key::FOREIGN_KEY) - key_list.push_back(key); + new_info.key_list.push_back(key); if (key->name && !my_strcasecmp(system_charset_info,key->name,primary_key_name)) { @@ -3337,7 +3161,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, /* We don't log the statement, it will be logged later. */ tmp_disable_binlog(thd); error= mysql_create_table(thd, new_db, tmp_name, - create_info,create_list,key_list,1,0); + create_info, &new_info, 1, 0); reenable_binlog(thd); if (error) DBUG_RETURN(error); @@ -3366,9 +3190,9 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, next_insert_id=thd->next_insert_id; // Remember for loggin copied=deleted=0; if (!new_table->is_view) - error=copy_data_between_tables(table,new_table,create_list, - handle_duplicates, ignore, - order_num, order, &copied, &deleted); + error= copy_data_between_tables(table, new_table, new_info.create_list, + handle_duplicates, ignore, + order_num, order, &copied, &deleted); thd->last_insert_id=next_insert_id; // Needed for correct log thd->count_cuted_fields= CHECK_FIELD_IGNORE; @@ -3574,8 +3398,7 @@ end_temporary: my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), (ulong) (copied + deleted), (ulong) deleted, (ulong) thd->cuted_fields); - if (do_send_ok) - send_ok(thd,copied+deleted,0L,tmp_name); + send_ok(thd, copied + deleted, 0L, tmp_name); thd->some_tables_deleted=0; DBUG_RETURN(0); @@ -3760,30 +3583,26 @@ copy_data_between_tables(TABLE *from,TABLE *to, mysql_recreate_table() thd Thread handler tables Tables to recreate - do_send_ok If we should send_ok() or leave it to caller RETURN Like mysql_alter_table(). */ -int mysql_recreate_table(THD *thd, TABLE_LIST *table_list, - bool do_send_ok) +int mysql_recreate_table(THD *thd, TABLE_LIST *table_list) { - DBUG_ENTER("mysql_recreate_table"); LEX *lex= thd->lex; HA_CREATE_INFO create_info; - lex->create_list.empty(); - lex->key_list.empty(); - lex->col_list.empty(); - lex->alter_info.reset(); - lex->alter_info.is_simple= 0; // Force full recreate + Alter_info alter_info; + + DBUG_ENTER("mysql_recreate_table"); + bzero((char*) &create_info,sizeof(create_info)); create_info.db_type=DB_TYPE_DEFAULT; create_info.row_type=ROW_TYPE_DEFAULT; create_info.default_table_charset=default_charset_info; + alter_info.is_simple= 0; // Force full recreate DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info, - table_list, lex->create_list, - lex->key_list, 0, (ORDER *) 0, - DUP_ERROR, 0, &lex->alter_info, do_send_ok)); + table_list, &alter_info, + 0, (ORDER *) 0, DUP_ERROR, 0)); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c6f4307f1ea..30283a47b21 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1021,8 +1021,7 @@ create: TL_READ_NO_INSERT: TL_READ))) YYABORT; - lex->create_list.empty(); - lex->key_list.empty(); + lex->alter_info.reset(); lex->col_list.empty(); lex->change=NullS; bzero((char*) &lex->create_info,sizeof(lex->create_info)); @@ -1040,16 +1039,18 @@ create: if (!lex->current_select->add_table_to_list(lex->thd, $7, NULL, TL_OPTION_UPDATING)) YYABORT; - lex->create_list.empty(); - lex->key_list.empty(); + lex->alter_info.reset(); + lex->alter_info.is_simple= 0; + lex->alter_info.flags= ALTER_ADD_INDEX; lex->col_list.empty(); lex->change=NullS; } '(' key_list ')' { LEX *lex=Lex; + Key *key= new Key($2, $4.str, $5, 0, lex->col_list); - lex->key_list.push_back(new Key($2,$4.str, $5, 0, lex->col_list)); + lex->alter_info.key_list.push_back(key); lex->col_list.empty(); } | CREATE DATABASE opt_if_not_exists ident @@ -1305,29 +1306,34 @@ key_def: key_type opt_ident key_alg '(' key_list ')' { LEX *lex=Lex; - lex->key_list.push_back(new Key($1,$2, $3, 0, lex->col_list)); + Key *key= new Key($1, $2, $3, 0, lex->col_list); + lex->alter_info.key_list.push_back(key); + lex->col_list.empty(); /* Alloced by sql_alloc */ } | opt_constraint constraint_key_type opt_ident key_alg '(' key_list ')' { LEX *lex=Lex; const char *key_name= $3 ? $3:$1; - lex->key_list.push_back(new Key($2, key_name, $4, 0, - lex->col_list)); + Key *key= new Key($2, key_name, $4, 0, lex->col_list); + lex->alter_info.key_list.push_back(key); lex->col_list.empty(); /* Alloced by sql_alloc */ } | opt_constraint FOREIGN KEY_SYM opt_ident '(' key_list ')' references { LEX *lex=Lex; - lex->key_list.push_back(new foreign_key($4 ? $4:$1, lex->col_list, - $8, - lex->ref_list, - lex->fk_delete_opt, - lex->fk_update_opt, - lex->fk_match_option)); - lex->key_list.push_back(new Key(Key::MULTIPLE, $4 ? $4 : $1, - HA_KEY_ALG_UNDEF, 1, - lex->col_list)); + const char *key_name= $4 ? $4 : $1; + Key *key= new foreign_key(key_name, lex->col_list, + $8, + lex->ref_list, + lex->fk_delete_opt, + lex->fk_update_opt, + lex->fk_match_option); + lex->alter_info.key_list.push_back(key); + key= new Key(Key::MULTIPLE, key_name, + HA_KEY_ALG_UNDEF, 1, + lex->col_list); + lex->alter_info.key_list.push_back(key); lex->col_list.empty(); /* Alloced by sql_alloc */ } | constraint opt_check_constraint @@ -1850,8 +1856,6 @@ alter: if (!lex->select_lex.add_table_to_list(thd, $4, NULL, TL_OPTION_UPDATING)) YYABORT; - lex->create_list.empty(); - lex->key_list.empty(); lex->col_list.empty(); lex->select_lex.init_order(); lex->select_lex.db=lex->name=0; @@ -1859,9 +1863,7 @@ alter: lex->create_info.db_type= DB_TYPE_DEFAULT; lex->create_info.default_table_charset= NULL; lex->create_info.row_type= ROW_TYPE_NOT_USED; - lex->alter_info.reset(); - lex->alter_info.is_simple= 1; - lex->alter_info.flags= 0; + lex->alter_info.reset(); } alter_list {} @@ -4030,7 +4032,9 @@ drop: { LEX *lex=Lex; lex->sql_command= SQLCOM_DROP_INDEX; - lex->alter_info.drop_list.empty(); + lex->alter_info.reset(); + lex->alter_info.is_simple= 0; + lex->alter_info.flags= ALTER_DROP_INDEX; lex->alter_info.drop_list.push_back(new Alter_drop(Alter_drop::KEY, $3.str)); if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, From 92f1c7623635adeeab08a58d83179e6b62a0b240 Mon Sep 17 00:00:00 2001 From: "kostja@bodhi.local" <> Date: Tue, 12 Dec 2006 01:50:12 +0300 Subject: [PATCH 2/2] Post-merge fixes for Bug#4968 "Stored procedure crash if cursor opened on altered table" and Bug#19733 "Repeated alter, or repeated create/drop, fails" --- mysql-test/r/ps.result | 282 ++++++++--------------------------------- mysql-test/r/sp.result | 17 +++ mysql-test/t/ps.test | 72 +++++++++++ mysql-test/t/sp.test | 28 ++++ sql/sql_insert.cc | 15 +-- sql/sql_lex.cc | 3 +- sql/sql_parse.cc | 24 ++-- sql/sql_table.cc | 20 +-- sql/sql_yacc.yy | 4 +- 9 files changed, 203 insertions(+), 262 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 7e5919af1b9..b54d31ecc99 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -1055,230 +1055,6 @@ EXECUTE stmt USING @a; 0 0 DEALLOCATE PREPARE stmt; DROP TABLE t1; -ERROR HY000: Unknown error -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 0 -set global max_prepared_stmt_count=1; -prepare stmt from "select 1"; -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 1 -prepare stmt1 from "select 1"; -ERROR HY000: Unknown error -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 1 -deallocate prepare stmt; -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 0 -prepare stmt from "select 1"; -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 1 -prepare stmt from "select 2"; -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 1 -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 1 -select @@max_prepared_stmt_count; -@@max_prepared_stmt_count -1 -set global max_prepared_stmt_count=0; -prepare stmt from "select 1"; -ERROR HY000: Unknown error -execute stmt; -ERROR HY000: Unknown prepared statement handler (stmt) given to EXECUTE -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 0 -prepare stmt from "select 1"; -ERROR HY000: Unknown error -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 0 -set global max_prepared_stmt_count=3; -select @@max_prepared_stmt_count; -@@max_prepared_stmt_count -3 -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 0 -prepare stmt from "select 1"; -prepare stmt from "select 2"; -prepare stmt1 from "select 3"; -prepare stmt2 from "select 4"; -ERROR HY000: Unknown error -prepare stmt2 from "select 4"; -ERROR HY000: Unknown error -select @@max_prepared_stmt_count; -@@max_prepared_stmt_count -3 -show status like 'prepared_stmt_count'; -Variable_name Value -Prepared_stmt_count 3 -deallocate prepare stmt; -set global max_prepared_stmt_count= @old_max_prepared_stmt_count; -drop table if exists t1; -create temporary table if not exists t1 (a1 int); -prepare stmt from "delete t1 from t1 where (cast(a1/3 as unsigned) * 3) = a1"; -drop temporary table t1; -create temporary table if not exists t1 (a1 int); -execute stmt; -drop temporary table t1; -create temporary table if not exists t1 (a1 int); -execute stmt; -drop temporary table t1; -create temporary table if not exists t1 (a1 int); -execute stmt; -drop temporary table t1; -deallocate prepare stmt; -CREATE TABLE t1( -ID int(10) unsigned NOT NULL auto_increment, -Member_ID varchar(15) NOT NULL default '', -Action varchar(12) NOT NULL, -Action_Date datetime NOT NULL, -Track varchar(15) default NULL, -User varchar(12) default NULL, -Date_Updated timestamp NOT NULL default CURRENT_TIMESTAMP on update -CURRENT_TIMESTAMP, -PRIMARY KEY (ID), -KEY Action (Action), -KEY Action_Date (Action_Date) -); -INSERT INTO t1(Member_ID, Action, Action_Date, Track) VALUES -('111111', 'Disenrolled', '2006-03-01', 'CAD' ), -('111111', 'Enrolled', '2006-03-01', 'CAD' ), -('111111', 'Disenrolled', '2006-07-03', 'CAD' ), -('222222', 'Enrolled', '2006-03-07', 'CAD' ), -('222222', 'Enrolled', '2006-03-07', 'CHF' ), -('222222', 'Disenrolled', '2006-08-02', 'CHF' ), -('333333', 'Enrolled', '2006-03-01', 'CAD' ), -('333333', 'Disenrolled', '2006-03-01', 'CAD' ), -('444444', 'Enrolled', '2006-03-01', 'CAD' ), -('555555', 'Disenrolled', '2006-03-01', 'CAD' ), -('555555', 'Enrolled', '2006-07-21', 'CAD' ), -('555555', 'Disenrolled', '2006-03-01', 'CHF' ), -('666666', 'Enrolled', '2006-02-09', 'CAD' ), -('666666', 'Enrolled', '2006-05-12', 'CHF' ), -('666666', 'Disenrolled', '2006-06-01', 'CAD' ); -PREPARE STMT FROM -"SELECT GROUP_CONCAT(Track SEPARATOR ', ') FROM t1 - WHERE Member_ID=? AND Action='Enrolled' AND - (Track,Action_Date) IN (SELECT Track, MAX(Action_Date) FROM t1 - WHERE Member_ID=? - GROUP BY Track - HAVING Track>='CAD' AND - MAX(Action_Date)>'2006-03-01')"; -SET @id='111111'; -EXECUTE STMT USING @id,@id; -GROUP_CONCAT(Track SEPARATOR ', ') -NULL -SET @id='222222'; -EXECUTE STMT USING @id,@id; -GROUP_CONCAT(Track SEPARATOR ', ') -CAD -DEALLOCATE PREPARE STMT; -DROP TABLE t1; -DROP TABLE IF EXISTS t1; -CREATE TABLE t1 (i INT, INDEX(i)); -INSERT INTO t1 VALUES (1); -PREPARE stmt FROM "SELECT (COUNT(i) = 1), COUNT(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(COUNT(i) = 1) COUNT(i) -0 0 -SET @a = 1; -EXECUTE stmt USING @a; -(COUNT(i) = 1) COUNT(i) -1 1 -SET @a = 0; -EXECUTE stmt USING @a; -(COUNT(i) = 1) COUNT(i) -0 0 -PREPARE stmt FROM "SELECT (AVG(i) = 1), AVG(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(AVG(i) = 1) AVG(i) -NULL NULL -SET @a = 1; -EXECUTE stmt USING @a; -(AVG(i) = 1) AVG(i) -1 1.0000 -SET @a = 0; -EXECUTE stmt USING @a; -(AVG(i) = 1) AVG(i) -NULL NULL -PREPARE stmt FROM "SELECT (VARIANCE(i) = 1), VARIANCE(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(VARIANCE(i) = 1) VARIANCE(i) -NULL NULL -SET @a = 1; -EXECUTE stmt USING @a; -(VARIANCE(i) = 1) VARIANCE(i) -0 0.0000 -SET @a = 0; -EXECUTE stmt USING @a; -(VARIANCE(i) = 1) VARIANCE(i) -NULL NULL -PREPARE stmt FROM "SELECT (STDDEV(i) = 1), STDDEV(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(STDDEV(i) = 1) STDDEV(i) -NULL NULL -SET @a = 1; -EXECUTE stmt USING @a; -(STDDEV(i) = 1) STDDEV(i) -0 0.0000 -SET @a = 0; -EXECUTE stmt USING @a; -(STDDEV(i) = 1) STDDEV(i) -NULL NULL -PREPARE stmt FROM "SELECT (BIT_OR(i) = 1), BIT_OR(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_OR(i) = 1) BIT_OR(i) -0 0 -SET @a = 1; -EXECUTE stmt USING @a; -(BIT_OR(i) = 1) BIT_OR(i) -1 1 -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_OR(i) = 1) BIT_OR(i) -0 0 -PREPARE stmt FROM "SELECT (BIT_AND(i) = 1), BIT_AND(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_AND(i) = 1) BIT_AND(i) -0 18446744073709551615 -SET @a = 1; -EXECUTE stmt USING @a; -(BIT_AND(i) = 1) BIT_AND(i) -1 1 -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_AND(i) = 1) BIT_AND(i) -0 18446744073709551615 -PREPARE stmt FROM "SELECT (BIT_XOR(i) = 1), BIT_XOR(i) FROM t1 WHERE i = ?"; -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_XOR(i) = 1) BIT_XOR(i) -0 0 -SET @a = 1; -EXECUTE stmt USING @a; -(BIT_XOR(i) = 1) BIT_XOR(i) -1 1 -SET @a = 0; -EXECUTE stmt USING @a; -(BIT_XOR(i) = 1) BIT_XOR(i) -0 0 -DEALLOCATE PREPARE stmt; -DROP TABLE t1; DROP TABLE IF EXISTS t1, t2; CREATE TABLE t1 (i INT); PREPARE st_19182 @@ -1311,7 +1087,7 @@ t1 CREATE TABLE `t1` ( show create table mysqltest.t2; Table Create Table t2 CREATE TABLE `t2` ( - `test` char(4) character set latin1 NOT NULL default '' + `test` varchar(4) character set latin1 NOT NULL default '' ) ENGINE=MyISAM DEFAULT CHARSET=utf8 drop table mysqltest.t1; drop table mysqltest.t2; @@ -1326,7 +1102,7 @@ t1 CREATE TABLE `t1` ( show create table mysqltest.t2; Table Create Table t2 CREATE TABLE `t2` ( - `test` char(4) NOT NULL default '' + `test` varchar(4) NOT NULL default '' ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop database mysqltest; deallocate prepare stmt1; @@ -1336,14 +1112,14 @@ show create table t1; Table Create Table t1 CREATE TABLE `t1` ( `c` char(10) default NULL -) ENGINE=MyISAM DEFAULT CHARSET=latin1 DATA DIRECTORY='MYSQLTEST_VARDIR/tmp/' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1; execute stmt; show create table t1; Table Create Table t1 CREATE TABLE `t1` ( `c` char(10) default NULL -) ENGINE=MyISAM DEFAULT CHARSET=latin1 DATA DIRECTORY='MYSQLTEST_VARDIR/tmp/' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1; deallocate prepare stmt; End of 4.1 tests. @@ -1787,4 +1563,54 @@ Variable_name Value Slow_queries 1 deallocate prepare no_index; deallocate prepare sq; +drop table if exists t1; +create table t1 (s1 char(20)); +prepare stmt from "alter table t1 modify s1 int"; +execute stmt; +execute stmt; +drop table t1; +deallocate prepare stmt; +drop table if exists t1; +create table t1 (a int, b int); +prepare s_6895 from "alter table t1 drop column b"; +execute s_6895; +show columns from t1; +Field Type Null Key Default Extra +a int(11) YES NULL +drop table t1; +create table t1 (a int, b int); +execute s_6895; +show columns from t1; +Field Type Null Key Default Extra +a int(11) YES NULL +drop table t1; +create table t1 (a int, b int); +execute s_6895; +show columns from t1; +Field Type Null Key Default Extra +a int(11) YES NULL +deallocate prepare s_6895; +drop table t1; +create table t1 (i int primary key auto_increment) comment='comment for table t1'; +create table t2 (i int, j int, k int); +prepare stmt from "alter table t1 auto_increment=100"; +execute stmt; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `i` int(11) NOT NULL auto_increment, + PRIMARY KEY (`i`) +) ENGINE=MyISAM AUTO_INCREMENT=100 DEFAULT CHARSET=latin1 COMMENT='comment for table t1' +flush tables; +select * from t2; +i j k +execute stmt; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `i` int(11) NOT NULL auto_increment, + PRIMARY KEY (`i`) +) ENGINE=MyISAM AUTO_INCREMENT=100 DEFAULT CHARSET=latin1 COMMENT='comment for table t1' +deallocate prepare stmt; +drop table t1, t2; End of 5.0 tests. diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 1bb4b3a405b..50ffe55ec02 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -5626,5 +5626,22 @@ Called B Called B drop procedure proc_21462_a| drop procedure proc_21462_b| +drop table if exists t3| +drop procedure if exists proc_bug19733| +create table t3 (s1 int)| +create procedure proc_bug19733() +begin +declare v int default 0; +while v < 100 do +create index i on t3 (s1); +drop index i on t3; +set v = v + 1; +end while; +end| +call proc_bug19733()| +call proc_bug19733()| +call proc_bug19733()| +drop procedure proc_bug19733| +drop table t3| End of 5.0 tests drop table t1,t2; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 9afb5c5ba59..e70ceeec077 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -1610,4 +1610,76 @@ execute sq; deallocate prepare no_index; deallocate prepare sq; +# +# Bug#4968 "Stored procedure crash if cursor opened on altered table" +# The bug is not repeatable any more after the fix for +# Bug#15217 "Bug #15217 Using a SP cursor on a table created with PREPARE +# fails with weird error", however ALTER TABLE is not re-execution friendly +# and that caused a valgrind warning. Check that the warning is gone. +# +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (s1 char(20)); +prepare stmt from "alter table t1 modify s1 int"; +execute stmt; +execute stmt; +drop table t1; +deallocate prepare stmt; + +# +# Bug#6895 "Prepared Statements: ALTER TABLE DROP COLUMN does nothing" +# +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int, b int); +prepare s_6895 from "alter table t1 drop column b"; +execute s_6895; +show columns from t1; +drop table t1; +create table t1 (a int, b int); +execute s_6895; +show columns from t1; +drop table t1; +create table t1 (a int, b int); +execute s_6895; +show columns from t1; +deallocate prepare s_6895; +drop table t1; + +# +# Bug #22060 "ALTER TABLE x AUTO_INCREMENT=y in SP crashes server" +# +# 5.0 part of the test. +# + +# ALTER TABLE +create table t1 (i int primary key auto_increment) comment='comment for table t1'; +create table t2 (i int, j int, k int); +prepare stmt from "alter table t1 auto_increment=100"; +execute stmt; +show create table t1; +# Let us trash table-cache's memory +flush tables; +select * from t2; +execute stmt; +show create table t1; +deallocate prepare stmt; +drop table t1, t2; +# 5.1 part of the test. +# CREATE DATABASE +#set @old_character_set_server= @@character_set_server; +#set @@character_set_server= latin1; +#prepare stmt from "create database mysqltest"; +#execute stmt; +#show create database mysqltest; +#drop database mysqltest; +#set @@character_set_server= utf8; +#execute stmt; +#show create database mysqltest; +#drop database mysqltest; +#deallocate prepare stmt; +#set @@character_set_server= @old_character_set_server; + --echo End of 5.0 tests. diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index fc6e8714a65..c0a5f69fd7c 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -6587,6 +6587,34 @@ call proc_21462_b(1)| drop procedure proc_21462_a| drop procedure proc_21462_b| + +# +# Bug#19733 "Repeated alter, or repeated create/drop, fails" +# Check that CREATE/DROP INDEX is re-execution friendly. +# +--disable_warnings +drop table if exists t3| +drop procedure if exists proc_bug19733| +--enable_warnings +create table t3 (s1 int)| + +create procedure proc_bug19733() +begin + declare v int default 0; + while v < 100 do + create index i on t3 (s1); + drop index i on t3; + set v = v + 1; + end while; +end| + +call proc_bug19733()| +call proc_bug19733()| +call proc_bug19733()| + +drop procedure proc_bug19733| +drop table t3| + --echo End of 5.0 tests diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 79e91dc4cb1..24c8e9863dd 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2620,11 +2620,11 @@ bool select_insert::send_eof() temporary table flag) create_table in Pointer to TABLE_LIST object providing database and name for table to be created or to be open - extra_fields in/out Initial list of fields for table to be created - keys in List of keys for table to be created + alter_info in/out Initial list of columns and indexes for the table + to be created items in List of items which should be used to produce rest of fields for the table (corresponding fields will - be added to the end of 'extra_fields' list) + be added to the end of alter_info->create_list) lock out Pointer to the MYSQL_LOCK object for table created (open) will be returned in this parameter. Since this table is not included in THD::lock caller is @@ -2646,8 +2646,8 @@ bool select_insert::send_eof() static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, TABLE_LIST *create_table, - List *extra_fields, - List *keys, List *items, + Alter_info *alter_info, + List *items, MYSQL_LOCK **lock) { TABLE tmp_table; // Used during 'create_field()' @@ -2686,7 +2686,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(0); if (item->maybe_null) cr_field->flags &= ~NOT_NULL_FLAG; - extra_fields->push_back(cr_field); + alter_info->create_list.push_back(cr_field); } /* create and lock table @@ -2707,8 +2707,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, { tmp_disable_binlog(thd); if (!mysql_create_table(thd, create_table->db, create_table->table_name, - create_info, *extra_fields, *keys, 0, - select_field_count)) + create_info, alter_info, 0, select_field_count)) { /* If we are here in prelocked mode we either create temporary table diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e063db58488..46fa205a25c 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1062,8 +1062,7 @@ Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) create_list(rhs.create_list, mem_root), flags(rhs.flags), keys_onoff(rhs.keys_onoff), - tablespace_op(rhs.tablespace_op), - is_simple(rhs.is_simple) + tablespace_op(rhs.tablespace_op) {} diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index aa69e3133fc..8cf6e84fc2c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2908,12 +2908,13 @@ mysql_execute_command(THD *thd) { /* out of memory when creating a copy of alter_info */ res= 1; - goto unsent_create_error; + goto end_with_restore_list; } if ((res= create_table_precheck(thd, select_tables, create_table))) goto end_with_restore_list; + #ifndef HAVE_READLINK create_info.data_file_name= create_info.index_file_name= NULL; #else @@ -2969,7 +2970,7 @@ mysql_execute_command(THD *thd) Is table which we are changing used somewhere in other parts of query */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) + if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { TABLE_LIST *duplicate; if ((duplicate= unique_table(thd, create_table, select_tables))) @@ -2980,10 +2981,10 @@ mysql_execute_command(THD *thd) } } /* If we create merge table, we have to test tables in merge, too */ - if (lex->create_info.used_fields & HA_CREATE_USED_UNION) + if (create_info.used_fields & HA_CREATE_USED_UNION) { TABLE_LIST *tab; - for (tab= (TABLE_LIST*) lex->create_info.merge_list.first; + for (tab= (TABLE_LIST*) create_info.merge_list.first; tab; tab= tab->next_local) { @@ -3021,7 +3022,7 @@ mysql_execute_command(THD *thd) /* regular create */ if (lex->name) res= mysql_create_like_table(thd, create_table, &create_info, - (Table_ident *)lex->name); + (Table_ident *)lex->name); else { res= mysql_create_table(thd, create_table->db, @@ -3075,9 +3076,9 @@ end_with_restore_list: create_info.db_type= DB_TYPE_DEFAULT; create_info.default_table_charset= thd->variables.collation_database; - res= mysql_alter_table(thd, first_table->db, first_table->real_name, + res= mysql_alter_table(thd, first_table->db, first_table->table_name, &create_info, first_table, &alter_info, - 0, (ORDER*)0, DUP_ERROR, 0); + 0, (ORDER*) 0, 0); break; } #ifdef HAVE_REPLICATION @@ -3820,6 +3821,7 @@ end_with_restore_list: break; } #endif + if (check_access(thd,CREATE_ACL,lex->name,0,1,0,is_schema_db(lex->name))) break; res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name), @@ -4507,7 +4509,7 @@ end_with_restore_list: goto error; } - my_bool nsok= thd->net.no_send_ok; + my_bool save_no_send_ok= thd->net.no_send_ok; thd->net.no_send_ok= TRUE; if (sp->m_flags & sp_head::MULTI_RESULTS) { @@ -4518,7 +4520,7 @@ end_with_restore_list: back */ my_error(ER_SP_BADSELECT, MYF(0), sp->m_qname.str); - thd->net.no_send_ok= nsok; + thd->net.no_send_ok= save_no_send_ok; goto error; } /* @@ -4534,7 +4536,7 @@ end_with_restore_list: if (check_routine_access(thd, EXECUTE_ACL, sp->m_db.str, sp->m_name.str, TRUE, FALSE)) { - thd->net.no_send_ok= nsok; + thd->net.no_send_ok= save_no_send_ok; goto error; } #endif @@ -4561,7 +4563,7 @@ end_with_restore_list: thd->variables.select_limit= select_limit; - thd->net.no_send_ok= nsok; + thd->net.no_send_ok= save_no_send_ok; thd->server_status&= ~bits_to_be_cleared; if (!res) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 56691821ce1..8207a3b7909 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1569,7 +1569,7 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) create_info [in/out] Create information (like MAX_ROWS) alter_info [in/out] List of columns and indexes to create internal_tmp_table Set to 1 if this is an internal temporary table - (From ALTER TABLE) + (From ALTER TABLE) DESCRIPTION If one creates a temporary table, this is automatically opened @@ -1592,7 +1592,7 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) bool mysql_create_table(THD *thd,const char *db, const char *table_name, HA_CREATE_INFO *create_info, Alter_info *alter_info, - List &keys,bool internal_tmp_table, + bool internal_tmp_table, uint select_field_count) { char path[FN_REFLEN]; @@ -2327,9 +2327,12 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, (table->table->file->ha_check_for_upgrade(check_opt) == HA_ADMIN_NEEDS_ALTER)) { + my_bool save_no_send_ok= thd->net.no_send_ok; close_thread_tables(thd); tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table, 0); + thd->net.no_send_ok= TRUE; + result_code= mysql_recreate_table(thd, table); + thd->net.no_send_ok= save_no_send_ok; reenable_binlog(thd); goto send_result; } @@ -2956,8 +2959,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, Alter_info *alter_info, - uint order_num, ORDER *order, - bool ignore) + uint order_num, ORDER *order, bool ignore) { TABLE *table,*new_table=0; int error; @@ -3565,7 +3567,7 @@ view_err: { tmp_disable_binlog(thd); error= mysql_create_table(thd, new_db, tmp_name, - create_info,alter_info, 1, 0); + create_info, &new_info, 1, 0); reenable_binlog(thd); if (error) DBUG_RETURN(error); @@ -4021,20 +4023,18 @@ copy_data_between_tables(TABLE *from,TABLE *to, Like mysql_alter_table(). */ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) -int mysql_recreate_table(THD *thd, TABLE_LIST *table_list) { - LEX *lex= thd->lex; HA_CREATE_INFO create_info; Alter_info alter_info; DBUG_ENTER("mysql_recreate_table"); - bzero((char*) &create_info,sizeof(create_info)); + bzero((char*) &create_info, sizeof(create_info)); create_info.db_type=DB_TYPE_DEFAULT; create_info.row_type=ROW_TYPE_NOT_USED; create_info.default_table_charset=default_charset_info; /* Force alter table to recreate table */ - lex->alter_info.flags= ALTER_CHANGE_COLUMN; + alter_info.flags= ALTER_CHANGE_COLUMN; DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info, table_list, &alter_info, 0, (ORDER *) 0, 0)); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 719d48af33f..abaed88af57 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1190,7 +1190,6 @@ create: TL_OPTION_UPDATING)) YYABORT; lex->alter_info.reset(); - lex->alter_info.is_simple= 0; lex->alter_info.flags= ALTER_ADD_INDEX; lex->col_list.empty(); lex->change=NullS; @@ -3313,7 +3312,7 @@ alter: lex->create_info.db_type= DB_TYPE_DEFAULT; lex->create_info.default_table_charset= NULL; lex->create_info.row_type= ROW_TYPE_NOT_USED; - lex->alter_info.reset(); + lex->alter_info.reset(); } alter_list {} @@ -6076,7 +6075,6 @@ drop: LEX *lex=Lex; lex->sql_command= SQLCOM_DROP_INDEX; lex->alter_info.reset(); - lex->alter_info.is_simple= 0; lex->alter_info.flags= ALTER_DROP_INDEX; lex->alter_info.drop_list.push_back(new Alter_drop(Alter_drop::KEY, $3.str));