Skip to content

Commit e3f59c1

Browse files
MDEV-37933: Rewrite [NATURAL] FULL OUTER to LEFT, RIGHT, or INNER JOIN
Rewrite FULL OUTER JOIN queries as either LEFT, RIGHT, or INNER JOIN by checking if and how the WHERE clause rejects nulls. For example, the following two queries are equivalent because the WHERE condition rejects nulls from the left table and allows matches in the right table (or NULL from the right table) for the remaining rows: SELECT * FROM t1 FULL JOIN t2 ON t1.v = t2.v WHERE t1.v IS NOT NULL; SELECT * FROM t1 LEFT JOIN t2 ON t1.v = t2.v;
1 parent d6c2697 commit e3f59c1

File tree

9 files changed

+784
-61
lines changed

9 files changed

+784
-61
lines changed

mysql-test/main/join.result

Lines changed: 278 additions & 25 deletions
Large diffs are not rendered by default.

mysql-test/main/join.test

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,34 +2073,34 @@ explain extended select * from t1 natural full outer join t2;
20732073
select * from t1 natural full join t2;
20742074
explain extended select * from t1 natural full join t2;
20752075

2076-
create view v1 as select * from t1 full join t2 on t1.a = t2.a;
2076+
create view v1 as select t1.a as t1a, t2.a as t2a from t1 full join t2 on t1.a = t2.a;
20772077
--error ER_NOT_SUPPORTED_YET
20782078
select * from v1;
20792079
explain extended select * from v1;
20802080
drop view v1;
20812081

2082-
create view v1 as select * from t1 full outer join t2 on t1.a = t2.a;
2082+
create view v1 as select t1.a as t1a, t2.a as t2a from t1 full outer join t2 on t1.a = t2.a;
20832083
--error ER_NOT_SUPPORTED_YET
20842084
select * from v1;
20852085
explain extended select * from v1;
20862086
drop view v1;
20872087

2088-
create view v1 as select * from t1 natural full join t2;
2088+
create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full join t2;
20892089
--error ER_NOT_SUPPORTED_YET
20902090
select * from v1;
20912091
explain extended select * from v1;
20922092
drop view v1;
20932093

2094-
create view v1 as select * from t1 natural full outer join t2;
2094+
create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full outer join t2;
20952095
--error ER_NOT_SUPPORTED_YET
20962096
select * from v1;
20972097
explain extended select * from v1;
20982098
drop view v1;
20992099

2100-
--error ER_BAD_FIELD_ERROR
2100+
--error ER_NOT_SUPPORTED_YET
21012101
select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt;
21022102

2103-
--error ER_BAD_FIELD_ERROR
2103+
--error ER_NOT_SUPPORTED_YET
21042104
select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt;
21052105

21062106
--error ER_NOT_SUPPORTED_YET
@@ -2127,7 +2127,104 @@ select * from t1, t2 natural full join t3;
21272127
--error ER_NO_SUCH_TABLE
21282128
select * from t1, t2 natural full outer join t3;
21292129

