diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index 5c78d296b6b12..e4f3e662513aa 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1112,3 +1112,35 @@ DROP TABLE t1, t2; # # End of 10.11 tests # +# +# MDEV-38136 Prevent elimination of tables in a FULL OUTER JOIN +# +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); +create table t2 (a int primary key, b int) +as select a, a as b from t1 where a in (1,2); +create table t3 (a int primary key, b int) +as select a, a as b from t1 where a in (1,3); +# These will not be eliminated because contains a FULL OUTER JOIN. +explain extended select t1.a from t1 full join t2 on t2.a=t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE t2 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE t2 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE t2 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +drop table t1, t2, t3; +# End of 12.3 tests diff --git a/mysql-test/main/table_elim.test b/mysql-test/main/table_elim.test index 4158d2ca5ac8c..986c7e037e8c8 100644 --- a/mysql-test/main/table_elim.test +++ b/mysql-test/main/table_elim.test @@ -844,3 +844,24 @@ DROP TABLE t1, t2; --echo # --echo # End of 10.11 tests --echo # + +--echo # +--echo # MDEV-38136 Prevent elimination of tables in a FULL OUTER JOIN +--echo # +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); + +create table t2 (a int primary key, b int) + as select a, a as b from t1 where a in (1,2); + +create table t3 (a int primary key, b int) + as select a, a as b from t1 where a in (1,3); + +--echo # These will not be eliminated because contains a FULL OUTER JOIN. +explain extended select t1.a from t1 full join t2 on t2.a=t1.a; +explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; +explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; + +drop table t1, t2, t3; + +--echo # End of 12.3 tests diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 812c206540f17..cc07903d0da78 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -825,7 +825,8 @@ eliminate_tables_for_list(JOIN *join, List *join_list, if (tbl->nested_join) { /* This is "... LEFT JOIN (join_nest) ON cond" */ - if (eliminate_tables_for_list(join, + if (!(tbl->outer_join & JOIN_TYPE_FULL) && + eliminate_tables_for_list(join, &tbl->nested_join->join_list, tbl->nested_join->used_tables, tbl->on_expr, @@ -840,7 +841,8 @@ eliminate_tables_for_list(JOIN *join, List *join_list, else { /* This is "... LEFT JOIN tbl ON cond" */ - if (!(tbl->table->map & outside_used_tables) && + if (!(tbl->outer_join & JOIN_TYPE_FULL) && + !(tbl->table->map & outside_used_tables) && check_func_dependency(join, tbl->table->map, NULL, tbl, tbl->on_expr)) { @@ -2105,7 +2107,8 @@ void Dep_analysis_context::dbug_print_deps() char buf[128]; String str(buf, sizeof(buf), &my_charset_bin); str.length(0); - eq_mod->expr->print(&str, QT_ORDINARY); + if (eq_mod->expr) + eq_mod->expr->print(&str, QT_ORDINARY); if (eq_mod->field) { fprintf(DBUG_FILE, " equality%ld: %s -> %s.%s\n", diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 64cfc5e255d90..0308ae7c4196f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1343,7 +1343,7 @@ void LEX::start(THD *thd_arg) memset(&trg_chistics, 0, sizeof(trg_chistics)); selects_for_hint_resolution.empty(); - has_full_outer_join= false; + full_join_count= 0; DBUG_VOID_RETURN; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 42c4449a0107e..8e46c26650318 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3593,8 +3593,8 @@ struct LEX: public Query_tables_list vers_select_conds_t vers_conditions; vers_select_conds_t period_conditions; - /* False by default, this will be true if the query has a full join. */ - bool has_full_outer_join; + /* Zero by default, this counts the number of full joins in the query. */ + uint full_join_count; inline void free_set_stmt_mem_root() { diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 0bc147c0f4ac4..4b7ff0a4b128a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2773,7 +2773,7 @@ JOIN::optimize_inner() mature enough to correctly execute the queries. But for now this allows for some EXPLAIN EXTENDED support. */ - else if (!thd->lex->has_full_outer_join) + else if (!thd->lex->full_join_count) { if (optimize_stage2()) DBUG_RETURN(1); @@ -5627,7 +5627,16 @@ make_join_statistics(JOIN *join, List &tables_list, TABLE **table_vector; JOIN_TAB *stat,*stat_end,*s,**stat_ref, **stat_vector; KEYUSE *keyuse,*start_keyuse; + + /* + outer_join here does not have the same meaning as TABLE_LIST::outer_join. + Here, outer_join is the union of all table numbers representing tables + that participate in this join. TABLE_LIST::outer_join marks how a + TABLE_LIST participates in a particular JOIN (as a right table, left table, + as part of a FULL JOIN, etc). + */ table_map outer_join=0; + table_map no_rows_const_tables= 0; SARGABLE_PARAM *sargables= 0; List_iterator ti(tables_list); @@ -5841,6 +5850,19 @@ make_join_statistics(JOIN *join, List &tables_list, join->const_table_map= no_rows_const_tables; join->const_tables= const_count; eliminate_tables(join); + + /* + Temporary gate. As the FULL JOIN implementation matures, this keeps moving + deeper into the server until it's eventually eliminated. + */ + if (thd->lex->full_join_count && !thd->lex->describe) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), + "FULL JOINs that cannot be converted to LEFT, RIGHT, or " + "INNER JOINs"); + goto error; + } + join->const_table_map &= ~no_rows_const_tables; const_count= join->const_tables; found_const_table_map= join->const_table_map; @@ -20353,23 +20375,10 @@ static COND *rewrite_full_outer_joins(JOIN *join, see the right table first. If, on this call to rewrite_full_outer_joins, the current table is left member of the JOIN (e.g., left_member FULL JOIN ...) it means we couldn't rewrite the FULL JOIN as a LEFT, RIGHT, or - INNER JOIN, so emit an error (unless we're in an EXPLAIN EXTENDED, permit - that). + INNER JOIN, so pass along the unmodified FULL JOIN. */ if (right_table->outer_join & JOIN_TYPE_LEFT) - { - if (join->thd->lex->describe) - return conds; - - /* - We always see the RIGHT table before the LEFT table, so nothing to - do here for JOIN_TYPE_LEFT. - */ - my_error(ER_NOT_SUPPORTED_YET, MYF(0), - "FULL JOINs that cannot be converted to LEFT, RIGHT, or " - "INNER JOINs"); - return nullptr; - } + return conds; /* Must always see the right table before the left. Down below, we deal @@ -20420,7 +20429,7 @@ static COND *rewrite_full_outer_joins(JOIN *join, RIGHT JOINs don't actually exist in MariaDB! */ *table_ptr= li.swap_next(); - join->thd->lex->has_full_outer_join= false; + --join->thd->lex->full_join_count; } else { @@ -20457,7 +20466,7 @@ static COND *rewrite_full_outer_joins(JOIN *join, if (peeked_map & not_null_tables) { rewrite_full_to_left(left_table, right_table); - join->thd->lex->has_full_outer_join= false; + --join->thd->lex->full_join_count; } // else the FULL JOIN cannot be rewritten, pass it along. } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 803f2a489dd98..1238eb1cf0592 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1909,7 +1909,7 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *view_table_alias, DBUG_ASSERT(view_query_lex == thd->lex); thd->lex= parent_query_lex; // Needed for prepare_security result= !view_table_alias->prelocking_placeholder && view_table_alias->prepare_security(thd); - parent_query_lex->has_full_outer_join= view_query_lex->has_full_outer_join; + parent_query_lex->full_join_count= view_query_lex->full_join_count; lex_end(view_query_lex); end: diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 24fd9b5510e5e..a5a5f164dc962 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12392,7 +12392,7 @@ join_table: $5->on_context= $1->on_context; Select->parsing_place= NO_MATTER; $$= $1; - Lex->has_full_outer_join= true; + ++Lex->full_join_count; } | table_ref FULL opt_outer JOIN_SYM table_factor { @@ -12408,7 +12408,7 @@ join_table: USING '(' using_list ')' { add_join_natural($1,$5,$9,Select); - Lex->has_full_outer_join= true; + ++Lex->full_join_count; } | table_ref NATURAL FULL opt_outer JOIN_SYM table_factor { @@ -12425,7 +12425,7 @@ join_table: JOIN_TYPE_NATURAL); add_join_natural($6,$1,NULL,Select); - Lex->has_full_outer_join= true; + ++Lex->full_join_count; } ;