mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-40620: Null complement a merged derived table inside a FULL JOIN
A FULL JOIN's null complement pass marks the whole opposite operand as a null row before it emits a row of the side it rescans. The walk that marks the operand treated a TABLE_LIST as either a plain table or a nest. A merged derived table or view is both, its own placeholder and the tables it was merged into, so the walk marked the placeholder and stopped. The query reads the fields of the merged tables, which still held the values of an earlier row, and those values reached the client where NULL belonged. The walk now descends into the nested tables as well as marking the placeholder. Section 22 covers the shape with the flattening of join nests turned off, which leaves the derived table in a nest of its own so the walk reaches it. Before this change the FULL JOIN returned the left side value 3 against the right side rows 3 and 4, where the UNION of a LEFT JOIN and a RIGHT JOIN returns NULL.
This commit is contained in:
@@ -6199,4 +6199,43 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
|
||||
Warnings:
|
||||
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` is not null and `test`.`t1`.`a` < 3) where 1
|
||||
drop table t1, t2;
|
||||
# ====================================================================
|
||||
# Section 22: A merged derived table inside a FULL JOIN operand
|
||||
# ====================================================================
|
||||
#
|
||||
# The null complement pass marks the whole opposite operand as a null
|
||||
# row before it emits a row of the side it rescans. A merged derived
|
||||
# table is both its own placeholder and the tables merged into it, and
|
||||
# the query reads the fields of those tables, so the walk that marks
|
||||
# the operand has to reach them. Keeping the nesting the parser built
|
||||
# leaves the derived table in a nest of its own, which is the shape
|
||||
# that reaches the walk.
|
||||
create table t1 (a int, b varchar(10));
|
||||
insert into t1 values (1,'a'), (2,'b'), (3,'c');
|
||||
create table t2 (a int, b varchar(10));
|
||||
insert into t2 values (2,'x'), (3,'y'), (4,'z');
|
||||
create table t3 (a int, b varchar(10));
|
||||
insert into t3 values (1,'w'), (2,'v'), (6,'u');
|
||||
set optimizer_switch='simplify_joins=off';
|
||||
with vals as (select a, b from t1)
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) full join t2 on vals.a = t2.a;
|
||||
a1 a3 a2
|
||||
1 1 NULL
|
||||
2 2 2
|
||||
NULL NULL 3
|
||||
NULL NULL 4
|
||||
with vals as (select a, b from t1)
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) left join t2 on vals.a = t2.a
|
||||
union
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) right join t2 on vals.a = t2.a;
|
||||
a1 a3 a2
|
||||
1 1 NULL
|
||||
2 2 2
|
||||
NULL NULL 3
|
||||
NULL NULL 4
|
||||
set optimizer_switch=default;
|
||||
drop table t1, t2, t3;
|
||||
# End of 12.3 tests
|
||||
|
||||
@@ -3826,4 +3826,39 @@ select t1.a, t2.a from t1 full join t2 on t2.a is not null and t1.a < 3;
|
||||
|
||||
drop table t1, t2;
|
||||
|
||||
--echo # ====================================================================
|
||||
--echo # Section 22: A merged derived table inside a FULL JOIN operand
|
||||
--echo # ====================================================================
|
||||
--echo #
|
||||
--echo # The null complement pass marks the whole opposite operand as a null
|
||||
--echo # row before it emits a row of the side it rescans. A merged derived
|
||||
--echo # table is both its own placeholder and the tables merged into it, and
|
||||
--echo # the query reads the fields of those tables, so the walk that marks
|
||||
--echo # the operand has to reach them. Keeping the nesting the parser built
|
||||
--echo # leaves the derived table in a nest of its own, which is the shape
|
||||
--echo # that reaches the walk.
|
||||
|
||||
create table t1 (a int, b varchar(10));
|
||||
insert into t1 values (1,'a'), (2,'b'), (3,'c');
|
||||
create table t2 (a int, b varchar(10));
|
||||
insert into t2 values (2,'x'), (3,'y'), (4,'z');
|
||||
create table t3 (a int, b varchar(10));
|
||||
insert into t3 values (1,'w'), (2,'v'), (6,'u');
|
||||
|
||||
set optimizer_switch='simplify_joins=off';
|
||||
--sorted_result
|
||||
with vals as (select a, b from t1)
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) full join t2 on vals.a = t2.a;
|
||||
--sorted_result
|
||||
with vals as (select a, b from t1)
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) left join t2 on vals.a = t2.a
|
||||
union
|
||||
select vals.a as a1, t3.a as a3, t2.a as a2
|
||||
from (vals join t3 on vals.a = t3.a) right join t2 on vals.a = t2.a;
|
||||
set optimizer_switch=default;
|
||||
|
||||
drop table t1, t2, t3;
|
||||
|
||||
--echo # End of 12.3 tests
|
||||
|
||||
@@ -26926,14 +26926,16 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
|
||||
|
||||
/*
|
||||
Recursively mark all base tables within a TABLE_LIST as null rows.
|
||||
Handles both single tables and nested joins (where table is NULL
|
||||
but nested_join contains child TABLE_LISTs).
|
||||
A plain table carries table alone and a nest carries nested_join
|
||||
alone, but a merged derived table or view carries both, its own
|
||||
placeholder and the tables it was merged into. The query reads the
|
||||
fields of those tables, so the walk cannot stop at the placeholder.
|
||||
*/
|
||||
static void mark_table_list_as_null_row(TABLE_LIST *tl)
|
||||
{
|
||||
if (tl->table)
|
||||
mark_as_null_row(tl->table);
|
||||
else if (tl->nested_join)
|
||||
if (tl->nested_join)
|
||||
{
|
||||
List_iterator<TABLE_LIST> li(tl->nested_join->join_list);
|
||||
TABLE_LIST *child;
|
||||
@@ -26948,7 +26950,7 @@ static void unmark_table_list_as_null_row(TABLE_LIST *tl)
|
||||
{
|
||||
if (tl->table)
|
||||
unmark_as_null_row(tl->table);
|
||||
else if (tl->nested_join)
|
||||
if (tl->nested_join)
|
||||
{
|
||||
List_iterator<TABLE_LIST> li(tl->nested_join->join_list);
|
||||
TABLE_LIST *child;
|
||||
|
||||
Reference in New Issue
Block a user