-
Guilhem Bichot authored
Introduced by WL#3634 (recursive CTE). Before WL3634, in unit::execute(), if one of the query blocks' join::exec() returned an error, unit::execute() would return immediately, but if the fake_select_lex's join::exec() returned an error then we would first restore current_select() to the value it had when the function started, and then return. In WL3634, I moved the fake_select_lex's join::exec() into the same loop as the query blocks' join::exec() because it may be executed several times. Doing this, I automatically removed the "exceptional" restoration of current_select(), judging that it was anyway unnecessary: "as a failure of join::exec() means a real problem, the statement is going to end in error very soon, so why restore?". This introduced the bug. Scenario is: DELETE IGNORE ... WHERE a!=(subq); where "subq" contains a UNION which returns more than one row. DELETE evaluates the scalar subq. The subq's fake_select_lex's JOIN sends its result to a Query_result_scalar_subquery which job is to fill the Item_singlerow_subselect with the JOIN's result. However if this item is already filled (i.e. the JOIN is finding a 2nd result row) the result object signals my_error(ER_SUBQUERY_1_ROW) then returns "error": bool Query_result_scalar_subquery::send_data(List<Item> &items) { Item_singlerow_subselect *it= (Item_singlerow_subselect *)item; if (it->assigned()) <<< already assigned a value { my_error(ER_SUBQUERY_NO_1_ROW, MYF(0)); DBUG_RETURN(true); <<<<<<< error There is IGNORE so my_error() only pushes a warning. Due to the returned "true" value, subquery's unit::execute() terminates, but as it's the fake_select_lex's JOIN's which failed, current_select() is restored() before terminating. The termination also means the 3rd row won't be read (that's efficient). The the subselect_union_engine gets the "true" return code, then the Item gets it too: longlong Item_singlerow_subselect::val_int() { DBUG_ASSERT(fixed == 1); if (!no_rows && !exec() && !value->null_value) { null_value= FALSE; return value->val_int(); } else { <<<<<< here as exec()==true reset(); return 0; } } reset() fills the Item with NULL and then "return 0" makes the error disappear. From the point of view of the outer statement, everything is said to be ok. So the DELETE uses the NULL value to evaluate its WHERE, then goes and delete the row in: which has this assertion: `thd->lex->current_select() == unit->first_select()'. Which passes. After the changes of WL#3634, as current_select() isn't restored, the assertion fails. Fix: restore current_select() On-the-side fix: in sql_union.cc: if (set_limit(thd, fake_select_lex)) - DBUG_RETURN(status); + DBUG_RETURN(true); because "status" may not be true at this point.
Guilhem Bichot authoredIntroduced by WL#3634 (recursive CTE). Before WL3634, in unit::execute(), if one of the query blocks' join::exec() returned an error, unit::execute() would return immediately, but if the fake_select_lex's join::exec() returned an error then we would first restore current_select() to the value it had when the function started, and then return. In WL3634, I moved the fake_select_lex's join::exec() into the same loop as the query blocks' join::exec() because it may be executed several times. Doing this, I automatically removed the "exceptional" restoration of current_select(), judging that it was anyway unnecessary: "as a failure of join::exec() means a real problem, the statement is going to end in error very soon, so why restore?". This introduced the bug. Scenario is: DELETE IGNORE ... WHERE a!=(subq); where "subq" contains a UNION which returns more than one row. DELETE evaluates the scalar subq. The subq's fake_select_lex's JOIN sends its result to a Query_result_scalar_subquery which job is to fill the Item_singlerow_subselect with the JOIN's result. However if this item is already filled (i.e. the JOIN is finding a 2nd result row) the result object signals my_error(ER_SUBQUERY_1_ROW) then returns "error": bool Query_result_scalar_subquery::send_data(List<Item> &items) { Item_singlerow_subselect *it= (Item_singlerow_subselect *)item; if (it->assigned()) <<< already assigned a value { my_error(ER_SUBQUERY_NO_1_ROW, MYF(0)); DBUG_RETURN(true); <<<<<<< error There is IGNORE so my_error() only pushes a warning. Due to the returned "true" value, subquery's unit::execute() terminates, but as it's the fake_select_lex's JOIN's which failed, current_select() is restored() before terminating. The termination also means the 3rd row won't be read (that's efficient). The the subselect_union_engine gets the "true" return code, then the Item gets it too: longlong Item_singlerow_subselect::val_int() { DBUG_ASSERT(fixed == 1); if (!no_rows && !exec() && !value->null_value) { null_value= FALSE; return value->val_int(); } else { <<<<<< here as exec()==true reset(); return 0; } } reset() fills the Item with NULL and then "return 0" makes the error disappear. From the point of view of the outer statement, everything is said to be ok. So the DELETE uses the NULL value to evaluate its WHERE, then goes and delete the row in: which has this assertion: `thd->lex->current_select() == unit->first_select()'. Which passes. After the changes of WL#3634, as current_select() isn't restored, the assertion fails. Fix: restore current_select() On-the-side fix: in sql_union.cc: if (set_limit(thd, fake_select_lex)) - DBUG_RETURN(status); + DBUG_RETURN(true); because "status" may not be true at this point.
Loading