diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index e1ae218b4cc41..b210903f229c2 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3969,6 +3969,8 @@ create table two (v int); insert into two (v) values (2); create table three (v int); insert into three (v) values (3); +create table four (v int); +insert into three (v) values (4); # (FULL)FULL to (INNER)INNER JOIN select * from one full join two on one.v = two.v full join three on two.v = three.v where one.v is not null and two.v is not null and three.v is not null; v v v @@ -4006,9 +4008,11 @@ NULL 2 NULL select * from one left join two on one.v = two.v full join three on two.v = three.v where three.v is not null; v v v NULL NULL 3 +NULL NULL 4 select * from one left join two on one.v = two.v right join three on two.v = three.v; v v v NULL NULL 3 +NULL NULL 4 # (LEFT)FULL to (LEFT)LEFT JOIN insert into one (v) values (2),(3); insert into two (v) values (1); @@ -4032,5 +4036,65 @@ v v v select * from three left join one on one.v = 1 left join two on two.v = 1; v v v 1 1 1 -drop table one, two, three; +# Interleaved JOIN checks +explain extended select * from one full outer join (two, three) on one.v=two.v; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`two`.`v` AS `v`,`test`.`three`.`v` AS `v` from `test`.`one` full join (`test`.`two` join `test`.`three`) on(multiple equal(`test`.`one`.`v`, `test`.`two`.`v`)) +# ^^^ join order must be one, two, three +explain extended select * from (one, two) full outer join three on one.v=two.v; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`two`.`v` AS `v`,`test`.`three`.`v` AS `v` from `test`.`one` join `test`.`two` full join `test`.`three` on(multiple equal(`test`.`one`.`v`, `test`.`two`.`v`)) +# ^^^ join order must be one, two, three +explain extended select * from one full outer join (three, two) on one.v=two.v; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`three`.`v` AS `v`,`test`.`two`.`v` AS `v` from `test`.`one` full join (`test`.`three` join `test`.`two`) on(multiple equal(`test`.`one`.`v`, `test`.`two`.`v`)) +# ^^^ join order must be one, three, two +explain extended select * from (one, two t) full outer join (two v, three) on true; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE t UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE v UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`t`.`v` AS `v`,`test`.`v`.`v` AS `v`,`test`.`three`.`v` AS `v` from `test`.`one` join `test`.`two` `t` full join (`test`.`two` `v` join `test`.`three`) on(1) +# ^^^ join order must be one, t, v, three +explain extended select * from (one full outer join two on true) full outer join (three full outer join four on true) on true; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE four UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`two`.`v` AS `v`,`test`.`three`.`v` AS `v`,`test`.`four`.`v` AS `v` from `test`.`one` full join `test`.`two` on(1) full join (`test`.`three` full join `test`.`four` on(1)) on(1) +# ^^^ join order must be one, two, three, four +explain extended select * from (one full outer join two on true) inner join (three full outer join four on true); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three system NULL NULL NULL NULL 1 100.00 +1 SIMPLE four system NULL NULL NULL NULL 0 0.00 Const row not found +Warnings: +Note 1003 select `test`.`one`.`v` AS `v`,`test`.`two`.`v` AS `v`,1 AS `v`,NULL AS `v` from `test`.`one` full join `test`.`two` on(1) +# ^^^ join order must be one, two, three, four +explain extended select * from four full outer join (two full outer join three on true) on true; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE four system NULL NULL NULL NULL 0 0.00 Const row not found +1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select NULL AS `v`,`test`.`two`.`v` AS `v`,`test`.`three`.`v` AS `v` from (`test`.`two` full join `test`.`three` on(1)) +# ^^^ join order must be four, two, three +drop table one, two, three, four; # End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index e2a668d048a9f..60f61f6dbb9c7 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2187,6 +2187,8 @@ create table two (v int); insert into two (v) values (2); create table three (v int); insert into three (v) values (3); +create table four (v int); +insert into three (v) values (4); --echo # (FULL)FULL to (INNER)INNER JOIN select * from one full join two on one.v = two.v full join three on two.v = three.v where one.v is not null and two.v is not null and three.v is not null; @@ -2223,7 +2225,23 @@ select * from three; select * from one left join two on one.v = two.v full join three on two.v = three.v where three.v = 1; select * from three left join one on one.v = 1 left join two on two.v = 1; -drop table one, two, three; +--echo # Interleaved JOIN checks +explain extended select * from one full outer join (two, three) on one.v=two.v; +--echo # ^^^ join order must be one, two, three +explain extended select * from (one, two) full outer join three on one.v=two.v; +--echo # ^^^ join order must be one, two, three +explain extended select * from one full outer join (three, two) on one.v=two.v; +--echo # ^^^ join order must be one, three, two +explain extended select * from (one, two t) full outer join (two v, three) on true; +--echo # ^^^ join order must be one, t, v, three +explain extended select * from (one full outer join two on true) full outer join (three full outer join four on true) on true; +--echo # ^^^ join order must be one, two, three, four +explain extended select * from (one full outer join two on true) inner join (three full outer join four on true); +--echo # ^^^ join order must be one, two, three, four +explain extended select * from four full outer join (two full outer join three on true) on true; +--echo # ^^^ join order must be four, two, three + +drop table one, two, three, four; # TODO fix PS protocol before end of FULL OUTER JOIN development --enable_ps_protocol diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index ed4797a34fe73..3360a62502e5f 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -4722,7 +4722,7 @@ void subselect_union_engine::print(String *str, enum_query_type query_type) void subselect_uniquesubquery_engine::print(String *str, enum_query_type query_type) { - TABLE *table= tab->tab_list ? tab->tab_list->table : tab->table; + TABLE *table= tab->get_tab_list() ? tab->get_tab_list()->table : tab->table; str->append(STRING_WITH_LEN("(")); tab->ref.items[0]->print(str, query_type); str->append(STRING_WITH_LEN(" in ")); @@ -4775,7 +4775,7 @@ void subselect_uniquesubquery_engine::print(String *str) void subselect_indexsubquery_engine::print(String *str, enum_query_type query_type) { - TABLE *table= tab->tab_list ? tab->tab_list->table : tab->table; + TABLE *table= tab->get_tab_list() ? tab->get_tab_list()->table : tab->table; str->append(STRING_WITH_LEN("(")); tab->ref.items[0]->print(str, query_type); str->append(STRING_WITH_LEN(" in ")); diff --git a/sql/opt_hints.cc b/sql/opt_hints.cc index 3d8f9fb919801..a73be3f991c97 100644 --- a/sql/opt_hints.cc +++ b/sql/opt_hints.cc @@ -1321,7 +1321,7 @@ bool Opt_hints_qb::set_join_hint_deps(JOIN *join, bool hint_table_found= false; for (uint i= 0; i < join->table_count; i++) { - TABLE_LIST *table= join->join_tab[i].tab_list; + TABLE_LIST *table= join->join_tab[i].get_tab_list(); if (!compare_table_name(&tbl_name_and_qb, table)) { hint_table_found= true; @@ -1337,7 +1337,7 @@ bool Opt_hints_qb::set_join_hint_deps(JOIN *join, // Hint tables are always dependent on preceding tables join_tab->dependent |= hint_tab_map; update_nested_join_deps(join, join_tab, hint_tab_map); - hint_tab_map |= join_tab->tab_list->get_map(); + hint_tab_map |= join_tab->get_tab_list()->get_map(); break; } } @@ -1356,7 +1356,7 @@ bool Opt_hints_qb::set_join_hint_deps(JOIN *join, JOIN_TAB *join_tab= &join->join_tab[i]; const table_map dependent_tables= get_other_dep(join, hint->hint_type, hint_tab_map, - join_tab->tab_list->get_map()); + join_tab->get_tab_list()->get_map()); update_nested_join_deps(join, join_tab, dependent_tables); join_tab->dependent |= dependent_tables; } @@ -1422,14 +1422,14 @@ bool Opt_hints_qb::set_join_hint_deps(JOIN *join, void Opt_hints_qb::update_nested_join_deps(JOIN *join, const JOIN_TAB *hint_tab, table_map hint_tab_map) { - const TABLE_LIST *table= hint_tab->tab_list; + const TABLE_LIST *table= hint_tab->get_tab_list(); if (table->embedding) { for (uint i= 0; i < join->table_count; i++) { JOIN_TAB *tab= &join->join_tab[i]; /* Walk up the nested joins that tab->table is a part of */ - for (TABLE_LIST *emb= tab->tab_list->embedding; emb; emb=emb->embedding) + for (TABLE_LIST *emb= tab->get_tab_list()->embedding; emb; emb=emb->embedding) { /* Apply the rule only for outer joins. Semi-joins do not impose such @@ -1439,7 +1439,7 @@ void Opt_hints_qb::update_nested_join_deps(JOIN *join, const JOIN_TAB *hint_tab, { const NESTED_JOIN *const nested_join= emb->nested_join; /* Is hint_tab somewhere inside this nested join, too? */ - if (hint_tab->embedding_map & nested_join->nj_map) + if (hint_tab->embedding_map & nested_join->get_nj_map()) { /* Yes, it is. Then, tab->table be also dependent on all outside diff --git a/sql/opt_trace.cc b/sql/opt_trace.cc index ed8b0137d84c2..410fb14dc2842 100644 --- a/sql/opt_trace.cc +++ b/sql/opt_trace.cc @@ -660,7 +660,7 @@ void trace_plan_prefix(Json_writer_object *jsobj, JOIN *join, uint idx, prefix_str.length(0); for (uint i= join->const_tables; i < idx; i++) { - TABLE_LIST *const tr= join->positions[i].table->tab_list; + TABLE_LIST *const tr= join->positions[i].table->get_tab_list(); if (!(tr->map & join_tables)) { String str; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index be98f1aee1f16..1ca4262aabaf4 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8346,6 +8346,8 @@ TABLE_LIST *st_select_lex::nest_last_join(THD *thd) if (prev_join_using) ptr->join_using_fields= prev_join_using; } + if (table->outer_join & JOIN_TYPE_FULL) + nested_join->is_foj= true; } nested_join->used_tables= nested_join->not_null_tables= (table_map) 0; DBUG_RETURN(ptr); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8133cc4074666..28b89c72e7aa5 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -584,7 +584,7 @@ static void trace_table_dependencies(THD *thd, for (uint i= 0; i < table_count; i++) { - TABLE_LIST *table_ref= join_tabs[i].tab_list; + TABLE_LIST *table_ref= join_tabs[i].get_tab_list(); Json_writer_object trace_one_table(thd); trace_one_table. add_table_name(&join_tabs[i]). @@ -2399,7 +2399,7 @@ JOIN::optimize_inner() } if (!allowed_top_level_tables) - calc_allowed_top_level_tables(select_lex); + allowed_top_level_tables= calc_allowed_top_level_tables(select_lex); if (optimize_constant_subqueries()) DBUG_RETURN(1); @@ -5799,7 +5799,7 @@ make_join_statistics(JOIN *join, List &tables_list, outer_join|= table->map; s->embedding_map= 0; for (;embedding; embedding= embedding->embedding) - s->embedding_map|= embedding->nested_join->nj_map; + s->embedding_map|= embedding->nested_join->get_nj_map(); continue; } if (embedding) @@ -5820,7 +5820,7 @@ make_join_statistics(JOIN *join, List &tables_list, } inside_an_outer_join= TRUE; NESTED_JOIN *nested_join= embedding->nested_join; - s->embedding_map|=nested_join->nj_map; + s->embedding_map|=nested_join->get_nj_map(); s->dependent|= embedding->dep_tables; embedding= embedding->embedding; outer_join|= nested_join->used_tables; @@ -5899,18 +5899,6 @@ make_join_statistics(JOIN *join, List &tables_list, 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; @@ -10291,6 +10279,7 @@ choose_plan(JOIN *join, table_map join_tables, TABLE_LIST *emb_sjm_nest) DBUG_ENTER("choose_plan"); join->limit_optimization_mode= false; + join->foj_tables= 0; join->extra_heuristic_pruning= false; join->prune_level= join->thd->variables.optimizer_prune_level; @@ -10407,6 +10396,18 @@ choose_plan(JOIN *join, table_map join_tables, TABLE_LIST *emb_sjm_nest) } } + /* + 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"); + DBUG_RETURN(TRUE); + } + join->emb_sjm_nest= 0; DBUG_RETURN(FALSE); } @@ -15910,13 +15911,13 @@ uint check_join_cache_usage(JOIN_TAB *tab, !(join->allowed_join_cache_types & JOIN_CACHE_INCREMENTAL_BIT); bool no_hashed_cache= !(join->allowed_join_cache_types & JOIN_CACHE_HASHED_BIT); - bool hint_disables_bnl= !hint_table_state(join->thd, tab->tab_list->table, + bool hint_disables_bnl= !hint_table_state(join->thd, tab->get_tab_list()->table, BNL_HINT_ENUM, true); - bool no_bka_cache= !hint_table_state(join->thd, tab->tab_list->table, + bool no_bka_cache= !hint_table_state(join->thd, tab->get_tab_list()->table, BKA_HINT_ENUM, join->allowed_join_cache_types & JOIN_CACHE_BKA_BIT); - bool hint_forces_bnl= hint_table_state(join->thd, tab->tab_list->table, + bool hint_forces_bnl= hint_table_state(join->thd, tab->get_tab_list()->table, BNL_HINT_ENUM, false); - bool hint_forces_bka= hint_table_state(join->thd, tab->tab_list->table, + bool hint_forces_bka= hint_table_state(join->thd, tab->get_tab_list()->table, BKA_HINT_ENUM, false); join->return_tab= 0; @@ -20552,7 +20553,7 @@ static COND *rewrite_full_outer_joins(JOIN *join, First unused bit in nested_join_map after the call. */ -static uint build_bitmap_for_nested_joins(List *join_list, +static uint build_bitmap_for_nested_joins(List *join_list, uint first_unused) { List_iterator li(*join_list); @@ -20561,26 +20562,26 @@ static uint build_bitmap_for_nested_joins(List *join_list, while ((table= li++)) { NESTED_JOIN *nested_join; - if ((nested_join= table->nested_join)) - { - /* - It is guaranteed by simplify_joins() function that a nested join - that has only one child represents a single table VIEW (and the child - is an underlying table). We don't assign bits to such nested join - structures because - 1. it is redundant (a "sequence" of one table cannot be interleaved - with anything) - 2. we could run out bits in nested_join_map otherwise. - */ - if (nested_join->n_tables != 1) - { - /* Don't assign bits to sj-nests */ - if (table->on_expr) - nested_join->nj_map= (nested_join_map) 1 << first_unused++; - first_unused= build_bitmap_for_nested_joins(&nested_join->join_list, - first_unused); - } - } + if (!(nested_join= table->nested_join)) + continue; + + /* + It is guaranteed by simplify_joins() function that a nested join + that has only one child represents a single table VIEW (and the child + is an underlying table). We don't assign bits to such nested join + structures because + 1. it is redundant (a "sequence" of one table cannot be interleaved + with anything) + 2. we could run out bits in nested_join_map otherwise. + */ + if (nested_join->n_tables == 1) + continue; + + /* Don't assign bits to sj-nests */ + if (table->on_expr) + nested_join->set_nj_map(first_unused++); + first_unused= build_bitmap_for_nested_joins(&nested_join->join_list, + first_unused); } DBUG_RETURN(first_unused); } @@ -20725,48 +20726,95 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab) { JOIN *join= next_tab->join; + /* next_tab must be a single table */ + DBUG_ASSERT(!next_tab || !next_tab->tab_list || + !next_tab->tab_list->nested_join); + + /* + If we've entered a FULL JOIN then we expect whatever table + is represented by next_tab to be on the other side of the + FULL JOIN. + */ + if (join->foj_tables) // we're in a FULL JOIN + { + /* + If the candidate next table is not a nested join, then + it's a single table and we can inspect its table map + directly to be sure that it is expected. + */ + if (!(join->foj_tables & next_tab->tab_list->get_map())) + return TRUE; // Error: attempted to interleave in the FULL JOIN. + } + if (join->cur_embedding_map & ~next_tab->embedding_map) { - /* + /* next_tab is outside of the "pair of brackets" we're currently in. Cannot add it. */ - return TRUE; + DBUG_ASSERT(false); } - - TABLE_LIST *next_emb= next_tab->table->pos_in_table_list->embedding; + /* Do update counters for "pairs of brackets" that we've left (marked as X,Y,Z in the above picture) */ - for (;next_emb && next_emb != join->emb_sjm_nest; + for (TABLE_LIST *next_emb= next_tab->table->pos_in_table_list->embedding; + next_emb && next_emb != join->emb_sjm_nest; next_emb= next_emb->embedding) { - if (!next_emb->sj_on_expr) + /* Tables with embeddings must have a nested_join instance */ + DBUG_ASSERT(next_emb->nested_join); + + if (next_emb->sj_on_expr) + continue; + + next_emb->nested_join->counter++; + DBUG_ASSERT(next_emb->nested_join->counter <= next_emb->nested_join->n_tables); + if (next_emb->nested_join->counter == 1) { - next_emb->nested_join->counter++; - DBUG_ASSERT(next_emb->nested_join->counter <= next_emb->nested_join->n_tables); - if (next_emb->nested_join->counter == 1) - { - /* - next_emb is the first table inside a nested join we've "entered". In - the picture above, we're looking at the 'X' bracket. Don't exit yet - as X bracket might have Y pair bracket. - */ - join->cur_embedding_map |= next_emb->nested_join->nj_map; - } - - DBUG_ASSERT(next_emb->nested_join->n_tables >= - next_emb->nested_join->counter); + /* + next_emb is the first table inside a nested join we've "entered". In + the picture above, we're looking at the 'X' bracket. Don't exit yet + as X bracket might have Y pair bracket. + */ + join->cur_embedding_map |= next_emb->nested_join->get_nj_map(); - if (next_emb->nested_join->n_tables != - next_emb->nested_join->counter) - break; /* - We're currently at Y or Z-bracket as depicted in the above picture. - Mark that we've left it and continue walking up the brackets hierarchy. + Remember which tables participate in the FULL OUTER JOIN. This allows + us to handle the nested FULL OUTER JOIN case too. */ - join->cur_embedding_map &= ~next_emb->nested_join->nj_map; + if (next_emb->nested_join->is_foj) + { + const table_map partner_tables= + next_emb->foj_partner->nested_join ? + next_emb->foj_partner->nested_join->used_tables : + next_emb->foj_partner->get_map(); + join->foj_tables|= (next_emb->nested_join->used_tables | + partner_tables); + } + } + + DBUG_ASSERT(next_emb->nested_join->n_tables >= + next_emb->nested_join->counter); + + if (next_emb->nested_join->n_tables != + next_emb->nested_join->counter) + break; + /* + We're currently at Y or Z-bracket as depicted in the above picture. + Mark that we've left it and continue walking up the brackets hierarchy. + */ + join->cur_embedding_map &= ~next_emb->nested_join->get_nj_map(); + + /* Don't forget to mask-out partner tables, too. */ + if (next_emb->nested_join->is_foj) + { + const table_map partner_tables= + next_emb->foj_partner->nested_join ? + next_emb->foj_partner->nested_join->used_tables : + next_emb->foj_partner->get_map(); + join->foj_tables&= ~(next_emb->nested_join->used_tables | partner_tables); } } return FALSE; @@ -20829,24 +20877,36 @@ static void restore_prev_nj_state(JOIN_TAB *last) { TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding; JOIN *join= last->join; - for (;last_emb != NULL && last_emb != join->emb_sjm_nest; + for (;last_emb != NULL && last_emb != join->emb_sjm_nest; last_emb= last_emb->embedding) { - if (!last_emb->sj_on_expr) + if (last_emb->sj_on_expr) + continue; + + NESTED_JOIN *nest= last_emb->nested_join; + DBUG_ASSERT(nest->counter > 0); + + bool was_fully_covered= nest->is_fully_covered(); + + join->cur_embedding_map|= nest->get_nj_map(); + + if (--nest->counter == 0) { - NESTED_JOIN *nest= last_emb->nested_join; - DBUG_ASSERT(nest->counter > 0); - - bool was_fully_covered= nest->is_fully_covered(); - - join->cur_embedding_map|= nest->nj_map; + join->cur_embedding_map&= ~nest->get_nj_map(); - if (--nest->counter == 0) - join->cur_embedding_map&= ~nest->nj_map; - - if (!was_fully_covered) - break; + /* Mask-out the partner tables */ + if (last_emb->nested_join->is_foj) + { + const table_map partner_tables= + last_emb->foj_partner->nested_join ? + last_emb->foj_partner->nested_join->used_tables : + last_emb->foj_partner->get_map(); + join->foj_tables&= ~(nest->used_tables | partner_tables); + } } + + if (!was_fully_covered) + break; } } @@ -20865,12 +20925,13 @@ static void restore_prev_nj_state(JOIN_TAB *last) semi-join nest. */ -void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) +table_map JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) const { TABLE_LIST *tl; List_iterator ti(lex->leaf_tables); DBUG_ENTER("JOIN::calc_allowed_top_level_tables"); DBUG_ASSERT(allowed_top_level_tables == 0); // Should only be called once + table_map allowed_top_level= 0; while ((tl= ti++)) { @@ -20887,7 +20948,7 @@ void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) if (!(embedding= tl->embedding)) { - allowed_top_level_tables |= map; + allowed_top_level |= map; continue; } @@ -20902,7 +20963,7 @@ void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) // Ok we are in the parent nested outer join nest. if (!embedding) { - allowed_top_level_tables |= map; + allowed_top_level |= map; continue; } embedding->nested_join->direct_children_map |= map; @@ -20924,9 +20985,9 @@ void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) embedding->nested_join->direct_children_map |= map; } else - allowed_top_level_tables |= map; + allowed_top_level |= map; } - DBUG_VOID_RETURN; + DBUG_RETURN(allowed_top_level); } @@ -20935,7 +20996,7 @@ void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) current plan */ -table_map JOIN::get_allowed_nj_tables(uint idx) +table_map JOIN::get_allowed_nj_tables(uint idx) const { TABLE_LIST *last_emb; if (idx > const_tables && @@ -20944,14 +21005,14 @@ table_map JOIN::get_allowed_nj_tables(uint idx) for (;last_emb && last_emb != emb_sjm_nest; last_emb= last_emb->embedding) { - if (!last_emb->sj_on_expr) + if (last_emb->sj_on_expr) + continue; + + NESTED_JOIN *nest= last_emb->nested_join; + if (!nest->is_fully_covered()) { - NESTED_JOIN *nest= last_emb->nested_join; - if (!nest->is_fully_covered()) - { - // Return tables that are direct members of this join nest - return nest->direct_children_map; - } + // Return tables that are direct members of this join nest + return nest->direct_children_map; } } } diff --git a/sql/sql_select.h b/sql/sql_select.h index 190302311dc44..0c038006d2cfa 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -251,8 +251,23 @@ struct SplM_plan_info; class SplM_opt_info; typedef struct st_join_table { - TABLE *table; - TABLE_LIST *tab_list; + TABLE *table; /**< pointer to table cursor */ + TABLE_LIST *tab_list; /**< pointer to query table, e.g. `t1` */ + + TABLE_LIST *get_tab_list() + { + DBUG_ASSERT(!tab_list || !table || tab_list == table->pos_in_table_list); + DBUG_ASSERT(!tab_list || !table || tab_list->table == table); + return tab_list; + } + + const TABLE_LIST *get_tab_list() const + { + DBUG_ASSERT(!tab_list || !table || tab_list == table->pos_in_table_list); + DBUG_ASSERT(!tab_list || !table || tab_list->table == table); + return tab_list; + } + KEYUSE *keyuse; /**< pointer to first used key */ KEY *hj_key; /**< descriptor of the used best hash join key not supported by any index */ @@ -517,8 +532,8 @@ typedef struct st_join_table { */ int keep_current_rowid; - /* NestedOuterJoins: Bitmap of nested joins this table is part of */ - nested_join_map embedding_map; + /* NestedOuterJoins: All nested joins in which this join table appears */ + nested_join_map embedding_map; // is a bitmap /* Tmp table info */ TMP_TABLE_PARAM *tmp_table_param; @@ -1284,6 +1299,13 @@ class JOIN :public Sql_alloc void restore_query_plan(Join_plan_state *restore_from); public: + /** + Used during check_interleaving_with_nj and restore_prev_nj_state, tracks + expected tables participating in a FULL OUTER JOIN (if applicable) to + ensure that they appear together in the final join order. + */ + table_map foj_tables{0}; + JOIN_TAB *join_tab, **best_ref; /* List of fields that aren't under an aggregate function */ @@ -1946,8 +1968,8 @@ class JOIN :public Sql_alloc bool transform_in_predicates_into_in_subq(THD *thd); bool optimize_upper_rownum_func(); - void calc_allowed_top_level_tables(SELECT_LEX *lex); - table_map get_allowed_nj_tables(uint idx); + table_map calc_allowed_top_level_tables(SELECT_LEX *lex) const; + table_map get_allowed_nj_tables(uint idx) const; bool propagate_dependencies(JOIN_TAB *stat); void update_key_dependencies(); table_map *export_table_dependencies() const; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 587f4f5640862..68c0d0d373c41 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12375,10 +12375,12 @@ join_table: Select->add_joined_table($1); $1->outer_join|= (JOIN_TYPE_LEFT | JOIN_TYPE_FULL); + $1->foj_partner= $5; Select->add_joined_table($5); $5->outer_join|= (JOIN_TYPE_RIGHT | JOIN_TYPE_FULL); + $5->foj_partner= $1; /* Change the current name resolution context to a local context. */ if (unlikely(push_new_name_resolution_context(thd, $1, $5))) @@ -12400,10 +12402,12 @@ join_table: Select->add_joined_table($1); $1->outer_join|= (JOIN_TYPE_LEFT | JOIN_TYPE_FULL); + $1->foj_partner= $5; Select->add_joined_table($5); $5->outer_join|= (JOIN_TYPE_RIGHT | JOIN_TYPE_FULL); + $5->foj_partner= $1; } USING '(' using_list ')' { @@ -12418,11 +12422,13 @@ join_table: $1->outer_join|= (JOIN_TYPE_LEFT | JOIN_TYPE_FULL | JOIN_TYPE_NATURAL); + $1->foj_partner= $6; Select->add_joined_table($6); $6->outer_join|= (JOIN_TYPE_RIGHT | JOIN_TYPE_FULL | JOIN_TYPE_NATURAL); + $6->foj_partner= $1; add_join_natural($6,$1,NULL,Select); ++Lex->full_join_count; diff --git a/sql/table.h b/sql/table.h index bb4975863257d..b3080a4a39075 100644 --- a/sql/table.h +++ b/sql/table.h @@ -3270,6 +3270,13 @@ struct TABLE_LIST tabledef_version.str= (const uchar *) version->str; tabledef_version.length= version->length; } + + /* + If not nullptr, then foj_partner points to the other + table in a FULL OUTER JOIN. For example, + SELECT ... FROM *this FULL OUTER JOIN foj_partner ... + */ + TABLE_LIST *foj_partner{nullptr}; private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); @@ -3462,6 +3469,15 @@ typedef struct st_nested_join */ uint n_tables; nested_join_map nj_map; /* Bit used to identify this nested join*/ + void set_nj_map(uint offset) + { + nj_map= static_cast(1ULL << offset); + } + nested_join_map get_nj_map() const + { + return nj_map; + } + /* (Valid only for semi-join nests) Bitmap of tables outside the semi-join that are used within the semi-join's ON condition. @@ -3484,6 +3500,11 @@ typedef struct st_nested_join 2. All child join nest nodes are fully covered. */ bool is_fully_covered() const { return n_tables == counter; } + + /** + True if this join nest represents a FULL OUTER JOIN. + */ + bool is_foj{false}; } NESTED_JOIN;