2130+
--error ER_FULL_JOIN_BASE_TABLES_ONLY
2131+
select * from (select * from t1) dt natural full join (select * from t2) du;
2132+
2133+
--error ER_FULL_JOIN_BASE_TABLES_ONLY
2134+
select * from (select * from t1) dt natural full join t2;
2135+
2136+
--error ER_FULL_JOIN_BASE_TABLES_ONLY
2137+
select * from t1 natural full join (select * from t2) du;
2138+
21302139
drop table t1, t2;
2140+
2141+
--echo # Exercise FULL JOIN rewrites to LEFT, RIGHT, and INNER JOIN.
2142+
create table x (pk int auto_increment, x int, y int, primary key (pk));
2143+
create table xsq (pk int auto_increment, x int, y int, primary key (pk));
2144+
insert into x (x, y) values (-5,-5),(-4,-4),(-3,-3),(-2,-2),(-1,-1),(0,0),(1,1),(2,2),(3,3),(4,4),(5,5);
2145+
insert into xsq (x, y) values (-5,25),(-4,16),(-3,9),(-2,4),(-1,1),(0,0),(1,1),(2,4),(3,9),(4,16),(5,25);
2146+
2147+
--echo # FULL to RIGHT JOIN, these two queries should be equal:
2148+
select * from x full join xsq on x.y = xsq.y where xsq.pk is not null;
2149+
select * from x right join xsq on x.y = xsq.y;
2150+
2151+
--echo # FULL to RIGHT JOIN, these two queries should be equal:
2152+
select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1 where xsq.pk is not null;
2153+
select * from x right join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1;
2154+
2155+
--echo # FULL to INNER JOIN, these two queries should be equal:
2156+
select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1 where x.pk is not null and xsq.pk is not null;
2157+
select * from x inner join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1;
2158+
2159+
--echo # FULL to LEFT JOIN, these two queries should be equal:
2160+
select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1 where x.pk is not null;
2161+
select * from x left join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1;
2162+
2163+
--echo # FULL NATURAL to INNER JOIN, these two queries should be equal:
2164+
select * from x natural full join xsq where x.pk is not null and xsq.pk is not null;
2165+
select * from x inner join xsq on x.x = xsq.x and x.y = xsq.y;
2166+
2167+
--echo # FULL NATURAL to LEFT JOIN, these two queries should be equal:
2168+
select * from x natural full join xsq where x.pk is not null;
2169+
select * from x left join xsq on xsq.pk = x.pk and xsq.x = x.x and xsq.y = x.y;
2170+
2171+
--echo # FULL NATURAL to RIGHT JOIN
2172+
select * from x natural full join xsq where xsq.pk is not null;
2173+
select * from x right join xsq on x.pk = xsq.pk and x.x = xsq.x and x.y = xsq.y;
2174+
2175+
--echo # These two will fail because it cannot be rewritten to a LEFT, RIGHT, nor INNER JOIN.
2176+
--error ER_NOT_SUPPORTED_YET
2177+
select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1;
2178+
--error ER_NOT_SUPPORTED_YET
2179+
select * from x natural full join xsq;
2180+
2181+
drop table x, xsq;
2182+
2183+
--echo # Nested JOINs
2184+
create table one (v int);
2185+
insert into one (v) values (1);
2186+
create table two (v int);
2187+
insert into two (v) values (2);
2188+
create table three (v int);
2189+
insert into three (v) values (3);
2190+
2191+
--echo # (FULL)FULL to (INNER)INNER JOIN
2192+
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;
2193+
select * from one inner join two on one.v = two.v inner join three on two.v = three.v;
2194+
2195+
--echo # (FULL)FULL to (RIGHT)LEFT JOIN
2196+
select * from one full join two on one.v = two.v full join three on one.v = three.v where two.v is not null;
2197+
select * from one right join two on one.v = two.v left join three on one.v = three.v;
2198+
2199+
--echo # (FULL)FULL to (LEFT)LEFT JOIN
2200+
select * from one full join two on one.v = two.v full join three on one.v = three.v where one.v is not null;
2201+
select * from one left join two on two.v = one.v left join three on three.v = one.v;
2202+
2203+
--echo # (FULL)LEFT to (LEFT)LEFT JOIN
2204+
select * from one full join two on one.v = two.v left join three on two.v = three.v where one.v is not null;
2205+
select * from one left join two on one.v = two.v left join three on two.v = three.v;
2206+
2207+
--echo # (FULL)LEFT to (RIGHT)LEFT JOIN
2208+
select * from one full join two on one.v = two.v left join three on two.v = three.v where two.v is not null;
2209+
select * from one right join two on one.v = two.v left join three on three.v = two.v;
2210+
2211+
--echo # (LEFT)FULL to (LEFT)RIGHT JOIN
2212+
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;
2213+
select * from one left join two on one.v = two.v right join three on two.v = three.v;
2214+
2215+
--echo # (LEFT)FULL to (LEFT)LEFT JOIN
2216+
insert into one (v) values (2),(3);
2217+
insert into two (v) values (1);
2218+
truncate three;
2219+
insert into three (v) values (1);
2220+
select * from one;
2221+
select * from two;
2222+
select * from three;
2223+
select * from one left join two on one.v = two.v full join three on two.v = three.v where three.v = 1;
2224+
select * from three left join one on one.v = 1 left join two on two.v = 1;
2225+
2226+
drop table one, two, three;
2227+
21312228
# TODO fix PS protocol before end of FULL OUTER JOIN development
21322229
--enable_ps_protocol
21332230
--echo # End of 12.3 tests

