MDEV-14857: problem with 10.2.11 server crashing when executing stored procedure

Counter for select numbering made stored with the statement (before was global)
So now it does have always accurate value which does not depend on
interruption of statement prepare by errors like lack of table in
a view definition.
This commit is contained in:
Oleksandr Byelkin 2018-01-26 16:59:53 +01:00
parent ad0013c8e2
commit 80d3eee072
15 changed files with 341 additions and 63 deletions

View File

@ -8119,4 +8119,127 @@ SET @aux = f1();
DROP FUNCTION f1;
DROP VIEW v1;
DROP TABLE t1;
#
# MDEV-14857: problem with 10.2.11 server crashing when
# executing stored procedure
#
SET max_sp_recursion_depth=10;
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (b INT);
CREATE PROCEDURE proc_0()
BEGIN
CALL empty_1();
CALL proc_1();
END ||
CREATE PROCEDURE proc_1()
BEGIN
CALL proc_2();
CALL proc_3();
CALL proc_4();
CALL proc_5();
END ||
CREATE PROCEDURE proc_2()
CALL proc_6();
||
CREATE PROCEDURE proc_3()
BEGIN
CALL empty_2();
CALL empty_3();
END ||
CREATE PROCEDURE proc_4()
CALL proc_7();
||
CREATE PROCEDURE proc_5()
CALL proc_select();
||
CREATE PROCEDURE proc_6()
BEGIN
CALL empty_4();
CALL empty_5();
CALL empty_6();
CALL empty_7();
CALL proc_8();
END ||
CREATE PROCEDURE proc_7()
CALL proc_9('foo');
||
CREATE PROCEDURE proc_8()
CALL proc_10();
||
CREATE PROCEDURE proc_9(IN opt VARCHAR(40))
IF LEFT(opt,1) <> '_' THEN
CALL proc_11();
END IF;
||
CREATE PROCEDURE proc_10()
CALL proc_12();
||
CREATE PROCEDURE proc_11()
BEGIN
CALL empty_8();
CALL empty_9();
CALL empty_10();
CALL proc_13();
END ||
CREATE PROCEDURE proc_12()
BEGIN
CALL empty_11();
CALL empty_12();
CALL empty_13();
END ||
CREATE PROCEDURE proc_13()
BEGIN
CALL proc_9('_bar');
CALL empty_14();
END ||
CREATE PROCEDURE empty_1() BEGIN END ;
CREATE PROCEDURE empty_2() BEGIN END ;
CREATE PROCEDURE empty_3() BEGIN END ;
CREATE PROCEDURE empty_4() BEGIN END ;
CREATE PROCEDURE empty_5() BEGIN END ;
CREATE PROCEDURE empty_6() BEGIN END ;
CREATE PROCEDURE empty_7() BEGIN END ;
CREATE PROCEDURE empty_8() BEGIN END ;
CREATE PROCEDURE empty_9() BEGIN END ;
CREATE PROCEDURE empty_10() BEGIN END ;
CREATE PROCEDURE empty_11() BEGIN END ;
CREATE PROCEDURE empty_12() BEGIN END ;
CREATE PROCEDURE empty_13() BEGIN END ;
CREATE PROCEDURE empty_14() BEGIN END ;
CREATE PROCEDURE proc_select()
SELECT * FROM t1 WHERE NOT EXISTS ( SELECT * FROM t2)
;
CALL proc_0();
a
DROP PROCEDURE empty_1;
DROP PROCEDURE empty_2;
DROP PROCEDURE empty_3;
DROP PROCEDURE empty_4;
DROP PROCEDURE empty_5;
DROP PROCEDURE empty_6;
DROP PROCEDURE empty_7;
DROP PROCEDURE empty_8;
DROP PROCEDURE empty_9;
DROP PROCEDURE empty_10;
DROP PROCEDURE empty_11;
DROP PROCEDURE empty_12;
DROP PROCEDURE empty_13;
DROP PROCEDURE empty_14;
DROP PROCEDURE proc_0;
DROP PROCEDURE proc_1;
DROP PROCEDURE proc_2;
DROP PROCEDURE proc_3;
DROP PROCEDURE proc_4;
DROP PROCEDURE proc_5;
DROP PROCEDURE proc_6;
DROP PROCEDURE proc_7;
DROP PROCEDURE proc_8;
DROP PROCEDURE proc_9;
DROP PROCEDURE proc_10;
DROP PROCEDURE proc_11;
DROP PROCEDURE proc_12;
DROP PROCEDURE proc_13;
DROP PROCEDURE proc_select;
DROP TABLE t1, t2;
SET max_sp_recursion_depth=default;
#End of 10.1 tests

