Commit b000d695 authored by Sergei Petrunia's avatar Sergei Petrunia

MDEV-23221: A subquery causes crash

* Fix the crash: IN-to-EXISTS rewrite causes an error (and so
  JOIN::optimize() fails with an error, too), don't call
  update_used_tables(). Terminate the query execution instead.

* Fix the cause of the error in the IN-to-EXISTS rewrite: don't do
  the rewrite if doing it will cause an error of this kind:
  This version of MariaDB doesn't yet support 'SUBQUERY in ROW in left
  expression of IN/ALL/ANY'

* Fix another issue exposed by this testcase:
  JOIN::setup_subquery_caches() may be invoked before any select has
  saved its query plan, and will crash because none of the SELECTs
  has called create_explain_query_if_not_exists() to create the Explain
  Data Structure for this SELECT.

TODO: When merging this to 10.2, remove the poorly-placed call to
create_explain_query_if_not_exists made by fix for M_D_E_V-16153
parent 4b97f14a
...@@ -972,4 +972,74 @@ id ...@@ -972,4 +972,74 @@ id
DROP PROCEDURE p1; DROP PROCEDURE p1;
DROP TABLE t1; DROP TABLE t1;
# End of 10.0 tests # End of 10.0 tests
#
# MDEV-23221: A subquery causes crash
#
create table t1 (
location_code varchar(10),
country_id varchar(10)
);
insert into t1 values ('HKG', 'HK');
insert into t1 values ('NYC', 'US');
insert into t1 values ('LAX', 'US');
create table t2 (
container_id varchar(10),
cntr_activity_type varchar(10),
cntr_dest varchar(10)
);
insert into t2 values ('AAAA1111', 'VSL', 'NYC');
insert into t2 values ('AAAA1111', 'CUV', 'NYC');
insert into t2 values ('BBBB2222', 'VSL', 'LAX');
insert into t2 values ('BBBB2222', 'XYZ', 'LAX');
# Must not crash or return an error:
select
(select country_id from t1 where location_code = cl1.cntr_dest) as dest_cntry,
(select
max(container_id)
from t2 as cl2
where
cl2.container_id = cl1.container_id and
cl2.cntr_activity_type = 'CUV' and
exists (select location_code
from t1
where
location_code = cl2.cntr_dest and
country_id = dest_cntry)
) as CUV
from
t2 cl1;
dest_cntry CUV
US AAAA1111
US AAAA1111
US NULL
US NULL
prepare s from "select
(select country_id from t1 where location_code = cl1.cntr_dest) as dest_cntry,
(select
max(container_id)
from t2 as cl2
where
cl2.container_id = cl1.container_id and
cl2.cntr_activity_type = 'CUV' and
exists (select location_code
from t1
where
location_code = cl2.cntr_dest and
country_id = dest_cntry)
) as CUV
from
t2 cl1";
execute s;
dest_cntry CUV
US AAAA1111
US AAAA1111
US NULL
US NULL
execute s;
dest_cntry CUV
US AAAA1111
US AAAA1111
US NULL
US NULL
drop table t1,t2;
set optimizer_switch=default; set optimizer_switch=default;
...@@ -828,5 +828,53 @@ DROP TABLE t1; ...@@ -828,5 +828,53 @@ DROP TABLE t1;
--echo # End of 10.0 tests --echo # End of 10.0 tests
--echo #
--echo # MDEV-23221: A subquery causes crash
--echo #
create table t1 (
location_code varchar(10),
country_id varchar(10)
);
insert into t1 values ('HKG', 'HK');
insert into t1 values ('NYC', 'US');
insert into t1 values ('LAX', 'US');
create table t2 (
container_id varchar(10),
cntr_activity_type varchar(10),
cntr_dest varchar(10)
);
insert into t2 values ('AAAA1111', 'VSL', 'NYC');
insert into t2 values ('AAAA1111', 'CUV', 'NYC');
insert into t2 values ('BBBB2222', 'VSL', 'LAX');
insert into t2 values ('BBBB2222', 'XYZ', 'LAX');
let $query=
select
(select country_id from t1 where location_code = cl1.cntr_dest) as dest_cntry,
(select
max(container_id)
from t2 as cl2
where
cl2.container_id = cl1.container_id and
cl2.cntr_activity_type = 'CUV' and
exists (select location_code
from t1
where
location_code = cl2.cntr_dest and
country_id = dest_cntry)
) as CUV
from
t2 cl1;
--echo # Must not crash or return an error:
eval $query;
eval prepare s from "$query";
execute s;
execute s;
drop table t1,t2;
#restore defaults #restore defaults
set optimizer_switch=default; set optimizer_switch=default;
...@@ -2727,6 +2727,8 @@ bool Item_exists_subselect::select_prepare_to_be_in() ...@@ -2727,6 +2727,8 @@ bool Item_exists_subselect::select_prepare_to_be_in()
Check if 'func' is an equality in form "inner_table.column = outer_expr" Check if 'func' is an equality in form "inner_table.column = outer_expr"
@param func Expression to check @param func Expression to check
@param allow_subselect If true, the outer_expr part can have a subquery
If false, it cannot.
@param local_field OUT Return "inner_table.column" here @param local_field OUT Return "inner_table.column" here
@param outer_expr OUT Return outer_expr here @param outer_expr OUT Return outer_expr here
...@@ -2734,6 +2736,7 @@ bool Item_exists_subselect::select_prepare_to_be_in() ...@@ -2734,6 +2736,7 @@ bool Item_exists_subselect::select_prepare_to_be_in()
*/ */
static bool check_equality_for_exist2in(Item_func *func, static bool check_equality_for_exist2in(Item_func *func,
bool allow_subselect,
Item_ident **local_field, Item_ident **local_field,
Item **outer_exp) Item **outer_exp)
{ {
...@@ -2744,7 +2747,8 @@ static bool check_equality_for_exist2in(Item_func *func, ...@@ -2744,7 +2747,8 @@ static bool check_equality_for_exist2in(Item_func *func,
args= func->arguments(); args= func->arguments();
if (args[0]->real_type() == Item::FIELD_ITEM && if (args[0]->real_type() == Item::FIELD_ITEM &&
args[0]->all_used_tables() != OUTER_REF_TABLE_BIT && args[0]->all_used_tables() != OUTER_REF_TABLE_BIT &&
args[1]->all_used_tables() == OUTER_REF_TABLE_BIT) args[1]->all_used_tables() == OUTER_REF_TABLE_BIT &&
(allow_subselect || !args[1]->has_subquery()))
{ {
/* It is Item_field or Item_direct_view_ref) */ /* It is Item_field or Item_direct_view_ref) */
DBUG_ASSERT(args[0]->type() == Item::FIELD_ITEM || DBUG_ASSERT(args[0]->type() == Item::FIELD_ITEM ||
...@@ -2755,7 +2759,8 @@ static bool check_equality_for_exist2in(Item_func *func, ...@@ -2755,7 +2759,8 @@ static bool check_equality_for_exist2in(Item_func *func,
} }
else if (args[1]->real_type() == Item::FIELD_ITEM && else if (args[1]->real_type() == Item::FIELD_ITEM &&
args[1]->all_used_tables() != OUTER_REF_TABLE_BIT && args[1]->all_used_tables() != OUTER_REF_TABLE_BIT &&
args[0]->all_used_tables() == OUTER_REF_TABLE_BIT) args[0]->all_used_tables() == OUTER_REF_TABLE_BIT &&
(allow_subselect || !args[0]->has_subquery()))
{ {
/* It is Item_field or Item_direct_view_ref) */ /* It is Item_field or Item_direct_view_ref) */
DBUG_ASSERT(args[1]->type() == Item::FIELD_ITEM || DBUG_ASSERT(args[1]->type() == Item::FIELD_ITEM ||
...@@ -2784,6 +2789,13 @@ typedef struct st_eq_field_outer ...@@ -2784,6 +2789,13 @@ typedef struct st_eq_field_outer
outer1=inner_tbl1.col1 AND ... AND outer2=inner_tbl1.col2 AND remainder_cond outer1=inner_tbl1.col1 AND ... AND outer2=inner_tbl1.col2 AND remainder_cond
if there is just one outer_expr=inner_expr pair, then outer_expr can have a
subselect in it. If there are many such pairs, then none of outer_expr can
have a subselect in it. If we allow this, the query will fail with an error:
This version of MariaDB doesn't yet support 'SUBQUERY in ROW in left
expression of IN/ALL/ANY'
@param conds Condition to be checked @param conds Condition to be checked
@parm result Array to collect EQ_FIELD_OUTER elements describing @parm result Array to collect EQ_FIELD_OUTER elements describing
inner-vs-outer equalities the function has found. inner-vs-outer equalities the function has found.
...@@ -2801,14 +2813,17 @@ static bool find_inner_outer_equalities(Item **conds, ...@@ -2801,14 +2813,17 @@ static bool find_inner_outer_equalities(Item **conds,
{ {
List_iterator<Item> li(*((Item_cond*)*conds)->argument_list()); List_iterator<Item> li(*((Item_cond*)*conds)->argument_list());
Item *item; Item *item;
bool allow_subselect= true;
while ((item= li++)) while ((item= li++))
{ {
if (item->type() == Item::FUNC_ITEM && if (item->type() == Item::FUNC_ITEM &&
check_equality_for_exist2in((Item_func *)item, check_equality_for_exist2in((Item_func *)item,
allow_subselect,
&element.local_field, &element.local_field,
&element.outer_exp)) &element.outer_exp))
{ {
found= TRUE; found= TRUE;
allow_subselect= false;
element.eq_ref= li.ref(); element.eq_ref= li.ref();
if (result.append(element)) if (result.append(element))
goto alloc_err; goto alloc_err;
...@@ -2817,6 +2832,7 @@ static bool find_inner_outer_equalities(Item **conds, ...@@ -2817,6 +2832,7 @@ static bool find_inner_outer_equalities(Item **conds,
} }
else if ((*conds)->type() == Item::FUNC_ITEM && else if ((*conds)->type() == Item::FUNC_ITEM &&
check_equality_for_exist2in((Item_func *)*conds, check_equality_for_exist2in((Item_func *)*conds,
true,
&element.local_field, &element.local_field,
&element.outer_exp)) &element.outer_exp))
{ {
......
...@@ -3802,7 +3802,8 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) ...@@ -3802,7 +3802,8 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only)
sl->options|= SELECT_DESCRIBE; sl->options|= SELECT_DESCRIBE;
inner_join->select_options|= SELECT_DESCRIBE; inner_join->select_options|= SELECT_DESCRIBE;
} }
res= inner_join->optimize(); if ((res= inner_join->optimize()))
return TRUE;
if (!inner_join->cleaned) if (!inner_join->cleaned)
sl->update_used_tables(); sl->update_used_tables();
sl->update_correlated_cache(); sl->update_correlated_cache();
......
...@@ -1055,11 +1055,11 @@ int JOIN::optimize() ...@@ -1055,11 +1055,11 @@ int JOIN::optimize()
if (optimization_state != JOIN::NOT_OPTIMIZED) if (optimization_state != JOIN::NOT_OPTIMIZED)
return FALSE; return FALSE;
optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS; optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS;
create_explain_query_if_not_exists(thd->lex, thd->mem_root);
int res= optimize_inner(); int res= optimize_inner();
if (!res && have_query_plan != QEP_DELETED) if (!res && have_query_plan != QEP_DELETED)
{ {
create_explain_query_if_not_exists(thd->lex, thd->mem_root);
have_query_plan= QEP_AVAILABLE; have_query_plan= QEP_AVAILABLE;
save_explain_data(thd->lex->explain, false /* can overwrite */, save_explain_data(thd->lex->explain, false /* can overwrite */,
need_tmp, need_tmp,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment