MDEV-35318 Assertion `tl->jtbm_subselect' failed in JOIN::calc_allowed_top_level_tables

Alternative, more general fix, Variant 2.

The problem was as follows: Suppose we are running a PS/SP statement and
we get an error while doing optimization that is done once per statement
life. This may leave the statement data structures in an undefined state,
where it is not safe to execute it again.

The fix: introduce LEX::needs_reprepare and set it in such cases.
Make PS and SP runtime check it and re-prepare the statement before
executing it again.

We do not use Reprepare_observer, because it turns out it is tightly tied
to watching versions of statement's objects. For example, it must not be
used when running the statement for the first time, exactly when the
once-per-statement-lifetime optimizations are done.
This commit is contained in:
Sergei Petrunia 2025-01-20 17:44:51 +02:00
parent 491e2b17a9
commit 0e21ff8ca4
7 changed files with 116 additions and 1 deletions

View file

@ -2070,3 +2070,50 @@ SHOW CREATE DATABASE `#testone#■■■■■■■■■■■■■■■■
ERROR 42000: Incorrect database name '#testone#■■■■■■■■■■■■■■■■■■■■■■■■■■■■■...'
SET NAMES DEFAULT;
# End of 10.5 Test
#
# MDEV-35318 Assertion `tl->jtbm_subselect' failed in JOIN::calc_allowed_top_level_tables on 2nd execution of PS
#
CREATE TABLE t (a INT);
INSERT INTO t VALUES (1),(2);
PREPARE stmt FROM 'CREATE TABLE tmp AS SELECT * FROM (select t1.* from t t1 join t t2 on(t1.a = t2.a)) sq WHERE "x"=0';
EXECUTE stmt;
ERROR 22007: Truncated incorrect DECIMAL value: 'x'
EXECUTE stmt;
ERROR 22007: Truncated incorrect DECIMAL value: 'x'
DEALLOCATE PREPARE stmt;
# Now, run the same in SP:
create procedure sp1()
begin
declare i int unsigned default 1;
declare continue handler for SQLEXCEPTION
begin
select 'In Continue handler' as printf;
end;
while i <= 3 do
begin
select concat('Iteration ', i) as printf;
CREATE temporary TABLE tmp AS
SELECT * FROM (select t1.* from t t1 join t t2 on(t1.a = t2.a)) sq WHERE "x"=0 ;
drop temporary table if exists tmp;
set i=i+1;
end;
end while;
end|
call sp1();
printf
Iteration 1
printf
In Continue handler
printf
Iteration 2
printf
In Continue handler
printf
Iteration 3
printf
In Continue handler
Warnings:
Note 1051 Unknown table 'test.tmp'
DROP PROCEDURE sp1;
DROP TABLE t;
# End of 11.7 Test

View file

@ -1945,3 +1945,46 @@ SHOW CREATE DATABASE `#testone#■■■■■■■■■■■■■■■■
SET NAMES DEFAULT;
--echo # End of 10.5 Test
--echo #
--echo # MDEV-35318 Assertion `tl->jtbm_subselect' failed in JOIN::calc_allowed_top_level_tables on 2nd execution of PS
--echo #
CREATE TABLE t (a INT);
INSERT INTO t VALUES (1),(2); # Optional, fails either way
PREPARE stmt FROM 'CREATE TABLE tmp AS SELECT * FROM (select t1.* from t t1 join t t2 on(t1.a = t2.a)) sq WHERE "x"=0';
--error ER_TRUNCATED_WRONG_VALUE
EXECUTE stmt;
--error ER_TRUNCATED_WRONG_VALUE
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
--echo # Now, run the same in SP:
delimiter |;
create procedure sp1()
begin
declare i int unsigned default 1;
declare continue handler for SQLEXCEPTION
begin
select 'In Continue handler' as printf;
end;
while i <= 3 do
begin
select concat('Iteration ', i) as printf;
CREATE temporary TABLE tmp AS
SELECT * FROM (select t1.* from t t1 join t t2 on(t1.a = t2.a)) sq WHERE "x"=0 ;
drop temporary table if exists tmp;
set i=i+1;
end;
end while;
end|
delimiter ;|
call sp1();
DROP PROCEDURE sp1;
DROP TABLE t;
--echo # End of 11.7 Test

View file

@ -459,7 +459,7 @@ int sp_lex_keeper::validate_lex_and_exec_core(THD *thd, uint *nextp,
while (true)
{
if (instr->is_invalid())
if (instr->is_invalid() || m_lex->needs_reprepare)
{
thd->clear_error();
free_lex(thd);

View file

@ -1336,6 +1336,7 @@ void LEX::start(THD *thd_arg)
exchange= 0;
table_count_update= 0;
needs_reprepare= false;
memset(&trg_chistics, 0, sizeof(trg_chistics));
DBUG_VOID_RETURN;

View file

@ -3136,6 +3136,13 @@ public:
/* Query Plan Footprint of a currently running select */
Explain_query *explain;
/*
If true, query optimizer has encountered an unrecoverable error when doing
once-per-statement optimization and it is not safe to re-execute this
statement.
*/
bool needs_reprepare{false};
/*
LEX which represents current statement (conventional, SP or PS)

View file

@ -4401,6 +4401,16 @@ Prepared_statement::execute_loop(String *expanded_query,
*/
DBUG_ASSERT(thd->free_list == NULL);
if (lex->needs_reprepare)
{
/*
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;
}
/* Check if we got an error when sending long data */
if (unlikely(state == Query_arena::STMT_ERROR))
{
@ -4448,6 +4458,7 @@ reexecute:
DBUG_ASSERT(thd->get_stmt_da()->sql_errno() == ER_NEED_REPREPARE);
thd->clear_error();
start_with_reprepare:
error= reprepare();
if (likely(!error)) /* Success */

View file

@ -2316,6 +2316,12 @@ JOIN::optimize_inner()
if (thd->is_error() ||
(!select_lex->leaf_tables_saved && select_lex->save_leaf_tables(thd)))
{
/*
If there was an error above, the data structures may have been left in
some undefined state. If this is a PS/SP statement, it might not be
safe to run it again. Note that it needs to be re-prepared.
*/
thd->lex->needs_reprepare= true;
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(1);