View File

@ -9605,4 +9605,153 @@ DROP FUNCTION f1;
DROP VIEW v1;
DROP TABLE t1;
--echo #
--echo # MDEV-14857: problem with 10.2.11 server crashing when
--echo # executing stored procedure
--echo #
SET max_sp_recursion_depth=10;
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (b INT);
delimiter ||;
CREATE PROCEDURE proc_0()
BEGIN
CALL empty_1();
CALL proc_1();
END ||
CREATE PROCEDURE proc_1()
BEGIN
CALL proc_2();
CALL proc_3();
CALL proc_4();
CALL proc_5();
END ||
CREATE PROCEDURE proc_2()
CALL proc_6();
||
CREATE PROCEDURE proc_3()
BEGIN
CALL empty_2();
CALL empty_3();
END ||
CREATE PROCEDURE proc_4()
CALL proc_7();
||
CREATE PROCEDURE proc_5()
CALL proc_select();
||
CREATE PROCEDURE proc_6()
BEGIN
CALL empty_4();
CALL empty_5();
CALL empty_6();
CALL empty_7();
CALL proc_8();
END ||
CREATE PROCEDURE proc_7()
CALL proc_9('foo');
||
CREATE PROCEDURE proc_8()
CALL proc_10();
||
CREATE PROCEDURE proc_9(IN opt VARCHAR(40))
IF LEFT(opt,1) <> '_' THEN
CALL proc_11();
END IF;
||
CREATE PROCEDURE proc_10()
CALL proc_12();
||
CREATE PROCEDURE proc_11()
BEGIN
CALL empty_8();
CALL empty_9();
CALL empty_10();
CALL proc_13();
END ||
CREATE PROCEDURE proc_12()
BEGIN
CALL empty_11();
CALL empty_12();
CALL empty_13();
END ||
CREATE PROCEDURE proc_13()
BEGIN
CALL proc_9('_bar');
CALL empty_14();
END ||
delimiter ;||
CREATE PROCEDURE empty_1() BEGIN END ;
CREATE PROCEDURE empty_2() BEGIN END ;
CREATE PROCEDURE empty_3() BEGIN END ;
CREATE PROCEDURE empty_4() BEGIN END ;
CREATE PROCEDURE empty_5() BEGIN END ;
CREATE PROCEDURE empty_6() BEGIN END ;
CREATE PROCEDURE empty_7() BEGIN END ;
CREATE PROCEDURE empty_8() BEGIN END ;
CREATE PROCEDURE empty_9() BEGIN END ;
CREATE PROCEDURE empty_10() BEGIN END ;
CREATE PROCEDURE empty_11() BEGIN END ;
CREATE PROCEDURE empty_12() BEGIN END ;
CREATE PROCEDURE empty_13() BEGIN END ;
CREATE PROCEDURE empty_14() BEGIN END ;
CREATE PROCEDURE proc_select()
SELECT * FROM t1 WHERE NOT EXISTS ( SELECT * FROM t2)
;
CALL proc_0();
# Cleanup
DROP PROCEDURE empty_1;
DROP PROCEDURE empty_2;
DROP PROCEDURE empty_3;
DROP PROCEDURE empty_4;
DROP PROCEDURE empty_5;
DROP PROCEDURE empty_6;
DROP PROCEDURE empty_7;
DROP PROCEDURE empty_8;
DROP PROCEDURE empty_9;
DROP PROCEDURE empty_10;
DROP PROCEDURE empty_11;
DROP PROCEDURE empty_12;
DROP PROCEDURE empty_13;
DROP PROCEDURE empty_14;
DROP PROCEDURE proc_0;
DROP PROCEDURE proc_1;
DROP PROCEDURE proc_2;
DROP PROCEDURE proc_3;
DROP PROCEDURE proc_4;
DROP PROCEDURE proc_5;
DROP PROCEDURE proc_6;
DROP PROCEDURE proc_7;
DROP PROCEDURE proc_8;
DROP PROCEDURE proc_9;
DROP PROCEDURE proc_10;
DROP PROCEDURE proc_11;
DROP PROCEDURE proc_12;
DROP PROCEDURE proc_13;
DROP PROCEDURE proc_select;
DROP TABLE t1, t2;
SET max_sp_recursion_depth=default;
--echo #End of 10.1 tests

