diff --git a/mysql-test/main/kill_debug.result b/mysql-test/main/kill_debug.result index 796156d6e45..e0695051194 100644 --- a/mysql-test/main/kill_debug.result +++ b/mysql-test/main/kill_debug.result @@ -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 diff --git a/mysql-test/main/kill_debug.test b/mysql-test/main/kill_debug.test index c305d069255..5644fa2c30a 100644 --- a/mysql-test/main/kill_debug.test +++ b/mysql-test/main/kill_debug.test @@ -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 diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 7733068eed3..5f9a14a4d94 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -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); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5069134e192..18feefbc41b 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -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}; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index d5ca525d181..95b8184c89e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -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; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 066c7b00ee5..51c0fd593dc 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -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) {