1
0
mirror of https://github.com/MariaDB/server.git synced 2025-12-24 11:21:21 +03:00

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:
  Update test results (Bug#19182, Bug#22060)
mysql-test/t/ps.test:
  Add a test case for Bug#19182, Bug#22060 (4.1-only parts)
sql/mysql_priv.h:
  LEX::key_list and LEX::create_list were moved to LEX::alter_info.
  Update declarations to use LEX::alter_info instead of these two
  members.
sql/sql_class.h:
  Replace pair<columns, keys> with an instance of Alter_info in
  select_create constructor. We create a new copy of Alter_info
  each time we re-execute SELECT .. CREATE prepared statement.
sql/sql_insert.cc:
  Adjust to a new signature of create_table_from_items.
sql/sql_lex.cc:
  Implement Alter_info::Alter_info that would make a "deep" copy
  of all definition lists (keys, columns).
sql/sql_lex.h:
  Move key_list and create_list to class Alter_info. Implement
  Alter_info::Alter_info that can be used with PS and SP.
sql/sql_list.h:
  Implement a copy constructor of class List that makes a deep copy
  of all list nodes.
sql/sql_parse.cc:
  Adjust to new signatures of mysql_create_table, mysql_alter_table,
  select_create. Functions mysql_create_index and mysql_drop_index has
  become identical after initialization of alter_info was moved to the 
  parser, and were merged. Flag enable_slow_log was not updated for 
  SQLCOM_DROP_INDEX, which is a bug. Just like CREATE INDEX, DROP INDEX
  is currently done via complete table rebuild and is rightfully a slow
  administrative statement.
sql/sql_show.cc:
  Adjust mysqld_show_create_db to a new signature.
sql/sql_table.cc:
  Adjust mysql_alter_table, mysql_recreate_table, mysql_create_table,
  mysql_prepare_table to new signatures.
sql/sql_yacc.yy:
  LEX::key_list and LEX::create_list moved to class Alter_info
This commit is contained in:
unknown
2006-12-08 02:20:09 +03:00
parent 73079a2406
commit e47ded8114
12 changed files with 511 additions and 441 deletions

View File

@@ -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<Key> &keys)
{
List<create_field> 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<create_field> fields;
List<Key> 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