mirror of
https://github.com/MariaDB/server.git
synced 2026-01-06 05:22:24 +03:00
Fix for bug#4912 "mysqld crashs in case a statement is executed
a second time". The bug was caused by incompatibility of
negations elimination algorithm and PS: during first statement
execute a subtree with negation was replaced with equivalent
subtree without NOTs.
The problem was that although this transformation was permanent,
items of the new subtree were created in execute-local memory.
The patch adds means to check if it is the first execute of a
prepared statement, and if this is the case, to allocate items
in memory of the prepared statement.
The implementation:
- backports Item_arena from 5.0
- adds Item_arena::is_stmt_prepare(),
Item_arena::is_first_stmt_execute().
- deletes THD::allocate_temporary_pool_for_ps_preparing(),
THD::free_temporary_pool_for_ps_preparing(); they
were redundant.
and adds a few invariants:
- thd->free_list never contains junk (= freed items)
- thd->current_arena is never null. If there is no
prepared statement, it points at the thd.
The rest of the patch contains mainly mechanical changes and
cleanups.
mysql-test/r/ps.result:
Test results updated (test case for Bug#4912)
mysql-test/t/ps.test:
A test case for Bug#4912 "mysqld crashs in case a statement is
executed a second time"
sql/item_cmpfunc.cc:
current_statement -> current_arena
sql/item_subselect.cc:
Statement -> Item_arena, current_statement -> current_arena
sql/item_subselect.h:
Item_subselect does not need to save thd->current_statement.
sql/item_sum.cc:
Statement -> Item_arena
sql/item_sum.h:
Statement -> Item_arena
sql/mysql_priv.h:
Statement -> Item_arena
sql/sql_base.cc:
current_statement -> current_arena
sql/sql_class.cc:
- Item_arena
- convenient set_n_backup_statement, restore_backup_statement
(nice idea, Sanja)
sql/sql_class.h:
- Item_arena: backport from 5.0
- allocate_temporary_pool_for_ps_preparing,
free_temporary_pool_for_ps_preparing removed.
sql/sql_derived.cc:
current_statement -> current_arena
sql/sql_lex.cc:
current_statement -> current_arena
sql/sql_parse.cc:
Deploy invariant that thd->free_list never contains junk items
(backport from 5.0).
sql/sql_prepare.cc:
- backporting Item_arena
- no need to allocate_temporary_pool_for_ps_preparing().
sql/sql_select.cc:
Fix for bug#4912 "mysqld crashs in case a statement is
executed a second time": if this is the first execute of
a prepared statement, negation elimination is
done in memory of the prepared statement.
sql/sql_union.cc:
Backporting Item_arena from 5.0.
This commit is contained in:
@@ -88,7 +88,6 @@ public:
|
||||
uint param_count;
|
||||
uint last_errno;
|
||||
char last_error[MYSQL_ERRMSG_SIZE];
|
||||
bool get_longdata_error;
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end,
|
||||
uchar *read_pos, String *expanded_query);
|
||||
@@ -102,7 +101,7 @@ public:
|
||||
Prepared_statement(THD *thd_arg);
|
||||
virtual ~Prepared_statement();
|
||||
void setup_set_params();
|
||||
virtual Statement::Type type() const;
|
||||
virtual Item_arena::Type type() const;
|
||||
};
|
||||
|
||||
static void execute_stmt(THD *thd, Prepared_statement *stmt,
|
||||
@@ -133,7 +132,7 @@ find_prepared_statement(THD *thd, ulong id, const char *where,
|
||||
{
|
||||
Statement *stmt= thd->stmt_map.find(id);
|
||||
|
||||
if (stmt == 0 || stmt->type() != Statement::PREPARED_STATEMENT)
|
||||
if (stmt == 0 || stmt->type() != Item_arena::PREPARED_STATEMENT)
|
||||
{
|
||||
char llbuf[22];
|
||||
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), 22, llstr(id, llbuf), where);
|
||||
@@ -894,10 +893,8 @@ static int mysql_test_insert(Prepared_statement *stmt,
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
if (open_and_lock_tables(thd, table_list))
|
||||
{
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
|
||||
@@ -932,7 +929,6 @@ static int mysql_test_insert(Prepared_statement *stmt,
|
||||
res= 0;
|
||||
error:
|
||||
lex->unit.cleanup();
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
|
||||
@@ -961,12 +957,6 @@ static int mysql_test_update(Prepared_statement *stmt,
|
||||
if ((res= update_precheck(thd, table_list)))
|
||||
DBUG_RETURN(res);
|
||||
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
|
||||
if (open_and_lock_tables(thd, table_list))
|
||||
res= -1;
|
||||
else
|
||||
@@ -986,7 +976,6 @@ static int mysql_test_update(Prepared_statement *stmt,
|
||||
}
|
||||
stmt->lex->unit.cleanup();
|
||||
}
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
/* TODO: here we should send types of placeholders to the client. */
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
@@ -1016,12 +1005,6 @@ static int mysql_test_delete(Prepared_statement *stmt,
|
||||
if ((res= delete_precheck(thd, table_list)))
|
||||
DBUG_RETURN(res);
|
||||
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
|
||||
if (open_and_lock_tables(thd, table_list))
|
||||
res= -1;
|
||||
else
|
||||
@@ -1029,7 +1012,6 @@ static int mysql_test_delete(Prepared_statement *stmt,
|
||||
res= mysql_prepare_delete(thd, table_list, &lex->select_lex.where);
|
||||
lex->unit.cleanup();
|
||||
}
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
/* TODO: here we should send types of placeholders to the client. */
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
@@ -1071,11 +1053,6 @@ static int mysql_test_select(Prepared_statement *stmt,
|
||||
DBUG_RETURN(1);
|
||||
#endif
|
||||
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
if (open_and_lock_tables(thd, tables))
|
||||
{
|
||||
send_error(thd);
|
||||
@@ -1090,33 +1067,30 @@ static int mysql_test_select(Prepared_statement *stmt,
|
||||
send_error(thd);
|
||||
goto err_prep;
|
||||
}
|
||||
if (lex->describe)
|
||||
if (!text_protocol)
|
||||
{
|
||||
if (!text_protocol && send_prep_stmt(stmt, 0))
|
||||
goto err_prep;
|
||||
unit->cleanup();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!text_protocol)
|
||||
if (lex->describe)
|
||||
{
|
||||
if (send_prep_stmt(stmt, 0))
|
||||
goto err_prep;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (send_prep_stmt(stmt, lex->select_lex.item_list.elements) ||
|
||||
thd->protocol_simple.send_fields(&lex->select_lex.item_list, 0)
|
||||
thd->protocol_simple.send_fields(&lex->select_lex.item_list, 0)
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
|| net_flush(&thd->net)
|
||||
#endif
|
||||
)
|
||||
goto err_prep;
|
||||
}
|
||||
unit->cleanup();
|
||||
}
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
unit->cleanup();
|
||||
DBUG_RETURN(0);
|
||||
|
||||
err_prep:
|
||||
unit->cleanup();
|
||||
err:
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
@@ -1145,19 +1119,13 @@ static int mysql_test_do_fields(Prepared_statement *stmt,
|
||||
int res= 0;
|
||||
if (tables && (res= check_table_access(thd, SELECT_ACL, tables, 0)))
|
||||
DBUG_RETURN(res);
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
|
||||
if (tables && (res= open_and_lock_tables(thd, tables)))
|
||||
{
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
res= setup_fields(thd, 0, 0, *values, 0, 0, 0);
|
||||
stmt->lex->unit.cleanup();
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
if (res)
|
||||
DBUG_RETURN(-1);
|
||||
DBUG_RETURN(0);
|
||||
@@ -1190,11 +1158,7 @@ static int mysql_test_set_fields(Prepared_statement *stmt,
|
||||
|
||||
if (tables && (res= check_table_access(thd, SELECT_ACL, tables, 0)))
|
||||
DBUG_RETURN(res);
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
|
||||
if (tables && (res= open_and_lock_tables(thd, tables)))
|
||||
goto error;
|
||||
while ((var= it++))
|
||||
@@ -1208,7 +1172,6 @@ static int mysql_test_set_fields(Prepared_statement *stmt,
|
||||
}
|
||||
error:
|
||||
stmt->lex->unit.cleanup();
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
|
||||
@@ -1233,11 +1196,7 @@ static int select_like_statement_test(Prepared_statement *stmt,
|
||||
THD *thd= stmt->thd;
|
||||
LEX *lex= stmt->lex;
|
||||
int res= 0;
|
||||
/*
|
||||
open temporary memory pool for temporary data allocated by derived
|
||||
tables & preparation procedure
|
||||
*/
|
||||
thd->allocate_temporary_memory_pool_for_ps_preparing();
|
||||
|
||||
if (tables && (res= open_and_lock_tables(thd, tables)))
|
||||
goto end;
|
||||
|
||||
@@ -1250,7 +1209,6 @@ static int select_like_statement_test(Prepared_statement *stmt,
|
||||
}
|
||||
end:
|
||||
lex->unit.cleanup();
|
||||
thd->free_temporary_memory_pool_for_ps_preparing();
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
|
||||
@@ -1594,17 +1552,13 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
thd->stmt_backup.set_statement(thd);
|
||||
thd->stmt_backup.set_item_arena(thd);
|
||||
thd->set_statement(stmt);
|
||||
thd->set_item_arena(stmt);
|
||||
thd->set_n_backup_statement(stmt, &thd->stmt_backup);
|
||||
thd->set_n_backup_item_arena(stmt, &thd->stmt_backup);
|
||||
|
||||
if (alloc_query(thd, packet, packet_length))
|
||||
{
|
||||
stmt->set_statement(thd);
|
||||
stmt->set_item_arena(thd);
|
||||
thd->set_statement(&thd->stmt_backup);
|
||||
thd->set_item_arena(&thd->stmt_backup);
|
||||
thd->restore_backup_statement(stmt, &thd->stmt_backup);
|
||||
thd->restore_backup_item_arena(stmt, &thd->stmt_backup);
|
||||
/* Statement map deletes statement on erase */
|
||||
thd->stmt_map.erase(stmt);
|
||||
send_error(thd, ER_OUT_OF_RESOURCES);
|
||||
@@ -1613,24 +1567,36 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
|
||||
|
||||
mysql_log.write(thd, COM_PREPARE, "%s", packet);
|
||||
|
||||
thd->current_statement= stmt;
|
||||
thd->current_arena= stmt;
|
||||
mysql_init_query(thd, (uchar *) thd->query, thd->query_length);
|
||||
lex= thd->lex;
|
||||
lex->safe_to_cache_query= 0;
|
||||
|
||||
error= yyparse((void *)thd) || thd->is_fatal_error ||
|
||||
init_param_array(stmt) ||
|
||||
send_prepare_results(stmt, test(name));
|
||||
init_param_array(stmt);
|
||||
/*
|
||||
While doing context analysis of the query (in send_prepare_results) we
|
||||
allocate a lot of additional memory: for open tables, JOINs, derived
|
||||
tables, etc. Let's save a snapshot of current parse tree to the
|
||||
statement and restore original THD. In cases when some tree
|
||||
transformation can be reused on execute, we set again thd->mem_root from
|
||||
stmt->mem_root (see setup_wild for one place where we do that).
|
||||
*/
|
||||
thd->restore_backup_item_arena(stmt, &thd->stmt_backup);
|
||||
|
||||
if (!error)
|
||||
error= send_prepare_results(stmt, test(name));
|
||||
|
||||
/* restore to WAIT_PRIOR: QUERY_PRIOR is set inside alloc_query */
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(),WAIT_PRIOR);
|
||||
lex_end(lex);
|
||||
stmt->set_statement(thd);
|
||||
stmt->set_item_arena(thd);
|
||||
thd->set_statement(&thd->stmt_backup);
|
||||
thd->set_item_arena(&thd->stmt_backup);
|
||||
thd->current_statement= 0;
|
||||
thd->restore_backup_statement(stmt, &thd->stmt_backup);
|
||||
cleanup_items(stmt->free_list);
|
||||
close_thread_tables(thd);
|
||||
free_items(thd->free_list);
|
||||
thd->free_list= 0;
|
||||
thd->current_arena= thd;
|
||||
|
||||
if (error)
|
||||
{
|
||||
@@ -1651,7 +1617,7 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
|
||||
{
|
||||
sl->prep_where= sl->where;
|
||||
}
|
||||
|
||||
stmt->state= Prepared_statement::PREPARED;
|
||||
}
|
||||
|
||||
DBUG_RETURN(!stmt);
|
||||
@@ -1765,7 +1731,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
|
||||
DBUG_PRINT("exec_query:", ("%s", stmt->query));
|
||||
|
||||
/* Check if we got an error when sending long data */
|
||||
if (stmt->get_longdata_error)
|
||||
if (stmt->state == Item_arena::ERROR)
|
||||
{
|
||||
send_error(thd, stmt->last_errno, stmt->last_error);
|
||||
DBUG_VOID_RETURN;
|
||||
@@ -1789,6 +1755,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
|
||||
if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query))
|
||||
goto set_params_data_err;
|
||||
#endif
|
||||
DBUG_ASSERT(thd->free_list == NULL);
|
||||
thd->protocol= &thd->protocol_prep; // Switch to binary protocol
|
||||
execute_stmt(thd, stmt, &expanded_query, true);
|
||||
thd->protocol= &thd->protocol_simple; // Use normal protocol
|
||||
@@ -1832,9 +1799,9 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name)
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
thd->free_list= NULL;
|
||||
thd->stmt_backup.set_statement(thd);
|
||||
thd->set_statement(stmt);
|
||||
DBUG_ASSERT(thd->free_list == NULL);
|
||||
|
||||
thd->set_n_backup_statement(stmt, &thd->stmt_backup);
|
||||
if (stmt->set_params_from_vars(stmt,
|
||||
thd->stmt_backup.lex->prepared_stmt_params,
|
||||
&expanded_query))
|
||||
@@ -1866,11 +1833,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
|
||||
{
|
||||
DBUG_ENTER("execute_stmt");
|
||||
if (set_context)
|
||||
{
|
||||
thd->free_list= NULL;
|
||||
thd->stmt_backup.set_statement(thd);
|
||||
thd->set_statement(stmt);
|
||||
}
|
||||
thd->set_n_backup_statement(stmt, &thd->stmt_backup);
|
||||
reset_stmt_for_execute(stmt);
|
||||
|
||||
if (expanded_query->length() &&
|
||||
@@ -1880,6 +1843,13 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
|
||||
my_error(ER_OUTOFMEMORY, 0, expanded_query->length());
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
/*
|
||||
At first execution of prepared statement we will perform logical
|
||||
transformations of the query tree (i.e. negations elimination).
|
||||
This should be done permanently on the parse tree of this statement.
|
||||
*/
|
||||
if (stmt->state == Item_arena::PREPARED)
|
||||
thd->current_arena= stmt;
|
||||
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
|
||||
@@ -1890,6 +1860,12 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
|
||||
|
||||
/* Free Items that were created during this execution of the PS. */
|
||||
free_items(thd->free_list);
|
||||
thd->free_list= 0;
|
||||
if (stmt->state == Item_arena::PREPARED)
|
||||
{
|
||||
thd->current_arena= thd;
|
||||
stmt->state= Item_arena::EXECUTED;
|
||||
}
|
||||
cleanup_items(stmt->free_list);
|
||||
reset_stmt_params(stmt);
|
||||
close_thread_tables(thd); // to close derived tables
|
||||
@@ -1927,7 +1903,7 @@ void mysql_stmt_reset(THD *thd, char *packet)
|
||||
SEND_ERROR)))
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
stmt->get_longdata_error= 0;
|
||||
stmt->state= Item_arena::PREPARED;
|
||||
|
||||
/*
|
||||
Clear parameters from data which could be set by
|
||||
@@ -2015,7 +1991,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
|
||||
if (param_number >= stmt->param_count)
|
||||
{
|
||||
/* Error will be sent in execute call */
|
||||
stmt->get_longdata_error= 1;
|
||||
stmt->state= Item_arena::ERROR;
|
||||
stmt->last_errno= ER_WRONG_ARGUMENTS;
|
||||
sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS),
|
||||
"mysql_stmt_send_long_data");
|
||||
@@ -2026,10 +2002,15 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
|
||||
param= stmt->param_array[param_number];
|
||||
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
param->set_longdata(packet, (ulong) (packet_end - packet));
|
||||
if (param->set_longdata(packet, (ulong) (packet_end - packet)))
|
||||
#else
|
||||
param->set_longdata(thd->extra_data, thd->extra_length);
|
||||
if (param->set_longdata(thd->extra_data, thd->extra_length))
|
||||
#endif
|
||||
{
|
||||
stmt->state= Item_arena::ERROR;
|
||||
stmt->last_errno= ER_OUTOFMEMORY;
|
||||
sprintf(stmt->last_error, ER(ER_OUTOFMEMORY), 0);
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
@@ -2039,8 +2020,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg)
|
||||
thd(thd_arg),
|
||||
param_array(0),
|
||||
param_count(0),
|
||||
last_errno(0),
|
||||
get_longdata_error(0)
|
||||
last_errno(0)
|
||||
{
|
||||
*last_error= '\0';
|
||||
}
|
||||
@@ -2074,7 +2054,7 @@ Prepared_statement::~Prepared_statement()
|
||||
}
|
||||
|
||||
|
||||
Statement::Type Prepared_statement::type() const
|
||||
Item_arena::Type Prepared_statement::type() const
|
||||
{
|
||||
return PREPARED_STATEMENT;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user