diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index ec51f15f168c1..e1ae218b4cc41 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3666,83 +3666,83 @@ insert into t1 (a) values (1),(2),(3); create table t2 (a int); insert into t2 (a) values (1),(2),(3); select * from t1 full join t2 on t1.a = t2.a; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from t1 full join t2 on t1.a = t2.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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) select * from t1 full outer join t2 on t1.a = t2.a; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from t1 full outer join t2 on t1.a = t2.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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) select * from t1 natural full outer join t2; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from t1 natural full outer join t2; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` select * from t1 natural full join t2; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from t1 natural full join t2; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` -create view v1 as select * from t1 full join t2 on t1.a = t2.a; +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +create view v1 as select t1.a as t1a, t2.a as t2a from t1 full join t2 on t1.a = t2.a; select * from v1; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from v1; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) drop view v1; -create view v1 as select * from t1 full outer join t2 on t1.a = t2.a; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 full outer join t2 on t1.a = t2.a; select * from v1; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from v1; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t1`.`a`, `test`.`t2`.`a`)) drop view v1; -create view v1 as select * from t1 natural full join t2; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full join t2; select * from v1; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from v1; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` natural full join `test`.`t2` drop view v1; -create view v1 as select * from t1 natural full outer join t2; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full outer join t2; select * from v1; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from v1; 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 NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` natural full join `test`.`t2` drop view v1; select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; -ERROR 42S22: Unknown column 't1.a' in 'SELECT' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt; -ERROR 42S22: Unknown column 't1.a' in 'SELECT' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY UNKNOWN NULL NULL NULL NULL 0 0.00 @@ -3753,7 +3753,7 @@ NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` union /* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1`) `dt` select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY UNKNOWN NULL NULL NULL NULL 0 0.00 @@ -3764,7 +3764,7 @@ NULL UNION RESULT ALL NULL NULL NULL NULL NULL NULL Warnings: Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` union /* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1`) `dt` with cte as (select t1.a from t1 natural full join t2) select * from cte; -ERROR 42000: This version of MariaDB doesn't yet support 'full join' +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' explain extended with cte as (select t1.a from t1 natural full join t2) select * from cte; 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 @@ -3779,5 +3779,258 @@ select * from t1, t2 natural full join t3; ERROR 42S02: Table 'test.t3' doesn't exist select * from t1, t2 natural full outer join t3; ERROR 42S02: Table 'test.t3' doesn't exist +select * from (select * from t1) dt natural full join (select * from t2) du; +ERROR HY000: FULL JOIN supported for base tables only, du is not a base table +select * from (select * from t1) dt natural full join t2; +ERROR HY000: FULL JOIN supported for base tables only, dt is not a base table +select * from t1 natural full join (select * from t2) du; +ERROR HY000: FULL JOIN supported for base tables only, du is not a base table drop table t1, t2; +# Exercise FULL JOIN rewrites to LEFT, RIGHT, and INNER JOIN. +create table x (pk int auto_increment, x int, y int, primary key (pk)); +create table xsq (pk int auto_increment, x int, y int, primary key (pk)); +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); +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); +# FULL to RIGHT JOIN, these two queries should be equal: +select * from x full join xsq on x.y = xsq.y where xsq.pk is not null; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 5 -1 1 +7 1 1 7 1 1 +10 4 4 4 -2 4 +10 4 4 8 2 4 +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +select * from x right join xsq on x.y = xsq.y; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 5 -1 1 +7 1 1 7 1 1 +10 4 4 4 -2 4 +10 4 4 8 2 4 +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +# FULL to RIGHT JOIN, these two queries should be equal: +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; +pk x y pk x y +6 0 0 6 0 0 +6 0 0 7 1 1 +7 1 1 6 0 0 +7 1 1 7 1 1 +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 4 -2 4 +NULL NULL NULL 5 -1 1 +NULL NULL NULL 8 2 4 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +select * from x right join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; +pk x y pk x y +6 0 0 6 0 0 +6 0 0 7 1 1 +7 1 1 6 0 0 +7 1 1 7 1 1 +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 4 -2 4 +NULL NULL NULL 5 -1 1 +NULL NULL NULL 8 2 4 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +# FULL to INNER JOIN, these two queries should be equal: +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; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 6 0 0 +6 0 0 7 1 1 +7 1 1 7 1 1 +select * from x inner join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 6 0 0 +6 0 0 7 1 1 +7 1 1 7 1 1 +# FULL to LEFT JOIN, these two queries should be equal: +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; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 6 0 0 +6 0 0 7 1 1 +7 1 1 7 1 1 +1 -5 -5 NULL NULL NULL +2 -4 -4 NULL NULL NULL +3 -3 -3 NULL NULL NULL +4 -2 -2 NULL NULL NULL +5 -1 -1 NULL NULL NULL +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +10 4 4 NULL NULL NULL +11 5 5 NULL NULL NULL +select * from x left join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 6 0 0 +6 0 0 7 1 1 +7 1 1 7 1 1 +1 -5 -5 NULL NULL NULL +2 -4 -4 NULL NULL NULL +3 -3 -3 NULL NULL NULL +4 -2 -2 NULL NULL NULL +5 -1 -1 NULL NULL NULL +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +10 4 4 NULL NULL NULL +11 5 5 NULL NULL NULL +# FULL NATURAL to INNER JOIN, these two queries should be equal: +select * from x natural full join xsq where x.pk is not null and xsq.pk is not null; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 7 1 1 +select * from x inner join xsq on x.x = xsq.x and x.y = xsq.y; +pk x y pk x y +6 0 0 6 0 0 +7 1 1 7 1 1 +# FULL NATURAL to LEFT JOIN, these two queries should be equal: +select * from x natural full join xsq where x.pk is not null; +pk x y pk x y +1 -5 -5 NULL NULL NULL +2 -4 -4 NULL NULL NULL +3 -3 -3 NULL NULL NULL +4 -2 -2 NULL NULL NULL +5 -1 -1 NULL NULL NULL +6 0 0 6 0 0 +7 1 1 7 1 1 +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +10 4 4 NULL NULL NULL +11 5 5 NULL NULL NULL +select * from x left join xsq on xsq.pk = x.pk and xsq.x = x.x and xsq.y = x.y; +pk x y pk x y +1 -5 -5 NULL NULL NULL +2 -4 -4 NULL NULL NULL +3 -3 -3 NULL NULL NULL +4 -2 -2 NULL NULL NULL +5 -1 -1 NULL NULL NULL +6 0 0 6 0 0 +7 1 1 7 1 1 +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +10 4 4 NULL NULL NULL +11 5 5 NULL NULL NULL +# FULL NATURAL to RIGHT JOIN +select * from x natural full join xsq where xsq.pk is not null; +pk x y pk x y +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 4 -2 4 +NULL NULL NULL 5 -1 1 +6 0 0 6 0 0 +7 1 1 7 1 1 +NULL NULL NULL 8 2 4 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +select * from x right join xsq on x.pk = xsq.pk and x.x = xsq.x and x.y = xsq.y; +pk x y pk x y +NULL NULL NULL 1 -5 25 +NULL NULL NULL 2 -4 16 +NULL NULL NULL 3 -3 9 +NULL NULL NULL 4 -2 4 +NULL NULL NULL 5 -1 1 +6 0 0 6 0 0 +7 1 1 7 1 1 +NULL NULL NULL 8 2 4 +NULL NULL NULL 9 3 9 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 5 25 +# These two will fail because it cannot be rewritten to a LEFT, RIGHT, nor INNER JOIN. +select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' +select * from x natural full join xsq; +ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' +drop table x, xsq; +# Nested JOINs +create table one (v int); +insert into one (v) values (1); +create table two (v int); +insert into two (v) values (2); +create table three (v int); +insert into three (v) values (3); +# (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 +select * from one inner join two on one.v = two.v inner join three on two.v = three.v; +v v v +# (FULL)FULL to (RIGHT)LEFT JOIN +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; +v v v +NULL 2 NULL +select * from one right join two on one.v = two.v left join three on one.v = three.v; +v v v +NULL 2 NULL +# (FULL)FULL to (LEFT)LEFT JOIN +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; +v v v +1 NULL NULL +select * from one left join two on two.v = one.v left join three on three.v = one.v; +v v v +1 NULL NULL +# (FULL)LEFT to (LEFT)LEFT JOIN +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; +v v v +1 NULL NULL +select * from one left join two on one.v = two.v left join three on two.v = three.v; +v v v +1 NULL NULL +# (FULL)LEFT to (RIGHT)LEFT JOIN +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; +v v v +NULL 2 NULL +select * from one right join two on one.v = two.v left join three on three.v = two.v; +v v v +NULL 2 NULL +# (LEFT)FULL to (LEFT)RIGHT JOIN +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 +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 +# (LEFT)FULL to (LEFT)LEFT JOIN +insert into one (v) values (2),(3); +insert into two (v) values (1); +truncate three; +insert into three (v) values (1); +select * from one; +v +1 +2 +3 +select * from two; +v +2 +1 +select * from three; +v +1 +select * from one left join two on one.v = two.v full join three on two.v = three.v where three.v = 1; +v v v +1 1 1 +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; # End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index 755653208ec87..e2a668d048a9f 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2073,34 +2073,34 @@ explain extended select * from t1 natural full outer join t2; select * from t1 natural full join t2; explain extended select * from t1 natural full join t2; -create view v1 as select * from t1 full join t2 on t1.a = t2.a; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 full join t2 on t1.a = t2.a; --error ER_NOT_SUPPORTED_YET select * from v1; explain extended select * from v1; drop view v1; -create view v1 as select * from t1 full outer join t2 on t1.a = t2.a; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 full outer join t2 on t1.a = t2.a; --error ER_NOT_SUPPORTED_YET select * from v1; explain extended select * from v1; drop view v1; -create view v1 as select * from t1 natural full join t2; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full join t2; --error ER_NOT_SUPPORTED_YET select * from v1; explain extended select * from v1; drop view v1; -create view v1 as select * from t1 natural full outer join t2; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full outer join t2; --error ER_NOT_SUPPORTED_YET select * from v1; explain extended select * from v1; drop view v1; ---error ER_BAD_FIELD_ERROR +--error ER_NOT_SUPPORTED_YET select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; ---error ER_BAD_FIELD_ERROR +--error ER_NOT_SUPPORTED_YET select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt; --error ER_NOT_SUPPORTED_YET @@ -2127,7 +2127,104 @@ select * from t1, t2 natural full join t3; --error ER_NO_SUCH_TABLE select * from t1, t2 natural full outer join t3; +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from (select * from t1) dt natural full join (select * from t2) du; + +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from (select * from t1) dt natural full join t2; + +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from t1 natural full join (select * from t2) du; + drop table t1, t2; + +--echo # Exercise FULL JOIN rewrites to LEFT, RIGHT, and INNER JOIN. +create table x (pk int auto_increment, x int, y int, primary key (pk)); +create table xsq (pk int auto_increment, x int, y int, primary key (pk)); +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); +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); + +--echo # FULL to RIGHT JOIN, these two queries should be equal: +select * from x full join xsq on x.y = xsq.y where xsq.pk is not null; +select * from x right join xsq on x.y = xsq.y; + +--echo # FULL to RIGHT JOIN, these two queries should be equal: +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; +select * from x right join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; + +--echo # FULL to INNER JOIN, these two queries should be equal: +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; +select * from x inner join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; + +--echo # FULL to LEFT JOIN, these two queries should be equal: +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; +select * from x left join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; + +--echo # FULL NATURAL to INNER JOIN, these two queries should be equal: +select * from x natural full join xsq where x.pk is not null and xsq.pk is not null; +select * from x inner join xsq on x.x = xsq.x and x.y = xsq.y; + +--echo # FULL NATURAL to LEFT JOIN, these two queries should be equal: +select * from x natural full join xsq where x.pk is not null; +select * from x left join xsq on xsq.pk = x.pk and xsq.x = x.x and xsq.y = x.y; + +--echo # FULL NATURAL to RIGHT JOIN +select * from x natural full join xsq where xsq.pk is not null; +select * from x right join xsq on x.pk = xsq.pk and x.x = xsq.x and x.y = xsq.y; + +--echo # These two will fail because it cannot be rewritten to a LEFT, RIGHT, nor INNER JOIN. +--error ER_NOT_SUPPORTED_YET +select * from x full join xsq on x.x >= 0 and x.x <= 1 and xsq.x >= 0 and xsq.x <= 1; +--error ER_NOT_SUPPORTED_YET +select * from x natural full join xsq; + +drop table x, xsq; + +--echo # Nested JOINs +create table one (v int); +insert into one (v) values (1); +create table two (v int); +insert into two (v) values (2); +create table three (v int); +insert into three (v) values (3); + +--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; +select * from one inner join two on one.v = two.v inner join three on two.v = three.v; + +--echo # (FULL)FULL to (RIGHT)LEFT JOIN +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; +select * from one right join two on one.v = two.v left join three on one.v = three.v; + +--echo # (FULL)FULL to (LEFT)LEFT JOIN +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; +select * from one left join two on two.v = one.v left join three on three.v = one.v; + +--echo # (FULL)LEFT to (LEFT)LEFT JOIN +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; +select * from one left join two on one.v = two.v left join three on two.v = three.v; + +--echo # (FULL)LEFT to (RIGHT)LEFT JOIN +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; +select * from one right join two on one.v = two.v left join three on three.v = two.v; + +--echo # (LEFT)FULL to (LEFT)RIGHT JOIN +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; +select * from one left join two on one.v = two.v right join three on two.v = three.v; + +--echo # (LEFT)FULL to (LEFT)LEFT JOIN +insert into one (v) values (2),(3); +insert into two (v) values (1); +truncate three; +insert into three (v) values (1); +select * from one; +select * from two; +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; + # TODO fix PS protocol before end of FULL OUTER JOIN development --enable_ps_protocol --echo # End of 12.3 tests diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 28cefd3dbf299..5e1ff17cdfbeb 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12362,3 +12362,5 @@ ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_KEY 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)" ER_WARN_NO_IMPLICIT_QB_NAMES_IN_VIEW eng "Implicit query block names are ignored for hints specified within VIEWs" +ER_FULL_JOIN_BASE_TABLES_ONLY + eng "FULL JOIN supported for base tables only, %s is not a base table" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index bd09730fd75b1..03c73c6a95d6e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7998,6 +7998,23 @@ store_top_level_join_columns(THD *thd, TABLE_LIST *table_ref, } +/* + If the tables involved participate in a FULL OUTER JOIN then don't + update their name resolution contexts at this point, or it will + break simplify_joins. + */ +static bool is_full_outer_join(Name_resolution_context *context, + TABLE_LIST *right_neighbor) +{ + const uint ctx_outer_join= context->first_name_resolution_table->outer_join; + const uint rtn_outer_join= + right_neighbor->first_leaf_for_name_resolution()->outer_join; + + return (ctx_outer_join & JOIN_TYPE_FULL) || + (rtn_outer_join & JOIN_TYPE_FULL); +} + + /* Compute and store the row types of the top-most NATURAL/USING joins in a FROM clause. @@ -8085,8 +8102,9 @@ static bool setup_natural_join_row_types(THD *thd, FROM clause. */ DBUG_ASSERT(right_neighbor); - context->first_name_resolution_table= - right_neighbor->first_leaf_for_name_resolution(); + if (!is_full_outer_join(context, right_neighbor)) + context->first_name_resolution_table= + right_neighbor->first_leaf_for_name_resolution(); /* This is only to ensure that first_name_resolution_table doesn't change on re-execution diff --git a/sql/sql_list.h b/sql/sql_list.h index 7cfbc112f88b0..8cf7dae389c3e 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -597,6 +597,34 @@ template class List_iterator :public base_list_iterator inline void remove() { base_list_iterator::remove(); } inline void after(T *a) { base_list_iterator::after(a); } inline T** ref(void) { return (T**) base_list_iterator::ref(); } + + /* + Swap the current element with the next one in the list. + + If this iterator points to no element or to the last element, then this + method does nothing and returns nullptr. + + If this iter points to B in the following list + A, B, C, D, ... + then after this method returns, the list will be + A, C, B, D, ... + and this method returns C. This iter will point to the same location + in the list after this method returns as it did before, but the element at + that location will be C instead of B. + + Other iterators pointing to the same list remain valid and will see the + updated list order. + */ + T* swap_next() + { + if (!ref() || !*ref() || !peek()) + return nullptr; + T* cur= *ref(); + List_iterator itr= *this; + T* nxt= itr++; + replace(nxt); + return itr.replace(cur); + } }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 517fcdaba0299..89225a07e762d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8672,12 +8672,10 @@ bool st_select_lex::add_cross_joined_table(TABLE_LIST *left_op, TABLE_LIST *st_select_lex::convert_right_join() { - TABLE_LIST *tab2= join_list->pop(); - TABLE_LIST *tab1= join_list->pop(); DBUG_ENTER("convert_right_join"); - - join_list->push_front(tab2, parent_lex->thd->mem_root); - join_list->push_front(tab1, parent_lex->thd->mem_root); + List_iterator li(*join_list); + li++; // points iterator at first element and returns it + TABLE_LIST* tab1= li.swap_next(); tab1->outer_join|= JOIN_TYPE_RIGHT; DBUG_RETURN(tab1); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 051c04e3f65f0..0bc147c0f4ac4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1894,18 +1894,6 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, goto err; prepared= true; - /* - This check gates FULL JOIN functionality while it is under - development. This check will be removed once FULL JOIN - has been completed and it allows some aspects of FULL JOIN - (see below) while exluding others, driven by whatever has - been implemented up to this point. - */ - if (thd->lex->has_full_outer_join && // FULL JOIN not yet supported... - !thd->lex->is_view_context_analysis() && // ...but allow VIEW creation... - !thd->lex->describe) // ...and limited EXPLAIN support during development - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "full join"); - DBUG_RETURN(0); // All OK err: @@ -19743,6 +19731,65 @@ propagate_cond_constants(THD *thd, I_List *save_list, } } + +/** + rewrite_full_outer_joins function prototype. + + Required to support recursive calls from this function to simplify_joins, the + entry point for both FULL OUTER and other JOIN rewrites. + + Complete function documentation at top of implementation, below. +*/ + +static COND *rewrite_full_outer_joins(JOIN *join, + COND *conds, + bool top, + bool in_sj, + TABLE_LIST **table_ptr, + List_iterator *li_ptr, + table_map *used_tables_ptr, + table_map *not_null_tables_ptr); + + +/** + Convenience function to wrap a recursive call to simplify_joins in the case + of a nested join, which requires updates to the NESTED_JOIN structure. + + @param join reference to the query info + @param table currently visited TABLE_LIST entry in the join_list + @param conds conditions to add on expressions for converted joins + @param top true <=> conds is the where condition + @param in_sj TRUE <=> processing semi-join nest's children + @parma used_tables_ptr IN/OUT parameter for the used_tables value + @parma not_null_tables_ptr IN/OUT parameter for the used_tables value + + @return the new condition on success, nullptr otherwise +*/ + +static COND *simplify_nested_join(JOIN *join, TABLE_LIST *table, + COND *conds, bool top, bool in_sj, + table_map *used_tables_ptr, + table_map *not_null_tables_ptr) +{ + DBUG_ASSERT(used_tables_ptr); + DBUG_ASSERT(not_null_tables_ptr); + table_map &used_tables= *used_tables_ptr; + table_map ¬_null_tables= *not_null_tables_ptr; + + NESTED_JOIN *nested_join= table->nested_join; + DBUG_ASSERT(nested_join); + nested_join->used_tables= (table_map) 0; + nested_join->not_null_tables=(table_map) 0; + conds= simplify_joins(join, &nested_join->join_list, conds, top, + in_sj || table->sj_on_expr); + used_tables= nested_join->used_tables; + not_null_tables= nested_join->not_null_tables; + /* The following two might become unequal after table elimination: */ + nested_join->n_tables= nested_join->join_list.elements; + return conds; +} + + /** Simplify joins replacing outer joins by inner joins whenever it's possible. @@ -19881,6 +19928,15 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, */ while ((table= li++)) { + // We only support FULL JOIN on base tables. + if (table->outer_join & JOIN_TYPE_FULL && + !table->is_non_derived()) + { + my_error(ER_FULL_JOIN_BASE_TABLES_ONLY, MYF(0), + table->alias.str); + DBUG_RETURN(nullptr); + } + table_map used_tables; table_map not_null_tables= (table_map) 0; @@ -19912,14 +19968,8 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, table->prep_on_expr= expr->copy_andor_structure(join->thd); } } - nested_join->used_tables= (table_map) 0; - nested_join->not_null_tables=(table_map) 0; - conds= simplify_joins(join, &nested_join->join_list, conds, top, - in_sj || table->sj_on_expr); - used_tables= nested_join->used_tables; - not_null_tables= nested_join->not_null_tables; - /* The following two might become unequal after table elimination: */ - nested_join->n_tables= nested_join->join_list.elements; + conds= simplify_nested_join(join, table, conds, top, in_sj, + &used_tables, ¬_null_tables); } else { @@ -19929,7 +19979,15 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, if (conds) not_null_tables= conds->not_null_tables(); } - + + /* + Attempt to rewrite any FULL JOINs as LEFT or RIGHT JOINs. Any subsequent + JOINs that could be further rewritten to INNER JOINs are done below. + */ + conds= rewrite_full_outer_joins(join, conds, top, in_sj, &table, + &li, &used_tables, + ¬_null_tables); + if (table->embedding) { table->embedding->nested_join->used_tables|= used_tables; @@ -20142,6 +20200,272 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, } +/** + Rewrite a FULL JOIN to a LEFT JOIN by mutating the + left and right table state to make them appear as though + the user wrote the FULL JOIN as a LEFT JOIN originally. + + @param left_table table t1 in t1 FULL JOIN t2 + @param right_table table t2 in t1 FULL JOIN t2 +*/ + +static void rewrite_full_to_left(TABLE_LIST *left_table, + TABLE_LIST *right_table) +{ + // Grammar does not mark the left table at all + left_table->outer_join= 0; + + /* + Clear FULL JOIN flag and do as the grammar does by marking + the right table as JOIN_TYPE_LEFT. + */ + right_table->outer_join= JOIN_TYPE_LEFT; + + /* + The right table must have an ON clause. NATURAL JOINs get + this not from the grammar but they're built before simplify_joins + is called. + */ + DBUG_ASSERT(right_table->on_expr); + + // Only the right table in a LEFT JOIN has the naming context in the grammar + left_table->on_context= nullptr; + + // The grammar 'search_condition: ' rule marks this. + if (!(right_table->outer_join & JOIN_TYPE_NATURAL)) + right_table->on_expr->base_flags|= item_base_t::IS_COND; +} + + +/** + Rewrite a FULL JOIN to a RIGHT JOIN by mutating the + left and right table state to make them appear as though + the user wrote the FULL JOIN as a RIGHT JOIN originally. + + It's important to keep in mind that this function does its + work updating the tables to prepare them to be swapped in + the join order. Had the user written the query as a RIGHT + JOIN, it would've then been converted to a LEFT JOIN by + convert_right_join. The caller will swap them in the join + list, so we prepare them in place, then once they're swapped + they will have the correct respective state. + + Consequently, in this method, we change the right_table with + the understanding that it will swap places with the left_table + very shortly (similarly with respect to the right_table). + + @param left_table table t1 in t1 FULL JOIN t2 + @param right_table table t2 in t1 FULL JOIN t2 +*/ + +static void rewrite_full_to_right(TABLE_LIST *left_table, + TABLE_LIST *right_table) +{ + // Grammar does not mark the right table at all. + right_table->outer_join= 0; + + /* + Clear FULL JOIN flag and do as convert_right_join does which + has the effect of marking the left table as JOIN_TYPE_RIGHT. + */ + left_table->outer_join= JOIN_TYPE_RIGHT; + + /* + The right table must have an ON clause. NATURAL JOINs get + this not from the grammar but they're built before simplify_joins + is called. + + The ON clause is moved from the right table to the left one + because, again, the tables will be swapped in the join list + to imitate the convert_right_join operation that would've been + done had the user written this query as a RIGHT JOIN instead + of a FULL JOIN. + */ + DBUG_ASSERT(right_table->on_expr); + DBUG_ASSERT(left_table->on_expr == nullptr); + left_table->on_expr= right_table->on_expr; + right_table->on_expr= nullptr; + + /* + Prepare the right table to become the left table by + clearing its context. The left table retains the context + set by the grammar. + */ + right_table->on_context= nullptr; +} + + +/** + Attempt to rewrite [NATURAL] FULL JOIN to LEFT, RIGHT, or INNER JOIN, + depending on the WHERE clause and whether it rejects NULLs. For example, + the following queries are equivalent: + + 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; + + The rewritten query, be it a LEFT or RIGHT JOIN, may yet again be + rewritten to an INNER JOIN if the WHERE clause permits. + + These parameters are the same as in simplify_joins: + @param join reference to the query info + @param join_list list representation of the join to be converted + @param conds conditions to add on expressions for converted joins + @param top true <=> conds is the where condition + @param in_sj TRUE <=> processing semi-join nest's children + + The following parameters are IN/OUT parameters and are mutated by + this function: + @param table_ptr the current TABLE_LIST from the join list + @param li_ptr the iterator into the join list + @param used_tables_ptr used_tables from simplify_joins + @param not_null_tables_ptr not_null_tables from simplify_joins + + @return + - The new condition, if success + - nullptr, otherwise +*/ + +static COND *rewrite_full_outer_joins(JOIN *join, + COND *conds, + bool top, + bool in_sj, + TABLE_LIST **table_ptr, + List_iterator *li_ptr, + table_map *used_tables_ptr, + table_map *not_null_tables_ptr) +{ + DBUG_ASSERT(table_ptr); + DBUG_ASSERT(*table_ptr); + DBUG_ASSERT(li_ptr); + DBUG_ASSERT(used_tables_ptr); + DBUG_ASSERT(not_null_tables_ptr); + TABLE_LIST *right_table= *table_ptr; + List_iterator &li= *li_ptr; + table_map &used_tables= *used_tables_ptr; + table_map ¬_null_tables= *not_null_tables_ptr; + + // There's no FULL OUTER JOIN to attempt to rewrite, so do nothing. + if (!(right_table->outer_join & JOIN_TYPE_FULL)) + return conds; + + /* + The join_list enumerates the tables from t_n, ..., t_0 so we always + 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). + */ + 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; + } + + /* + Must always see the right table before the left. Down below, we deal + with the left table at the same time as the right, so we'll never get + to this point with a single table remaining in the join_list. If + there's a right table remaining then there will be a left one, too. + */ + DBUG_ASSERT(right_table->outer_join & JOIN_TYPE_RIGHT); + + + /* + If the left table is a nested join, then recursively rewrite any + FULL JOINs within it. + */ + TABLE_LIST *left_table= li.peek(); + table_map nested_used_tables= 0; + table_map nested_not_null_tables= 0; + DBUG_ASSERT(left_table->outer_join & JOIN_TYPE_FULL); + DBUG_ASSERT(left_table->outer_join & JOIN_TYPE_LEFT); + const bool left_has_nested= left_table->nested_join; + if (left_has_nested) + { + conds= simplify_nested_join(join, left_table, conds, top, in_sj, + &nested_used_tables, &nested_not_null_tables); + } + + /* + If the right hand table is not null under the WHERE clause then we can + rewrite it as a RIGHT JOIN, mutating the data structures to make it + appear as though the user wrote the query as a RIGHT JOIN originally. + */ + if (used_tables & not_null_tables) + { + /* + RIGHT JOINs don't actually exist in MariaDB! This will do what + the grammar and convert_right_join together do when given a RIGHT JOIN. + */ + rewrite_full_to_right(left_table, right_table); + + // This will be reflected to the caller, too. + used_tables = left_has_nested ? nested_used_tables + : left_table->get_map(); + + /* + Swap myself with the left as though we did convert_right_join(). + Then we will have effectively done the following transformation: + FULL -> RIGHT -> LEFT. + RIGHT JOINs don't actually exist in MariaDB! + */ + *table_ptr= li.swap_next(); + join->thd->lex->has_full_outer_join= false; + } + else + { + /* + Peek at the left table and see if it rejects nulls and if so, rewrite. + We peek instead of advancing the iterator outright because the outer + loop in simplify_joins will advance the iterator. We're merely mutating + the datastructures at this point to make the FULL JOIN look like it was + written as a LEFT JOIN by the user (WHERE condition permitting). + */ + table_map peeked_map= 0; + if (left_has_nested) + { + // The left table was a nested join, so peek at that map. + peeked_map= nested_used_tables; + not_null_tables= nested_not_null_tables; + } + else + { + /* + The left table was not a nested join, so peek at its map. + We can't just set peeked_map to left_table->get_map() in + all cases because, in the case of a nested_join, the underlying + TABLE_LIST::table member (from which the map is derived) + is nullptr. + */ + peeked_map= left_table->get_map(); + } + + /* + If the left table, be it a nested join or not, rejects nulls for + the WHERE condition, then rewrite. + */ + if (peeked_map & not_null_tables) + { + rewrite_full_to_left(left_table, right_table); + join->thd->lex->has_full_outer_join= false; + } + // else the FULL JOIN cannot be rewritten, pass it along. + } + + return conds; +} + + /** Assign each nested join structure a bit in nested_join_map. diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 17120e3ab76d5..24fd9b5510e5e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12389,6 +12389,7 @@ join_table: { add_join_on(thd, $5, $8); $1->on_context= Lex->pop_context(); + $5->on_context= $1->on_context; Select->parsing_place= NO_MATTER; $$= $1; Lex->has_full_outer_join= true; diff --git a/sql/table.cc b/sql/table.cc index ae25eb396d04e..b2f66f21168e6 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -6854,7 +6854,8 @@ TABLE_LIST *TABLE_LIST::first_leaf_for_name_resolution() already at the front of the list. Otherwise the first operand is in the end of the list of join operands. */ - if (!(cur_table_ref->outer_join & JOIN_TYPE_RIGHT)) + if (!(cur_table_ref->outer_join & JOIN_TYPE_RIGHT) || + (cur_table_ref->outer_join & JOIN_TYPE_FULL)) { TABLE_LIST *next; while ((next= it++)) @@ -6909,7 +6910,8 @@ TABLE_LIST *TABLE_LIST::last_leaf_for_name_resolution() 'join_list' are in reverse order, thus the last operand is in the end of the list. */ - if ((cur_table_ref->outer_join & JOIN_TYPE_RIGHT)) + if ((cur_table_ref->outer_join & JOIN_TYPE_RIGHT) && + !(cur_table_ref->outer_join & JOIN_TYPE_FULL)) { List_iterator_fast it(cur_nested_join->join_list); TABLE_LIST *next;