mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute
If the first execution of a prepared statement was killed (for example by max_statement_time) after the top-level JOIN had done the once-per-statement semi-join rewrite of the nested subqueries, but before the nested selects saved their rewritten WHERE into prep_where, the statement was left half-transformed. A later execution then used a freed Item from prep_where or an unfixed Item_field and crashed. Set LEX::needs_reprepare when JOIN::optimize() fails during the select's first optimization, so the statement is re-prepared before it runs again. Also make the handling of needs_reprepare safe when the re-prepare fails: keep the flag so it is tried again, do not skip a pending long data error, and invalidate the SP instruction so that a failed re-parse is retried.
This commit is contained in:
@@ -265,3 +265,102 @@ ERROR 70100: Query execution was interrupted
|
||||
DROP TABLE t1;
|
||||
disconnect con1;
|
||||
SET debug_sync='RESET';
|
||||
#
|
||||
# MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute
|
||||
#
|
||||
# The first EXECUTE is killed after the one-time semi-join rewrite, but
|
||||
# before the nested selects save their WHERE. The statement must be
|
||||
# re-prepared, not run half-rewritten. The bug was reported with
|
||||
# max_statement_time, KILL QUERY at a sync point makes it deterministic.
|
||||
CREATE TABLE t (a INT);
|
||||
connect con1,localhost,root,,;
|
||||
PREPARE s FROM <62 nested IN subqueries>
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
KILL QUERY id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
SET debug_sync='RESET';
|
||||
EXECUTE s;
|
||||
a
|
||||
EXECUTE s;
|
||||
a
|
||||
DEALLOCATE PREPARE s;
|
||||
# The same with rows, the results must be correct
|
||||
INSERT INTO t VALUES (1),(2),(3);
|
||||
PREPARE s FROM <62 nested IN subqueries>
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
KILL QUERY id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
SET debug_sync='RESET';
|
||||
EXECUTE s;
|
||||
a
|
||||
1
|
||||
EXECUTE s;
|
||||
a
|
||||
1
|
||||
DEALLOCATE PREPARE s;
|
||||
# The same in a stored procedure
|
||||
CREATE PROCEDURE p() <62 nested IN subqueries>
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
CALL p();
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
KILL QUERY id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
SET debug_sync='RESET';
|
||||
CALL p();
|
||||
a
|
||||
1
|
||||
CALL p();
|
||||
a
|
||||
1
|
||||
DROP PROCEDURE p;
|
||||
connection default;
|
||||
disconnect con1;
|
||||
# If the re-prepare fails, it must be tried again on the next EXECUTE
|
||||
CREATE USER u@localhost;
|
||||
GRANT SUPER ON *.* TO u@localhost;
|
||||
GRANT SELECT ON test.t TO u@localhost;
|
||||
connect con2,localhost,u,,test;
|
||||
PREPARE s FROM <62 nested IN subqueries>
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
KILL QUERY id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
REVOKE SELECT ON test.t FROM u@localhost;
|
||||
connection con2;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
SET debug_sync='RESET';
|
||||
EXECUTE s;
|
||||
ERROR 42000: SELECT command denied to user 'u'@'localhost' for table `test`.`t`
|
||||
SHOW STATUS LIKE 'Com_stmt_reprepare';
|
||||
Variable_name Value
|
||||
Com_stmt_reprepare 1
|
||||
connection default;
|
||||
GRANT SELECT ON test.t TO u@localhost;
|
||||
connection con2;
|
||||
EXECUTE s;
|
||||
a
|
||||
1
|
||||
SHOW STATUS LIKE 'Com_stmt_reprepare';
|
||||
Variable_name Value
|
||||
Com_stmt_reprepare 2
|
||||
connection default;
|
||||
disconnect con2;
|
||||
DROP USER u@localhost;
|
||||
DROP TABLE t;
|
||||
SET debug_sync='RESET';
|
||||
# End of 11.4 tests
|
||||
|
||||
@@ -361,3 +361,120 @@ reap;
|
||||
DROP TABLE t1;
|
||||
disconnect con1;
|
||||
SET debug_sync='RESET';
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute
|
||||
--echo #
|
||||
--echo # The first EXECUTE is killed after the one-time semi-join rewrite, but
|
||||
--echo # before the nested selects save their WHERE. The statement must be
|
||||
--echo # re-prepared, not run half-rewritten. The bug was reported with
|
||||
--echo # max_statement_time, KILL QUERY at a sync point makes it deterministic.
|
||||
CREATE TABLE t (a INT);
|
||||
let $q= 1;
|
||||
let $d= 62;
|
||||
while ($d)
|
||||
{
|
||||
let $q= SELECT a FROM t WHERE a IN ($q);
|
||||
dec $d;
|
||||
}
|
||||
connect (con1,localhost,root,,);
|
||||
let $id= `SELECT CONNECTION_ID()`;
|
||||
--echo PREPARE s FROM <62 nested IN subqueries>
|
||||
--disable_query_log
|
||||
eval PREPARE s FROM '$q';
|
||||
--enable_query_log
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
send EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
replace_result $id id;
|
||||
eval KILL QUERY $id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
SET debug_sync='RESET';
|
||||
EXECUTE s;
|
||||
EXECUTE s;
|
||||
DEALLOCATE PREPARE s;
|
||||
|
||||
--echo # The same with rows, the results must be correct
|
||||
INSERT INTO t VALUES (1),(2),(3);
|
||||
--echo PREPARE s FROM <62 nested IN subqueries>
|
||||
--disable_query_log
|
||||
eval PREPARE s FROM '$q';
|
||||
--enable_query_log
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
send EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
replace_result $id id;
|
||||
eval KILL QUERY $id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
SET debug_sync='RESET';
|
||||
EXECUTE s;
|
||||
EXECUTE s;
|
||||
DEALLOCATE PREPARE s;
|
||||
|
||||
--echo # The same in a stored procedure
|
||||
--echo CREATE PROCEDURE p() <62 nested IN subqueries>
|
||||
--disable_query_log
|
||||
eval CREATE PROCEDURE p() $q;
|
||||
--enable_query_log
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
send CALL p();
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
replace_result $id id;
|
||||
eval KILL QUERY $id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
connection con1;
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
SET debug_sync='RESET';
|
||||
CALL p();
|
||||
CALL p();
|
||||
DROP PROCEDURE p;
|
||||
connection default;
|
||||
disconnect con1;
|
||||
|
||||
--echo # If the re-prepare fails, it must be tried again on the next EXECUTE
|
||||
CREATE USER u@localhost;
|
||||
GRANT SUPER ON *.* TO u@localhost;
|
||||
GRANT SELECT ON test.t TO u@localhost;
|
||||
connect (con2,localhost,u,,test);
|
||||
let $id= `SELECT CONNECTION_ID()`;
|
||||
--echo PREPARE s FROM <62 nested IN subqueries>
|
||||
--disable_query_log
|
||||
eval PREPARE s FROM '$q';
|
||||
--enable_query_log
|
||||
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
|
||||
send EXECUTE s;
|
||||
connection default;
|
||||
SET debug_sync='now WAIT_FOR parked';
|
||||
replace_result $id id;
|
||||
eval KILL QUERY $id;
|
||||
SET debug_sync='now SIGNAL go';
|
||||
REVOKE SELECT ON test.t FROM u@localhost;
|
||||
connection con2;
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
SET debug_sync='RESET';
|
||||
--error ER_TABLEACCESS_DENIED_ERROR
|
||||
EXECUTE s;
|
||||
SHOW STATUS LIKE 'Com_stmt_reprepare';
|
||||
connection default;
|
||||
GRANT SELECT ON test.t TO u@localhost;
|
||||
connection con2;
|
||||
EXECUTE s;
|
||||
SHOW STATUS LIKE 'Com_stmt_reprepare';
|
||||
connection default;
|
||||
disconnect con2;
|
||||
DROP USER u@localhost;
|
||||
DROP TABLE t;
|
||||
SET debug_sync='RESET';
|
||||
|
||||
--echo # End of 11.4 tests
|
||||
|
||||
@@ -478,7 +478,10 @@ int sp_lex_keeper::validate_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (instr->is_invalid() || m_lex->needs_reprepare)
|
||||
if (!instr->is_invalid() && m_lex->needs_reprepare)
|
||||
instr->invalidate();
|
||||
|
||||
if (instr->is_invalid())
|
||||
{
|
||||
thd->clear_error();
|
||||
free_lex(thd);
|
||||
|
||||
@@ -3313,9 +3313,9 @@ public:
|
||||
Explain_query *explain;
|
||||
|
||||
/*
|
||||
If true, query optimizer has encountered an unrecoverable error when doing
|
||||
If true, query optimizer has encountered an error when doing
|
||||
once-per-statement optimization and it is not safe to re-execute this
|
||||
statement.
|
||||
statement without re-preparing it.
|
||||
*/
|
||||
bool needs_reprepare{false};
|
||||
|
||||
|
||||
@@ -4468,13 +4468,12 @@ Prepared_statement::execute_loop(String *expanded_query,
|
||||
*/
|
||||
DBUG_ASSERT(thd->free_list == NULL);
|
||||
|
||||
if (lex->needs_reprepare)
|
||||
if (lex->needs_reprepare && state != Query_arena::STMT_ERROR)
|
||||
{
|
||||
/*
|
||||
Something has happened on previous execution that requires us to
|
||||
re-prepare before we try to execute.
|
||||
*/
|
||||
lex->needs_reprepare= false;
|
||||
goto start_with_reprepare;
|
||||
}
|
||||
|
||||
@@ -4648,7 +4647,6 @@ Prepared_statement::execute_bulk_loop(String *expanded_query,
|
||||
Something has happened on previous execution that requires us to
|
||||
re-prepare before we try to execute.
|
||||
*/
|
||||
lex->needs_reprepare= false;
|
||||
error= reprepare();
|
||||
if (error)
|
||||
goto err;
|
||||
|
||||
@@ -2029,7 +2029,13 @@ int JOIN::optimize()
|
||||
if (optimization_state != JOIN::NOT_OPTIMIZED)
|
||||
return FALSE;
|
||||
optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS;
|
||||
bool first_optimization= select_lex->first_cond_optimization;
|
||||
res= optimize_inner();
|
||||
if (unlikely(res) && first_optimization)
|
||||
{
|
||||
/* A failed first optimization may leave the statement half-done */
|
||||
thd->lex->needs_reprepare= true;
|
||||
}
|
||||
}
|
||||
if (!with_two_phase_optimization)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user