View File

@ -760,7 +760,6 @@ static sp_head *sp_compile(THD *thd, String *defstr, ulonglong sql_mode,
else
{
sp= thd->lex->sphead;
sp->set_select_number(thd->select_number);
}
thd->pop_internal_handler();

View File

@ -597,7 +597,7 @@ sp_head::sp_head()
m_flags(0),
m_sp_cache_version(0),
m_creation_ctx(0),
unsafe_flags(0), m_select_number(1),
unsafe_flags(0),
m_recursion_level(0),
m_next_cached_sp(0),
m_cont_level(0)
@ -840,7 +840,7 @@ sp_head::~sp_head()
thd->lex->sphead= NULL;
lex_end(thd->lex);
delete thd->lex;
thd->lex= lex;
thd->lex= thd->stmt_lex= lex;
}
my_hash_free(&m_sptabs);
@ -1121,7 +1121,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
backup_arena;
query_id_t old_query_id;
TABLE *old_derived_tables;
LEX *old_lex;
LEX *old_lex, *old_stmt_lex;
Item_change_list old_change_list;
String old_packet;
uint old_server_status;
@ -1136,19 +1136,6 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
if (check_stack_overrun(thd, 7 * STACK_MIN_SIZE, (uchar*)&old_packet))
DBUG_RETURN(TRUE);
/*
Normally the counter is not reset between parsing and first execution,
but it is possible in case of error to have parsing on one CALL and
first execution (where VIEW will be parsed and added). So we store the
counter after parsing and restore it before execution just to avoid
repeating SELECT numbers.
Other problem is that it can be more SELECTs parsed in case of fixing
error causes previous interruption of the SP. So it is save not just
assign old value but add it.
*/
thd->select_number+= m_select_number;
/* init per-instruction memroot */
init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
@ -1237,6 +1224,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
do it in each instruction
*/
old_lex= thd->lex;
old_stmt_lex= thd->stmt_lex;
/*
We should also save Item tree change list to avoid rollback something
too early in the calling query.
@ -1384,6 +1372,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
DBUG_ASSERT(thd->change_list.is_empty());
old_change_list.move_elements_to(&thd->change_list);
thd->lex= old_lex;
thd->stmt_lex= old_stmt_lex;
thd->set_query_id(old_query_id);
DBUG_ASSERT(!thd->derived_tables);
thd->derived_tables= old_derived_tables;
@ -1482,16 +1471,6 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
m_recursion_level + 1));
m_first_instance->m_first_free_instance= this;
/*
This execution of the SP was aborted with an error (e.g. "Table not
found"). However it might still have consumed some numbers from the
thd->select_number counter. The next sp->exec() call must not use the
consumed numbers, so we remember the first free number (We know that
nobody will use it as this execution has stopped with an error).
*/
if (err_status)
set_select_number(thd->select_number);
DBUG_RETURN(err_status);
}
@ -2228,7 +2207,7 @@ sp_head::reset_lex(THD *thd)
if (sublex == 0)
DBUG_RETURN(TRUE);
thd->lex= sublex;
thd->lex= thd->stmt_lex= sublex;
(void)m_lex.push_front(oldlex);
/* Reset most stuff. */
@ -2974,7 +2953,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
We should not save old value since it is saved/restored in
sp_head::execute() when we are entering/leaving routine.
*/
thd->lex= m_lex;
thd->lex= thd->stmt_lex= m_lex;
thd->set_query_id(next_query_id());

View File

