Skip to content
/ server Public

Commit 2d8ad76

Browse files
MDEV-38177: FULL OUTER JOIN Allowed JOIN Orders
Check that tables participating in a FULL OUTER JOIN are not separated from each other when trying new extenions to the query plan. Some tables t1, t2 participating in FULL OUTER JOIN should remain adjacent in the final JOIN order, otherwise wrong results will be computed. In this patch, tables in a FULL OUTER JOIN remember who their JOIN partner is. When JOINs are nested, these partners are preserved and checked when walking the nested JOIN tree during check_interleaving_in_nj. This code accounts for the case when tables in a FULL OUTER JOIN may themselves be nested joins, such as (t1, t2) FULL OUTER JOIN t3.
1 parent 1a6f718 commit 2d8ad76

File tree

7 files changed

+174
-5
lines changed

7 files changed

+174
-5
lines changed

mysql-test/main/join.result

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3969,6 +3969,8 @@ create table two (v int);
39693969
insert into two (v) values (2);
39703970
create table three (v int);
39713971
insert into three (v) values (3);
3972+
create table four (v int);
3973+
insert into three (v) values (4);
39723974
# (FULL)FULL to (INNER)INNER JOIN
39733975
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;
39743976
v v v
@@ -4006,9 +4008,11 @@ NULL 2 NULL
40064008
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;
40074009
v v v
40084010
NULL NULL 3
4011+
NULL NULL 4
40094012
select * from one left join two on one.v = two.v right join three on two.v = three.v;
40104013
v v v
40114014
NULL NULL 3
4015+
NULL NULL 4
40124016
# (LEFT)FULL to (LEFT)LEFT JOIN
40134017
insert into one (v) values (2),(3);
40144018
insert into two (v) values (1);
@@ -4032,5 +4036,65 @@ v v v
40324036
select * from three left join one on one.v = 1 left join two on two.v = 1;
40334037
v v v
40344038
1 1 1
4035-
drop table one, two, three;
4039+
# Interleaved JOIN checks
4040+
explain extended select * from one full outer join (two, three) on one.v=two.v;
4041+
id select_type table type possible_keys key key_len ref rows filtered Extra
4042+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4043+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4044+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4045+
Warnings:
4046+
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`))
4047+
# ^^^ join order must be one, two, three
4048+
explain extended select * from (one, two) full outer join three on one.v=two.v;
4049+
id select_type table type possible_keys key key_len ref rows filtered Extra
4050+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4051+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4052+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4053+
Warnings:
4054+
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`))
4055+
# ^^^ join order must be one, two, three
4056+
explain extended select * from one full outer join (three, two) on one.v=two.v;
4057+
id select_type table type possible_keys key key_len ref rows filtered Extra
4058+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4059+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4060+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4061+
Warnings:
4062+
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`))
4063+
# ^^^ join order must be one, three, two
4064+
explain extended select * from (one, two t) full outer join (two v, three) on true;
4065+
id select_type table type possible_keys key key_len ref rows filtered Extra
4066+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4067+
1 SIMPLE t UNKNOWN NULL NULL NULL NULL 0 0.00
4068+
1 SIMPLE v UNKNOWN NULL NULL NULL NULL 0 0.00
4069+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4070+
Warnings:
4071+
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)
4072+
# ^^^ join order must be one, t, v, three
4073+
explain extended select * from (one full outer join two on true) full outer join (three full outer join four on true) on true;
4074+
id select_type table type possible_keys key key_len ref rows filtered Extra
4075+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4076+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4077+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4078+
1 SIMPLE four UNKNOWN NULL NULL NULL NULL 0 0.00
4079+
Warnings:
4080+
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)
4081+
# ^^^ join order must be one, two, three, four
4082+
explain extended select * from (one full outer join two on true) inner join (three full outer join four on true);
4083+
id select_type table type possible_keys key key_len ref rows filtered Extra
4084+
1 SIMPLE one UNKNOWN NULL NULL NULL NULL 0 0.00
4085+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4086+
1 SIMPLE three system NULL NULL NULL NULL 1 100.00
4087+
1 SIMPLE four system NULL NULL NULL NULL 0 0.00 Const row not found
4088+
Warnings:
4089+
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)
4090+
# ^^^ join order must be one, two, three, four
4091+
explain extended select * from four full outer join (two full outer join three on true) on true;
4092+
id select_type table type possible_keys key key_len ref rows filtered Extra
4093+
1 SIMPLE four system NULL NULL NULL NULL 0 0.00 Const row not found
4094+
1 SIMPLE two UNKNOWN NULL NULL NULL NULL 0 0.00
4095+
1 SIMPLE three UNKNOWN NULL NULL NULL NULL 0 0.00
4096+
Warnings:
4097+
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))
4098+
# ^^^ join order must be four, two, three
4099+
drop table one, two, three, four;
40364100
# End of 12.3 tests

mysql-test/main/join.test

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,8 @@ create table two (v int);
21872187
insert into two (v) values (2);
21882188
create table three (v int);
21892189
insert into three (v) values (3);
2190+
create table four (v int);
2191+
insert into three (v) values (4);
21902192

21912193
--echo # (FULL)FULL to (INNER)INNER JOIN
21922194
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;
22232225
select * from one left join two on one.v = two.v full join three on two.v = three.v where three.v = 1;
22242226
select * from three left join one on one.v = 1 left join two on two.v = 1;
22252227

2226-
drop table one, two, three;
2228+
--echo # Interleaved JOIN checks
2229+
explain extended select * from one full outer join (two, three) on one.v=two.v;
2230+
--echo # ^^^ join order must be one, two, three
2231+
explain extended select * from (one, two) full outer join three on one.v=two.v;
2232+
--echo # ^^^ join order must be one, two, three
2233+
explain extended select * from one full outer join (three, two) on one.v=two.v;
2234+
--echo # ^^^ join order must be one, three, two
2235+
explain extended select * from (one, two t) full outer join (two v, three) on true;
2236+
--echo # ^^^ join order must be one, t, v, three
2237+
explain extended select * from (one full outer join two on true) full outer join (three full outer join four on true) on true;
2238+
--echo # ^^^ join order must be one, two, three, four
2239+
explain extended select * from (one full outer join two on true) inner join (three full outer join four on true);
2240+
--echo # ^^^ join order must be one, two, three, four
2241+
explain extended select * from four full outer join (two full outer join three on true) on true;
2242+
--echo # ^^^ join order must be four, two, three
2243+
2244+
drop table one, two, three, four;
22272245

22282246
# TODO fix PS protocol before end of FULL OUTER JOIN development
22292247
--enable_ps_protocol

sql/sql_parse.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8346,6 +8346,8 @@ TABLE_LIST *st_select_lex::nest_last_join(THD *thd)
83468346
if (prev_join_using)
83478347
ptr->join_using_fields= prev_join_using;
83488348
}
8349+
if (table->outer_join & JOIN_TYPE_FULL)
8350+
nested_join->is_foj= true;
83498351
}
83508352
nested_join->used_tables= nested_join->not_null_tables= (table_map) 0;
83518353
DBUG_RETURN(ptr);

sql/sql_select.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10279,6 +10279,7 @@ choose_plan(JOIN *join, table_map join_tables, TABLE_LIST *emb_sjm_nest)
1027910279
DBUG_ENTER("choose_plan");
1028010280

1028110281
join->limit_optimization_mode= false;
10282+
join->foj_tables= 0;
1028210283
join->extra_heuristic_pruning= false;
1028310284
join->prune_level= join->thd->variables.optimizer_prune_level;
1028410285

@@ -20725,6 +20726,26 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab)
2072520726
{
2072620727
JOIN *join= next_tab->join;
2072720728

20729+
/* next_tab must be a single table */
20730+
DBUG_ASSERT(!next_tab || !next_tab->tab_list ||
20731+
!next_tab->tab_list->nested_join);
20732+
20733+
/*
20734+
If we've entered a FULL JOIN then we expect whatever table
20735+
is represented by next_tab to be on the other side of the
20736+
FULL JOIN.
20737+
*/
20738+
if (join->foj_tables) // we're in a FULL JOIN
20739+
{
20740+
/*
20741+
If the candidate next table is not a nested join, then
20742+
it's a single table and we can inspect its table map
20743+
directly to be sure that it is expected.
20744+
*/
20745+
if (!(join->foj_tables & next_tab->tab_list->get_map()))
20746+
return TRUE; // Error: attempted to interleave in the FULL JOIN.
20747+
}
20748+
2072820749
if (join->cur_embedding_map & ~next_tab->embedding_map)
2072920750
{
2073020751
/*
@@ -20742,6 +20763,9 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab)
2074220763
next_emb && next_emb != join->emb_sjm_nest;
2074320764
next_emb= next_emb->embedding)
2074420765
{
20766+
/* Tables with embeddings must have a nested_join instance */
20767+
DBUG_ASSERT(next_emb->nested_join);
20768+
2074520769
if (next_emb->sj_on_expr)
2074620770
continue;
2074720771

@@ -20755,6 +20779,20 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab)
2075520779
as X bracket might have Y pair bracket.
2075620780
*/
2075720781
join->cur_embedding_map |= next_emb->nested_join->get_nj_map();
20782+
20783+
/*
20784+
Remember which tables participate in the FULL OUTER JOIN. This allows
20785+
us to handle the nested FULL OUTER JOIN case too.
20786+
*/
20787+
if (next_emb->nested_join->is_foj)
20788+
{
20789+
const table_map partner_tables=
20790+
next_emb->foj_partner->nested_join ?
20791+
next_emb->foj_partner->nested_join->used_tables :
20792+
next_emb->foj_partner->get_map();
20793+
join->foj_tables|= (next_emb->nested_join->used_tables |
20794+
partner_tables);
20795+
}
2075820796
}
2075920797

2076020798
DBUG_ASSERT(next_emb->nested_join->n_tables >=
@@ -20768,6 +20806,16 @@ static bool check_interleaving_with_nj(JOIN_TAB *next_tab)
2076820806
Mark that we've left it and continue walking up the brackets hierarchy.
2076920807
*/
2077020808
join->cur_embedding_map &= ~next_emb->nested_join->get_nj_map();
20809+
20810+
/* Don't forget to mask-out partner tables, too. */
20811+
if (next_emb->nested_join->is_foj)
20812+
{
20813+
const table_map partner_tables=
20814+
next_emb->foj_partner->nested_join ?
20815+
next_emb->foj_partner->nested_join->used_tables :
20816+
next_emb->foj_partner->get_map();
20817+
join->foj_tables&= ~(next_emb->nested_join->used_tables | partner_tables);
20818+
}
2077120819
}
2077220820
return FALSE;
2077320821
}
@@ -20843,8 +20891,20 @@ static void restore_prev_nj_state(JOIN_TAB *last)
2084320891
join->cur_embedding_map|= nest->get_nj_map();
2084420892

2084520893
if (--nest->counter == 0)
20894+
{
2084620895
join->cur_embedding_map&= ~nest->get_nj_map();
2084720896

20897+
/* Mask-out the partner tables */
20898+
if (last_emb->nested_join->is_foj)
20899+
{
20900+
const table_map partner_tables=
20901+
last_emb->foj_partner->nested_join ?
20902+
last_emb->foj_partner->nested_join->used_tables :
20903+
last_emb->foj_partner->get_map();
20904+
join->foj_tables&= ~(nest->used_tables | partner_tables);
20905+
}
20906+
}
20907+
2084820908
if (!was_fully_covered)
2084920909
break;
2085020910
}

sql/sql_select.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ typedef struct st_join_table {
532532
*/
533533
int keep_current_rowid;
534534

535-
/* NestedOuterJoins: Bitmap of nested joins this table is part of */
536-
nested_join_map embedding_map;
535+
/* NestedOuterJoins: All nested joins in which this join table appears */
536+
nested_join_map embedding_map; // is a bitmap
537537

538538
/* Tmp table info */
539539
TMP_TABLE_PARAM *tmp_table_param;
@@ -1299,6 +1299,13 @@ class JOIN :public Sql_alloc
12991299
void restore_query_plan(Join_plan_state *restore_from);
13001300

13011301
public:
1302+
/**
1303+
Used during check_interleaving_with_nj and restore_prev_nj_state, tracks
1304+
expected tables participating in a FULL OUTER JOIN (if applicable) to
1305+
ensure that they appear together in the final join order.
1306+
*/
1307+
table_map foj_tables{0};
1308+
13021309
JOIN_TAB *join_tab, **best_ref;
13031310

13041311
/* List of fields that aren't under an aggregate function */

sql/sql_yacc.yy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12375,10 +12375,12 @@ join_table:
1237512375
Select->add_joined_table($1);
1237612376
$1->outer_join|= (JOIN_TYPE_LEFT |
1237712377
JOIN_TYPE_FULL);
12378+
$1->foj_partner= $5;
1237812379

1237912380
Select->add_joined_table($5);
1238012381
$5->outer_join|= (JOIN_TYPE_RIGHT |
1238112382
JOIN_TYPE_FULL);
12383+
$5->foj_partner= $1;
1238212384

1238312385
/* Change the current name resolution context to a local context. */
1238412386
if (unlikely(push_new_name_resolution_context(thd, $1, $5)))
@@ -12400,10 +12402,12 @@ join_table:
1240012402
Select->add_joined_table($1);
1240112403
$1->outer_join|= (JOIN_TYPE_LEFT |
1240212404
JOIN_TYPE_FULL);
12405+
$1->foj_partner= $5;
1240312406

1240412407
Select->add_joined_table($5);
1240512408
$5->outer_join|= (JOIN_TYPE_RIGHT |
1240612409
JOIN_TYPE_FULL);
12410+
$5->foj_partner= $1;
1240712411
}
1240812412
USING '(' using_list ')'
1240912413
{
@@ -12418,11 +12422,13 @@ join_table:
1241812422
$1->outer_join|= (JOIN_TYPE_LEFT |
1241912423
JOIN_TYPE_FULL |
1242012424
JOIN_TYPE_NATURAL);
12425+
$1->foj_partner= $6;
1242112426

1242212427
Select->add_joined_table($6);
1242312428
$6->outer_join|= (JOIN_TYPE_RIGHT |
1242412429
JOIN_TYPE_FULL |
1242512430
JOIN_TYPE_NATURAL);
12431+
$6->foj_partner= $1;
1242612432

1242712433
add_join_natural($6,$1,NULL,Select);
1242812434
++Lex->full_join_count;

sql/table.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3270,6 +3270,13 @@ struct TABLE_LIST
32703270
tabledef_version.str= (const uchar *) version->str;
32713271
tabledef_version.length= version->length;
32723272
}
3273+
3274+
/*
3275+
If not nullptr, then foj_partner points to the other
3276+
table in a FULL OUTER JOIN. For example,
3277+
SELECT ... FROM *this FULL OUTER JOIN foj_partner ...
3278+
*/
3279+
TABLE_LIST *foj_partner{nullptr};
32733280
private:
32743281
bool prep_check_option(THD *thd, uint8 check_opt_type);
32753282
bool prep_where(THD *thd, Item **conds, bool no_where_clause);
@@ -3464,7 +3471,7 @@ typedef struct st_nested_join
34643471
nested_join_map nj_map; /* Bit used to identify this nested join*/
34653472
void set_nj_map(uint offset)
34663473
{
3467-
nj_map= static_cast<nested_join_map>(1 << offset);
3474+
nj_map= static_cast<nested_join_map>(1ULL << offset);
34683475
}
34693476
nested_join_map get_nj_map() const
34703477
{
@@ -3493,6 +3500,11 @@ typedef struct st_nested_join
34933500
2. All child join nest nodes are fully covered.
34943501
*/
34953502
bool is_fully_covered() const { return n_tables == counter; }
3503+
3504+
/**
3505+
True if this join nest represents a FULL OUTER JOIN.
3506+
*/
3507+
bool is_foj{false};
34963508
} NESTED_JOIN;
34973509

34983510

0 commit comments

Comments
 (0)