sql/share/errmsg-utf8.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12362,3 +12362,5 @@ ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_KEY
1236212362
eng "Hint %s is ignored as conflicting/duplicated (an index hint of the same type or opposite kind has already been specified for the key)"
1236312363
ER_WARN_NO_IMPLICIT_QB_NAMES_IN_VIEW
1236412364
eng "Implicit query block names are ignored for hints specified within VIEWs"
12365+
ER_FULL_JOIN_BASE_TABLES_ONLY
12366+
eng "FULL JOIN supported for base tables only, %s is not a base table"

sql/sql_base.cc

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7998,6 +7998,23 @@ store_top_level_join_columns(THD *thd, TABLE_LIST *table_ref,
79987998
}
79997999

80008000

8001+
/*
8002+
If the tables involved participate in a FULL OUTER JOIN then don't
8003+
update their name resolution contexts at this point, or it will
8004+
break simplify_joins.
8005+
*/
8006+
static bool is_full_outer_join(Name_resolution_context *context,
8007+
TABLE_LIST *right_neighbor)
8008+
{
8009+
const uint ctx_outer_join= context->first_name_resolution_table->outer_join;
8010+
const uint rtn_outer_join=
8011+
right_neighbor->first_leaf_for_name_resolution()->outer_join;
8012+
8013+
return (ctx_outer_join & JOIN_TYPE_FULL) ||
8014+
(rtn_outer_join & JOIN_TYPE_FULL);
8015+
}
8016+
8017+
80018018
/*
80028019
Compute and store the row types of the top-most NATURAL/USING joins
80038020
in a FROM clause.
@@ -8085,8 +8102,9 @@ static bool setup_natural_join_row_types(THD *thd,
80858102
FROM clause.
80868103
*/
80878104
DBUG_ASSERT(right_neighbor);
8088-
context->first_name_resolution_table=
8089-
right_neighbor->first_leaf_for_name_resolution();
8105+
if (!is_full_outer_join(context, right_neighbor))
8106+
context->first_name_resolution_table=
8107+
right_neighbor->first_leaf_for_name_resolution();
80908108
/*
80918109
This is only to ensure that first_name_resolution_table doesn't
80928110
change on re-execution

sql/sql_list.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,34 @@ template <class T> class List_iterator :public base_list_iterator
597597
inline void remove() { base_list_iterator::remove(); }
598598
inline void after(T *a) { base_list_iterator::after(a); }
599599
inline T** ref(void) { return (T**) base_list_iterator::ref(); }
600+
601+
/*
602+
Swap the current element with the next one in the list.
603+
604+
If this iterator points to no element or to the last element, then this
605+
method does nothing and returns nullptr.
606+
607+
If this iter points to B in the following list
608+
A, B, C, D, ...
609+
then after this method returns, the list will be
610+
A, C, B, D, ...
611+
and this method returns C. This iter will point to the same location
612+
in the list after this method returns as it did before, but the element at
613+
that location will be C instead of B.
614+
615+
Other iterators pointing to the same list remain valid and will see the
616+
updated list order.
617+
*/
618+
T* swap_next()
619+
{
620+
if (!ref() || !*ref() || !peek())
621+
return nullptr;
622+
T* cur= *ref();
623+
List_iterator<T> itr= *this;
624+
T* nxt= itr++;
625+
replace(nxt);
626+
return itr.replace(cur);
627+
}
600628
};
601629

602630

sql/sql_parse.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8672,12 +8672,10 @@ bool st_select_lex::add_cross_joined_table(TABLE_LIST *left_op,
86728672

86738673
TABLE_LIST *st_select_lex::convert_right_join()
86748674
{
8675-
TABLE_LIST *tab2= join_list->pop();
8676-
TABLE_LIST *tab1= join_list->pop();
86778675
DBUG_ENTER("convert_right_join");
8678-
8679-
join_list->push_front(tab2, parent_lex->thd->mem_root);
8680-
join_list->push_front(tab1, parent_lex->thd->mem_root);
8676+
List_iterator<TABLE_LIST> li(*join_list);
8677+
li++; // points iterator at first element and returns it
8678+
TABLE_LIST* tab1= li.swap_next();
86818679
tab1->outer_join|= JOIN_TYPE_RIGHT;
86828680

86838681
DBUG_RETURN(tab1);

0 commit comments

Comments
 (0)