Commit 5abc3de2 authored by konstantin@mysql.com's avatar konstantin@mysql.com

A fix and test case for Bug#5985 ""prepare stmt from "select rand(?)"

crashes server." The fix makes Item_func_rand prepared-statements
aware plus it fixes the case when RAND is used in prepared
statements and replication is on (as well as several similar issues).
Until now we did not reset THD before every execution of a prepared
statement, so if some execution had set thd->time_zone_used
or thd->rand_used they would not be reset until next mysql_parse.
Some of post-review fixes done.
parent cc57252b
...@@ -336,3 +336,42 @@ id select_type table type possible_keys key key_len ref rows Extra ...@@ -336,3 +336,42 @@ id select_type table type possible_keys key key_len ref rows Extra
- - - - - - - - NULL Impossible WHERE - - - - - - - - NULL Impossible WHERE
drop table t1; drop table t1;
deallocate prepare stmt; deallocate prepare stmt;
create table t1 (a int);
insert into t1 (a) values (1), (2), (3), (4);
set @precision=10000000000;
select rand(),
cast(rand(10)*@precision as unsigned integer),
cast(rand(a)*@precision as unsigned integer) from t1;
rand() cast(rand(10)*@precision as unsigned integer) cast(rand(a)*@precision as unsigned integer)
- 6570515219 -
- 1282061302 -
- 6698761160 -
- 9647622201 -
prepare stmt from
"select rand(),
cast(rand(10)*@precision as unsigned integer),
cast(rand(a)*@precision as unsigned integer),
cast(rand(?)*@precision as unsigned integer) from t1";
set @var=1;
execute stmt using @var;
rand() cast(rand(10)*@precision as unsigned integer) cast(rand(a)*@precision as unsigned integer) cast(rand(?)*@precision as unsigned integer)
- 6570515219 - 4054035371
- 1282061302 - 8716141803
- 6698761160 - 1418603212
- 9647622201 - 944590960
set @var=2;
execute stmt using @var;
rand() cast(rand(10)*@precision as unsigned integer) cast(rand(a)*@precision as unsigned integer) cast(rand(?)*@precision as unsigned integer)
- 6570515219 1559528654 6555866465
- 1282061302 6238114970 1223466192
- 6698761160 6511989195 6449731873
- 9647622201 3845601374 8578261098
set @var=3;
execute stmt using @var;
rand() cast(rand(10)*@precision as unsigned integer) cast(rand(a)*@precision as unsigned integer) cast(rand(?)*@precision as unsigned integer)
- 6570515219 1559528654 9057697559
- 1282061302 6238114970 3730790581
- 6698761160 6511989195 1480860534
- 9647622201 3845601374 6211931236
drop table t1;
deallocate prepare stmt;
...@@ -363,4 +363,30 @@ execute stmt using @v; ...@@ -363,4 +363,30 @@ execute stmt using @v;
drop table t1; drop table t1;
deallocate prepare stmt; deallocate prepare stmt;
#
# A test case for Bug#5985 prepare stmt from "select rand(?)" crashes
# server. Check that Item_func_rand is prepared-statements friendly.
#
create table t1 (a int);
insert into t1 (a) values (1), (2), (3), (4);
set @precision=10000000000;
--replace_column 1 - 3 -
select rand(),
cast(rand(10)*@precision as unsigned integer),
cast(rand(a)*@precision as unsigned integer) from t1;
prepare stmt from
"select rand(),
cast(rand(10)*@precision as unsigned integer),
cast(rand(a)*@precision as unsigned integer),
cast(rand(?)*@precision as unsigned integer) from t1";
set @var=1;
--replace_column 1 - 3 -
execute stmt using @var;
set @var=2;
--replace_column 1 -
execute stmt using @var;
set @var=3;
--replace_column 1 -
execute stmt using @var;
drop table t1;
deallocate prepare stmt;
...@@ -1010,21 +1010,38 @@ double Item_func_round::val() ...@@ -1010,21 +1010,38 @@ double Item_func_round::val()
} }
void Item_func_rand::fix_length_and_dec() bool Item_func_rand::fix_fields(THD *thd, struct st_table_list *tables,
Item **ref)
{ {
decimals=NOT_FIXED_DEC; Item_real_func::fix_fields(thd, tables, ref);
max_length=float_length(decimals);
used_tables_cache|= RAND_TABLE_BIT; used_tables_cache|= RAND_TABLE_BIT;
if (arg_count) if (arg_count)
{ // Only use argument once in query { // Only use argument once in query
uint32 tmp= (uint32) (args[0]->val_int()); /*
if ((rand= (struct rand_struct*) sql_alloc(sizeof(*rand)))) Allocate rand structure once: we must use thd->current_arena
randominit(rand,(uint32) (tmp*0x10001L+55555555L), to create rand in proper mem_root if it's a prepared statement or
(uint32) (tmp*0x10000001L)); stored procedure.
*/
if (!rand && !(rand= (struct rand_struct*)
thd->current_arena->alloc(sizeof(*rand))))
return TRUE;
/*
PARAM_ITEM is returned if we're in statement prepare and consequently
no placeholder value is set yet.
*/
if (args[0]->type() != PARAM_ITEM)
{
/*
TODO: do not do reinit 'rand' for every execute of PS/SP if
args[0] is a constant.
*/
uint32 tmp= (uint32) args[0]->val_int();
randominit(rand, (uint32) (tmp*0x10001L+55555555L),
(uint32) (tmp*0x10000001L));
}
} }
else else
{ {
THD *thd= current_thd;
/* /*
No need to send a Rand log event if seed was given eg: RAND(seed), No need to send a Rand log event if seed was given eg: RAND(seed),
as it will be replicated in the query as such. as it will be replicated in the query as such.
...@@ -1038,6 +1055,7 @@ void Item_func_rand::fix_length_and_dec() ...@@ -1038,6 +1055,7 @@ void Item_func_rand::fix_length_and_dec()
thd->rand_saved_seed2=thd->rand.seed2; thd->rand_saved_seed2=thd->rand.seed2;
rand= &thd->rand; rand= &thd->rand;
} }
return FALSE;
} }
void Item_func_rand::update_used_tables() void Item_func_rand::update_used_tables()
......
...@@ -512,13 +512,13 @@ class Item_func_rand :public Item_real_func ...@@ -512,13 +512,13 @@ class Item_func_rand :public Item_real_func
{ {
struct rand_struct *rand; struct rand_struct *rand;
public: public:
Item_func_rand(Item *a) :Item_real_func(a) {} Item_func_rand(Item *a) :Item_real_func(a), rand(0) {}
Item_func_rand() :Item_real_func() {} Item_func_rand() :Item_real_func() {}
double val(); double val();
const char *func_name() const { return "rand"; } const char *func_name() const { return "rand"; }
bool const_item() const { return 0; } bool const_item() const { return 0; }
void update_used_tables(); void update_used_tables();
void fix_length_and_dec(); bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
}; };
......
...@@ -447,6 +447,7 @@ bool is_update_query(enum enum_sql_command command); ...@@ -447,6 +447,7 @@ bool is_update_query(enum enum_sql_command command);
bool alloc_query(THD *thd, char *packet, ulong packet_length); bool alloc_query(THD *thd, char *packet, ulong packet_length);
void mysql_init_select(LEX *lex); void mysql_init_select(LEX *lex);
void mysql_init_query(THD *thd, uchar *buf, uint length); void mysql_init_query(THD *thd, uchar *buf, uint length);
void mysql_reset_thd_for_next_command(THD *thd);
bool mysql_new_select(LEX *lex, bool move_down); bool mysql_new_select(LEX *lex, bool move_down);
void create_select_for_variable(const char *var_name); void create_select_for_variable(const char *var_name);
void mysql_init_multi_delete(LEX *lex); void mysql_init_multi_delete(LEX *lex);
......
...@@ -1498,7 +1498,7 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup) ...@@ -1498,7 +1498,7 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup)
} }
void Statement::end_statement() void THD::end_statement()
{ {
/* Cleanup SQL processing state to resuse this statement in next query. */ /* Cleanup SQL processing state to resuse this statement in next query. */
lex_end(lex); lex_end(lex);
......
...@@ -565,12 +565,6 @@ public: ...@@ -565,12 +565,6 @@ public:
void restore_backup_statement(Statement *stmt, Statement *backup); void restore_backup_statement(Statement *stmt, Statement *backup);
/* return class type */ /* return class type */
virtual Type type() const; virtual Type type() const;
/*
Cleanup statement parse state (parse tree, lex) after execution of
a non-prepared SQL statement.
*/
void end_statement();
}; };
...@@ -1064,6 +1058,12 @@ public: ...@@ -1064,6 +1058,12 @@ public:
void nocheck_register_item_tree_change(Item **place, Item *old_value, void nocheck_register_item_tree_change(Item **place, Item *old_value,
MEM_ROOT *runtime_memroot); MEM_ROOT *runtime_memroot);
void rollback_item_tree_changes(); void rollback_item_tree_changes();
/*
Cleanup statement parse state (parse tree, lex) and execution
state after execution of a non-prepared SQL statement.
*/
void end_statement();
}; };
/* Flags for the THD::system_thread (bitmap) variable */ /* Flags for the THD::system_thread (bitmap) variable */
......
...@@ -117,7 +117,30 @@ void lex_free(void) ...@@ -117,7 +117,30 @@ void lex_free(void)
void lex_start(THD *thd, uchar *buf,uint length) void lex_start(THD *thd, uchar *buf,uint length)
{ {
LEX *lex= thd->lex; LEX *lex= thd->lex;
lex->unit.init_query();
lex->unit.init_select();
lex->thd= thd; lex->thd= thd;
lex->unit.thd= thd;
lex->select_lex.init_query();
lex->value_list.empty();
lex->param_list.empty();
lex->unit.next= lex->unit.master=
lex->unit.link_next= lex->unit.return_to= 0;
lex->unit.prev= lex->unit.link_prev= 0;
lex->unit.slave= lex->unit.global_parameters= lex->current_select=
lex->all_selects_list= &lex->select_lex;
lex->select_lex.master= &lex->unit;
lex->select_lex.prev= &lex->unit.slave;
lex->select_lex.link_next= lex->select_lex.slave= lex->select_lex.next= 0;
lex->select_lex.link_prev= (st_select_lex_node**)&(lex->all_selects_list);
lex->select_lex.options= 0;
lex->describe= 0;
lex->derived_tables= FALSE;
lex->lock_option= TL_READ;
lex->found_colon= 0;
lex->safe_to_cache_query= 1;
lex->time_zone_tables_used= 0;
lex->select_lex.select_number= 1;
lex->next_state=MY_LEX_START; lex->next_state=MY_LEX_START;
lex->end_of_query=(lex->ptr=buf)+length; lex->end_of_query=(lex->ptr=buf)+length;
lex->yylineno = 1; lex->yylineno = 1;
......
...@@ -369,7 +369,7 @@ public: ...@@ -369,7 +369,7 @@ public:
ulong init_prepare_fake_select_lex(THD *thd); ulong init_prepare_fake_select_lex(THD *thd);
int change_result(select_subselect *result, select_subselect *old_result); int change_result(select_subselect *result, select_subselect *old_result);
friend void mysql_init_query(THD *thd, uchar *buf, uint length); friend void lex_start(THD *thd, uchar *buf, uint length);
friend int subselect_union_engine::exec(); friend int subselect_union_engine::exec();
private: private:
bool create_total_list_n_last_return(THD *thd, st_lex *lex, bool create_total_list_n_last_return(THD *thd, st_lex *lex,
...@@ -508,7 +508,7 @@ public: ...@@ -508,7 +508,7 @@ public:
bool test_limit(); bool test_limit();
friend void mysql_init_query(THD *thd, uchar *buf, uint length); friend void lex_start(THD *thd, uchar *buf, uint length);
st_select_lex() {} st_select_lex() {}
void make_empty_select() void make_empty_select()
{ {
......
...@@ -1001,16 +1001,15 @@ pthread_handler_decl(handle_one_connection,arg) ...@@ -1001,16 +1001,15 @@ pthread_handler_decl(handle_one_connection,arg)
net->compress=1; // Use compression net->compress=1; // Use compression
thd->version= refresh_version; thd->version= refresh_version;
thd->proc_info= 0;
thd->set_time();
thd->init_for_queries();
if (sys_init_connect.value_length && !(thd->master_access & SUPER_ACL)) if (sys_init_connect.value_length && !(thd->master_access & SUPER_ACL))
{ {
execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect);
if (thd->query_error) if (thd->query_error)
thd->killed= 1; thd->killed= 1;
} }
thd->proc_info=0;
thd->set_time();
thd->init_for_queries();
while (!net->error && net->vio != 0 && !thd->killed) while (!net->error && net->vio != 0 && !thd->killed)
{ {
if (do_command(thd)) if (do_command(thd))
...@@ -3854,7 +3853,6 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize) ...@@ -3854,7 +3853,6 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize)
return 0; return 0;
} }
/**************************************************************************** /****************************************************************************
Initialize global thd variables needed for query Initialize global thd variables needed for query
****************************************************************************/ ****************************************************************************/
...@@ -3863,33 +3861,30 @@ void ...@@ -3863,33 +3861,30 @@ void
mysql_init_query(THD *thd, uchar *buf, uint length) mysql_init_query(THD *thd, uchar *buf, uint length)
{ {
DBUG_ENTER("mysql_init_query"); DBUG_ENTER("mysql_init_query");
LEX *lex= thd->lex;
lex->unit.init_query();
lex->unit.init_select();
lex->unit.thd= thd;
lex->select_lex.init_query();
lex->value_list.empty();
lex->param_list.empty();
lex->unit.next= lex->unit.master=
lex->unit.link_next= lex->unit.return_to=0;
lex->unit.prev= lex->unit.link_prev= 0;
lex->unit.slave= lex->unit.global_parameters= lex->current_select=
lex->all_selects_list= &lex->select_lex;
lex->select_lex.master= &lex->unit;
lex->select_lex.prev= &lex->unit.slave;
lex->select_lex.link_next= lex->select_lex.slave= lex->select_lex.next= 0;
lex->select_lex.link_prev= (st_select_lex_node**)&(lex->all_selects_list);
lex->select_lex.options=0;
lex->describe= 0;
lex->derived_tables= FALSE;
lex->lock_option= TL_READ;
lex->found_colon= 0;
lex->safe_to_cache_query= 1;
lex->time_zone_tables_used= 0;
lex_start(thd, buf, length); lex_start(thd, buf, length);
thd->select_number= lex->select_lex.select_number= 1; mysql_reset_thd_for_next_command(thd);
thd->free_list= 0; DBUG_VOID_RETURN;
thd->total_warn_count=0; // Warnings for this query }
/*
Reset THD part responsible for command processing state.
DESCRIPTION
This needs to be called before execution of every statement
(prepared or conventional).
TODO
Make it a method of THD and align its name with the rest of
reset/end/start/init methods.
Call it after we use THD for queries, not before.
*/
void mysql_reset_thd_for_next_command(THD *thd)
{
DBUG_ENTER("mysql_reset_thd_for_next_command");
thd->select_number= 1;
thd->total_warn_count= 0; // Warnings for this query
thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0; thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0;
thd->sent_row_count= thd->examined_row_count= 0; thd->sent_row_count= thd->examined_row_count= 0;
thd->is_fatal_error= thd->rand_used= thd->time_zone_used= 0; thd->is_fatal_error= thd->rand_used= thd->time_zone_used= 0;
......
...@@ -1760,6 +1760,8 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) ...@@ -1760,6 +1760,8 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
DBUG_ASSERT(thd->free_list == NULL);
mysql_reset_thd_for_next_command(thd);
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
if (stmt->param_count) if (stmt->param_count)
{ {
...@@ -1778,7 +1780,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) ...@@ -1778,7 +1780,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query)) if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query))
goto set_params_data_err; goto set_params_data_err;
#endif #endif
DBUG_ASSERT(thd->free_list == NULL);
thd->protocol= &thd->protocol_prep; // Switch to binary protocol thd->protocol= &thd->protocol_prep; // Switch to binary protocol
execute_stmt(thd, stmt, &expanded_query, true); execute_stmt(thd, stmt, &expanded_query, true);
thd->protocol= &thd->protocol_simple; // Use normal protocol thd->protocol= &thd->protocol_simple; // Use normal protocol
...@@ -1823,7 +1824,8 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) ...@@ -1823,7 +1824,8 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name)
} }
DBUG_ASSERT(thd->free_list == NULL); DBUG_ASSERT(thd->free_list == NULL);
/* Must go before setting variables, as it clears thd->user_var_events */
mysql_reset_thd_for_next_command(thd);
thd->set_n_backup_statement(stmt, &thd->stmt_backup); thd->set_n_backup_statement(stmt, &thd->stmt_backup);
if (stmt->set_params_from_vars(stmt, if (stmt->set_params_from_vars(stmt,
thd->stmt_backup.lex->prepared_stmt_params, thd->stmt_backup.lex->prepared_stmt_params,
...@@ -1932,6 +1934,7 @@ void mysql_stmt_reset(THD *thd, char *packet) ...@@ -1932,6 +1934,7 @@ void mysql_stmt_reset(THD *thd, char *packet)
*/ */
reset_stmt_params(stmt); reset_stmt_params(stmt);
mysql_reset_thd_for_next_command(thd);
send_ok(thd); send_ok(thd);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment