diff --git a/mysql-test/r/subselect_mat_cost_bugs.result b/mysql-test/r/subselect_mat_cost_bugs.result index 9bfd5bd67b5..dba4d049da3 100644 --- a/mysql-test/r/subselect_mat_cost_bugs.result +++ b/mysql-test/r/subselect_mat_cost_bugs.result @@ -379,6 +379,7 @@ drop table t3, t4, t5; # # LP BUG#858038 The result of a query with NOT IN subquery depends on the state of the optimizer switch # +set @optimizer_switch_save= @@optimizer_switch; create table t1 (c1 char(2) not null, c2 char(2)); create table t2 (c3 char(2), c4 char(2)); insert into t1 values ('a1', 'b1'); @@ -400,6 +401,7 @@ id select_type table type possible_keys key key_len ref rows Extra select * from t1 where c1 = 'a2' and (c1, c2) not in (select * from t2); c1 c2 drop table t1, t2; +set optimizer_switch= @optimizer_switch_save; # # MDEV-12673: cost-based choice between materialization and in-to-exists # @@ -442,3 +444,44 @@ id select_type table type possible_keys key key_len ref rows Extra 2 DEPENDENT SUBQUERY t3 const PRIMARY PRIMARY 4 const 1 2 DEPENDENT SUBQUERY t2 index NULL i2 11 NULL 2 Using where; Using index DROP TABLE t1,t2,t3; +# +# MDEV-7599: in-to-exists chosen after min/max optimization +# +set @optimizer_switch_save= @@optimizer_switch; +CREATE TABLE t1 (a INT, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT, c INT) ENGINE=MyISAM; +INSERT INTO t2 VALUES (1,6),(2,4), (8,9); +SELECT * FROM t2 WHERE b != ALL (SELECT MIN(a) FROM t1, t2 WHERE t2.c = t2.b); +b c +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b != ALL (SELECT MIN(a) FROM t1, t2 WHERE t2.c = t2.b); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using where +2 MATERIALIZED t1 index NULL a 5 NULL 2 100.00 Using index +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (not(<`test`.`t2`.`b`>((`test`.`t2`.`b`,`test`.`t2`.`b` in ( (select min(`test`.`t1`.`a`) from `test`.`t1` join `test`.`t2` where (`test`.`t2`.`c` = `test`.`t2`.`b`) ), (`test`.`t2`.`b` in on distinct_key where ((`test`.`t2`.`b` = ``.`MIN(a)`)))))))) +set optimizer_switch= 'materialization=off'; +SELECT * FROM t2 WHERE b != ALL (SELECT MIN(a) FROM t1, t2 WHERE t2.c = t2.b); +b c +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b != ALL (SELECT MIN(a) FROM t1, t2 WHERE t2.c = t2.b); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t1 index NULL a 5 NULL 2 100.00 Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` where (not(<`test`.`t2`.`b`>((`test`.`t2`.`b`,(select min(`test`.`t1`.`a`) from `test`.`t1` join `test`.`t2` where (`test`.`t2`.`c` = `test`.`t2`.`b`) having trigcond(((`test`.`t2`.`b`) = (min(`test`.`t1`.`a`))))))))) +set optimizer_switch= @optimizer_switch_save; +DROP TABLE t1,t2; +CREATE TABLE t1 (f1 varchar(10)) ENGINE=MyISAM; +INSERT INTO t1 VALUES ('foo'),('bar'); +CREATE TABLE t2 (f2 varchar(10), key(f2)) ENGINE=MyISAM; +INSERT INTO t2 VALUES ('baz'),('qux'); +CREATE TABLE t3 (f3 varchar(10)) ENGINE=MyISAM; +INSERT INTO t3 VALUES ('abc'),('def'); +SELECT * FROM t1 +WHERE f1 = ALL( SELECT MAX(t2a.f2) +FROM t2 AS t2a INNER JOIN t2 t2b INNER JOIN t3 +ON (f3 = t2b.f2) ); +f1 +DROP TABLE t1,t2,t3; diff --git a/mysql-test/t/subselect_mat_cost_bugs.test b/mysql-test/t/subselect_mat_cost_bugs.test index 316ac707bef..9e3ac603ec6 100644 --- a/mysql-test/t/subselect_mat_cost_bugs.test +++ b/mysql-test/t/subselect_mat_cost_bugs.test @@ -406,6 +406,8 @@ drop table t3, t4, t5; --echo # LP BUG#858038 The result of a query with NOT IN subquery depends on the state of the optimizer switch --echo # +set @optimizer_switch_save= @@optimizer_switch; + create table t1 (c1 char(2) not null, c2 char(2)); create table t2 (c3 char(2), c4 char(2)); @@ -425,6 +427,8 @@ select * from t1 where c1 = 'a2' and (c1, c2) not in (select * from t2); drop table t1, t2; +set optimizer_switch= @optimizer_switch_save; + --echo # --echo # MDEV-12673: cost-based choice between materialization and in-to-exists --echo # @@ -463,3 +467,43 @@ SELECT * FROM t1 WHERE i1 NOT IN ( ); DROP TABLE t1,t2,t3; + +--echo # +--echo # MDEV-7599: in-to-exists chosen after min/max optimization +--echo # + +set @optimizer_switch_save= @@optimizer_switch; + +CREATE TABLE t1 (a INT, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1),(2); + +CREATE TABLE t2 (b INT, c INT) ENGINE=MyISAM; +INSERT INTO t2 VALUES (1,6),(2,4), (8,9); + +let $q= +SELECT * FROM t2 WHERE b != ALL (SELECT MIN(a) FROM t1, t2 WHERE t2.c = t2.b); + +eval $q; +eval EXPLAIN EXTENDED $q; +set optimizer_switch= 'materialization=off'; +eval $q; +eval EXPLAIN EXTENDED $q; +set optimizer_switch= @optimizer_switch_save; + +DROP TABLE t1,t2; + +CREATE TABLE t1 (f1 varchar(10)) ENGINE=MyISAM; +INSERT INTO t1 VALUES ('foo'),('bar'); + +CREATE TABLE t2 (f2 varchar(10), key(f2)) ENGINE=MyISAM; +INSERT INTO t2 VALUES ('baz'),('qux'); + +CREATE TABLE t3 (f3 varchar(10)) ENGINE=MyISAM; +INSERT INTO t3 VALUES ('abc'),('def'); + +SELECT * FROM t1 + WHERE f1 = ALL( SELECT MAX(t2a.f2) + FROM t2 AS t2a INNER JOIN t2 t2b INNER JOIN t3 + ON (f3 = t2b.f2) ); + +DROP TABLE t1,t2,t3; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 12337ec2b1c..e0da946d190 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -2493,6 +2493,27 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) DBUG_ENTER("Item_in_subselect::inject_in_to_exists_cond"); DBUG_ASSERT(thd == join_arg->thd); + if (select_lex->min_max_opt_list.elements) + { + /* + MIN/MAX optimizations have been applied to Item_sum objects + of the subquery this subquery predicate in opt_sum_query(). + Injection of new condition invalidates this optimizations. + Thus those optimizations must be rolled back. + */ + List_iterator_fast it(select_lex->min_max_opt_list); + Item_sum *item; + while ((item= it++)) + { + item->clear(); + item->reset_forced_const(); + } + if (where_item) + where_item->update_used_tables(); + if (having_item) + having_item->update_used_tables(); + } + if (where_item) { List *and_args= NULL; diff --git a/sql/item_sum.h b/sql/item_sum.h index 86093f5d4f5..dcc3e494f82 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -454,6 +454,7 @@ public: used_tables_cache= 0; forced_const= TRUE; } + void reset_forced_const() { forced_const= FALSE; } virtual bool const_item() const { return forced_const; } virtual bool const_during_execution() const { return false; } virtual void print(String *str, enum_query_type query_type); diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index aa8b17a2c85..1a8c6be5f41 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -253,6 +253,8 @@ int opt_sum_query(THD *thd, int error= 0; DBUG_ENTER("opt_sum_query"); + thd->lex->current_select->min_max_opt_list.empty(); + if (conds) where_tables= conds->used_tables(); @@ -444,7 +446,14 @@ int opt_sum_query(THD *thd, item_sum->aggregator_clear(); } else + { item_sum->reset_and_add(); + /* + Save a reference to the item for possible rollback + of the min/max optimizations for this select + */ + thd->lex->current_select->min_max_opt_list.push_back(item_sum); + } item_sum->make_const(); recalc_const_item= 1; break; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index fa866bc7008..6611fd43876 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1875,6 +1875,7 @@ void st_select_lex::init_query() leaf_tables_prep.empty(); leaf_tables.empty(); item_list.empty(); + min_max_opt_list.empty(); join= 0; having= prep_having= where= prep_where= 0; olap= UNSPECIFIED_OLAP_TYPE; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index b13befd8dbe..d283085886e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -766,6 +766,11 @@ public: */ List *ftfunc_list; List ftfunc_list_alloc; + /* + The list of items to which MIN/MAX optimizations of opt_sum_query() + have been applied. Used to rollback those optimizations if it's needed. + */ + List min_max_opt_list; JOIN *join; /* after JOIN::prepare it is pointer to corresponding JOIN */ List top_join_list; /* join list of the top level */ List *join_list; /* list for the currently parsed join */