@ -232,7 +232,6 @@ private:
*/
uint32 unsafe_flags;
uint m_select_number;
public:
inline Stored_program_creation_ctx *get_creation_ctx()
{
@ -522,8 +521,6 @@ public:
sp_pcontext *get_parse_context() { return m_pcont; }
void set_select_number(uint num) { m_select_number= num; }
private:
MEM_ROOT *m_thd_root; ///< Temp. store for thd's mem_root

View File

@ -3654,7 +3654,7 @@ void Statement::set_statement(Statement *stmt)
{
id= stmt->id;
mark_used_columns= stmt->mark_used_columns;
lex= stmt->lex;
stmt_lex= lex= stmt->lex;
query_string= stmt->query_string;
}

View File

@ -1027,6 +1027,21 @@ public:
LEX_STRING name; /* name for named prepared statements */
LEX *lex; // parse tree descriptor
/*
LEX which represents current statement (conventional, SP or PS)
For example during view parsing THD::lex will point to the views LEX and
THD::stmt_lex will point to LEX of the statement where the view will be
included
Currently it is used to have always correct select numbering inside
statement (LEX::current_select_number) without storing and restoring a
global counter which was THD::select_number.
TODO: make some unified statement representation (now SP has different)
to store such data like LEX::current_select_number.
*/
LEX *stmt_lex;
/*
Points to the query associated with this statement. It's const, but
we need to declare it char * because all table handlers are written
@ -2690,7 +2705,6 @@ public:
uint tmp_table, global_disable_checkpoint;
uint server_status,open_options;
enum enum_thread_type system_thread;
uint select_number; //number of select (used for EXPLAIN)
/*
Current or next transaction isolation level.
When a connection is established, the value is taken from

View File

@ -208,6 +208,9 @@ public:
Explain_select(MEM_ROOT *root, bool is_analyze) :
Explain_basic_join(root),
#ifndef DBUG_OFF
select_lex(NULL),
#endif
message(NULL),
having(NULL), having_value(Item::COND_UNDEF),
using_temporary(false), using_filesort(false),
@ -222,6 +225,9 @@ public:
void replace_table(uint idx, Explain_table_access *new_tab);
public:
#ifndef DBUG_OFF
SELECT_LEX *select_lex;
#endif
const char *select_type;
/*

View File

@ -657,6 +657,7 @@ void lex_start(THD *thd)
{
LEX *lex= thd->lex;
DBUG_ENTER("lex_start");
DBUG_PRINT("info", ("Lex %p stmt_lex: %p", thd->lex, thd->stmt_lex));
lex->thd= lex->unit.thd= thd;
@ -668,6 +669,7 @@ void lex_start(THD *thd)
/* 'parent_lex' is used in init_query() so it must be before it. */
lex->select_lex.parent_lex= lex;
lex->select_lex.init_query();
lex->current_select_number= 1;
lex->value_list.empty();
lex->update_list.empty();
lex->set_var_list.empty();

View File

@ -727,7 +727,13 @@ public:
Item *prep_having;/* saved HAVING clause for prepared statement processing */
/* Saved values of the WHERE and HAVING clauses*/
Item::cond_result cond_value, having_value;
/* point on lex in which it was created, used in view subquery detection */
/*
Point to the LEX in which it was created, used in view subquery detection.
TODO: make also st_select_lex::parent_stmt_lex (see THD::stmt_lex)
and use st_select_lex::parent_lex & st_select_lex::parent_stmt_lex
instead of global (from THD) references where it is possible.
*/
LEX *parent_lex;
enum olap_type olap;
/* FROM clause - points to the beginning of the TABLE_LIST::next_local list. */
@ -2452,7 +2458,7 @@ struct LEX: public Query_tables_list
/** SELECT of CREATE VIEW statement */
LEX_STRING create_view_select;
uint number_of_selects; // valid only for view
uint current_select_number; // valid for statment LEX (not view)
/** Start of 'ON table', in trigger statements. */
const char* raw_trg_on_table_name_begin;

View File

@ -6903,7 +6903,12 @@ void THD::reset_for_next_command(bool do_clear_error)
clear_error(1);
thd->free_list= 0;
thd->select_number= 1;
/*
We also assign thd->stmt_lex in lex_start(), but during bootstrap this
code is executed first.
*/
thd->stmt_lex= &main_lex; thd->stmt_lex->current_select_number= 1;
DBUG_PRINT("info", ("Lex %p stmt_lex: %p", thd->lex, thd->stmt_lex));
/*
Those two lines below are theoretically unneeded as
THD::cleanup_after_query() should take care of this already.
@ -7021,7 +7026,7 @@ mysql_new_select(LEX *lex, bool move_down)
if (!(select_lex= new (thd->mem_root) SELECT_LEX()))
DBUG_RETURN(1);
select_lex->select_number= ++thd->select_number;
select_lex->select_number= ++thd->stmt_lex->current_select_number;
select_lex->parent_lex= lex; /* Used in init_query. */
select_lex->init_query();
select_lex->init_select();

View File

@ -164,20 +164,6 @@ public:
uint param_count;
uint last_errno;
uint flags;
/*
The value of thd->select_number at the end of the PREPARE phase.
The issue is: each statement execution opens VIEWs, which may cause
select_lex objects to be created, and select_number values to be assigned.
On the other hand, PREPARE assigns select_number values for triggers and
subqueries.
In order for select_number values from EXECUTE not to conflict with
select_number values from PREPARE, we keep the number and set it at each
execution.
*/
uint select_number_after_prepare;
char last_error[MYSQL_ERRMSG_SIZE];
#ifndef EMBEDDED_LIBRARY
bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end,
@ -3649,6 +3635,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
if (! (lex= new (mem_root) st_lex_local))
DBUG_RETURN(TRUE);
stmt_lex= lex;
if (set_db(thd->db, thd->db_length))
DBUG_RETURN(TRUE);
@ -3754,8 +3741,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
select_number_after_prepare= thd->select_number;
/* Preserve CHANGE MASTER attributes */
lex_end_stage1(lex);
@ -3891,7 +3876,6 @@ Prepared_statement::execute_loop(String *expanded_query,
*/
DBUG_ASSERT(thd->free_list == NULL);
thd->select_number= select_number_after_prepare;
/* Check if we got an error when sending long data */
if (state == Query_arena::STMT_ERROR)
{

View File

@ -2462,6 +2462,17 @@ void JOIN::save_explain_data(Explain_query *output, bool can_overwrite,
bool need_tmp_table, bool need_order,
bool distinct)
{
/*
If there is SELECT in this statemet with the same number it must be the
same SELECT
*/
DBUG_ASSERT(select_lex->select_number == UINT_MAX ||
select_lex->select_number == INT_MAX ||
!output ||
!output->get_select(select_lex->select_number) ||
output->get_select(select_lex->select_number)->select_lex ==
select_lex);
if (select_lex->select_number != UINT_MAX &&
select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ &&
have_query_plan != JOIN::QEP_NOT_PRESENT_YET &&
@ -24601,6 +24612,11 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table,
{
explain= new (output->mem_root) Explain_select(output->mem_root,
thd->lex->analyze_stmt);
if (!explain)
DBUG_RETURN(1); // EoM
#ifndef DBUG_OFF
explain->select_lex= select_lex;
#endif
join->select_lex->set_explain_type(true);
explain->select_id= join->select_lex->select_number;

View File

@ -1373,12 +1373,13 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
List_iterator_fast<LEX_STRING> it_client_cs_name(triggers->client_cs_names);
List_iterator_fast<LEX_STRING> it_connection_cl_name(triggers->connection_cl_names);
List_iterator_fast<LEX_STRING> it_db_cl_name(triggers->db_cl_names);
LEX *old_lex= thd->lex, lex;
LEX *old_lex= thd->lex, *old_stmt_lex= thd->stmt_lex;
LEX lex;
sp_rcontext *save_spcont= thd->spcont;
ulonglong save_sql_mode= thd->variables.sql_mode;
LEX_STRING *on_table_name;
thd->lex= &lex;
thd->lex= thd->stmt_lex= &lex;
save_db.str= thd->db;
save_db.length= thd->db_length;
@ -1577,6 +1578,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
}
thd->reset_db(save_db.str, save_db.length);
thd->lex= old_lex;
thd->stmt_lex= old_stmt_lex;
thd->spcont= save_spcont;
thd->variables.sql_mode= save_sql_mode;
@ -1589,6 +1591,7 @@ err_with_lex_cleanup:
// QQ: anything else ?
lex_end(&lex);
thd->lex= old_lex;
thd->stmt_lex= old_stmt_lex;
thd->spcont= save_spcont;
thd->variables.sql_mode= save_sql_mode;
thd->reset_db(save_db.str, save_db.length);

View File

@ -1187,8 +1187,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
*/
mysql_derived_reinit(thd, NULL, table);
thd->select_number+= table->view->number_of_selects;
DEBUG_SYNC(thd, "after_cached_view_opened");
DBUG_RETURN(0);
}
@ -1343,7 +1341,7 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
lex_start(thd);
view_select= &lex->select_lex;
view_select->select_number= ++thd->select_number;
view_select->select_number= ++thd->stmt_lex->current_select_number;
ulonglong saved_mode= thd->variables.sql_mode;
/* switch off modes which can prevent normal parsing of VIEW
@ -1377,9 +1375,6 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
parse_status= parse_sql(thd, & parser_state, table->view_creation_ctx);
lex->number_of_selects=
(thd->select_number - view_select->select_number) + 1;
/* Restore environment. */
if ((old_lex->sql_command == SQLCOM_SHOW_FIELDS) ||