From 6708b118b858c5c09e0964bb93a7e6eaf2f08ef8 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Fri, 30 Jan 2026 13:41:05 -0500 Subject: [PATCH 1/9] Remove unnecessary and unused 'top' parameter from simplify_joins. --- sql/sql_select.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index dbd3283c45c5c..5f2c3714ba44f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -201,7 +201,7 @@ static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, void *table_join_idx, bool do_substitution); static COND *simplify_joins(JOIN *join, List *join_list, - COND *conds, bool top, bool in_sj); + COND *conds, bool in_sj); static bool check_interleaving_with_nj(JOIN_TAB *next); static void restore_prev_nj_state(JOIN_TAB *last); static uint reset_nj_counters(JOIN *join, List *join_list); @@ -2359,7 +2359,7 @@ JOIN::optimize_inner() sel->first_cond_optimization= 0; /* Convert all outer joins to inner joins if possible */ - conds= simplify_joins(this, join_list, conds, TRUE, FALSE); + conds= simplify_joins(this, join_list, conds, FALSE); add_table_function_dependencies(join_list, table_map(-1), &error); @@ -19896,7 +19896,6 @@ propagate_cond_constants(THD *thd, I_List *save_list, @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 @return - The new condition, if success @@ -19904,8 +19903,7 @@ propagate_cond_constants(THD *thd, I_List *save_list, */ static COND * -simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, - bool in_sj) +simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) { TABLE_LIST *table; NESTED_JOIN *nested_join; @@ -19941,7 +19939,7 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, the corresponding on expression is added to E. */ expr= simplify_joins(join, &nested_join->join_list, - expr, FALSE, in_sj || table->sj_on_expr); + expr, in_sj || table->sj_on_expr); if (!table->prep_on_expr || expr != table->on_expr) { @@ -19953,7 +19951,7 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top, } 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, + conds= simplify_joins(join, &nested_join->join_list, conds, in_sj || table->sj_on_expr); used_tables= nested_join->used_tables; not_null_tables= nested_join->not_null_tables; From e4fce72fd99095f87269b889545220ee1c009f17 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 29 Oct 2025 13:47:05 -0400 Subject: [PATCH 2/9] MDEV-37932: Parser support FULL OUTER JOIN syntax Syntax support for FULL JOIN, FULL OUTER JOIN, NATURAL FULL JOIN, and NATURAL FULL OUTER JOIN in the parser. While we accept full join syntax, such joins are not yet supported. Queries specifying any of the above joins will fail with ER_NOT_SUPPORTED_YET. --- mysql-test/main/join.result | 25 +++++++++++++ mysql-test/main/join.test | 27 ++++++++++++++ sql/sql_lex.cc | 1 + sql/sql_lex.h | 3 ++ sql/sql_parse.cc | 5 +++ sql/sql_yacc.yy | 70 ++++++++++++++++++++++++++++++++++--- sql/table.h | 8 +++-- 7 files changed, 132 insertions(+), 7 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index d9dbf80642b34..40a744398e35e 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3657,3 +3657,28 @@ DROP TABLE t1,t2,t3; # # End of 11.0 tests # +# +# MDEV-37932: FULL OUTER JOIN: Make the parser support FULL OUTER JOIN +# syntax +# +create table t1 (a int); +insert into t1 (a) values (1),(2),(3); +create table t2 (a int); +insert into t2 (a) values (1),(2),(3); +# This test only verifies syntax acceptance. +select * from t1 full join t2 on t1.a = t2.a; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +select * from t1 full outer join t2 on t1.a = t2.a; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +select * from t1 natural full outer join t2; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +select * from t1 natural full join t2; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +create view v1 as select * from t1 full outer join t2 on t1.a = t2.a; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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' +drop table t1, t2; +# End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index efdbf6724d963..943849f0fd64e 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2046,3 +2046,30 @@ DROP TABLE t1,t2,t3; --echo # --echo # End of 11.0 tests --echo # + +--echo # +--echo # MDEV-37932: FULL OUTER JOIN: Make the parser support FULL OUTER JOIN +--echo # syntax +--echo # +create table t1 (a int); +insert into t1 (a) values (1),(2),(3); +create table t2 (a int); +insert into t2 (a) values (1),(2),(3); +--echo # This test only verifies syntax acceptance. +--error ER_NOT_SUPPORTED_YET +select * from t1 full join t2 on t1.a = t2.a; +--error ER_NOT_SUPPORTED_YET +select * from t1 full outer join t2 on t1.a = t2.a; +--error ER_NOT_SUPPORTED_YET +select * from t1 natural full outer join t2; +--error ER_NOT_SUPPORTED_YET +select * from t1 natural full join t2; +--error ER_NOT_SUPPORTED_YET +create view v1 as select * from t1 full outer join t2 on t1.a = t2.a; +--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 +with cte as (select t1.a from t1 natural full join t2) select * from cte; +drop table t1, t2; + +--echo # End of 12.3 tests diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 2543eaf6c9a2a..e5c1a46277a78 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1346,6 +1346,7 @@ void LEX::start(THD *thd_arg) memset(&trg_chistics, 0, sizeof(trg_chistics)); selects_for_hint_resolution.empty(); + full_join_count= 0; DBUG_VOID_RETURN; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 69625c3c4019a..05f1c1f5fb184 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3526,6 +3526,9 @@ struct LEX: public Query_tables_list uint8 context_analysis_only; uint8 lex_options; // see OPTION_LEX_* + /* Zero by default, this counts the number of FULL JOINs in the query. */ + uint16 full_join_count; + Alter_info alter_info; Lex_prepared_stmt prepared_stmt; /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b0affea6e252b..135245ff0b8a3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -10360,6 +10360,11 @@ bool parse_sql(THD *thd, Parser_state *parser_state, bool mysql_parse_status= thd->variables.sql_mode & MODE_ORACLE ? ORAparse(thd) : MYSQLparse(thd); + /* While we accept full join syntax, such joins are not yet supported. */ + mysql_parse_status|= (thd->lex->full_join_count > 0); + if (thd->lex->full_join_count) + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "full join"); + if (mysql_parse_status) /* Restore the original LEX if it was replaced when parsing diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c6c46ee063efd..76cacfb4c5bd4 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1212,8 +1212,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token ST_COLLECT_SYM /* A dummy token to force the priority of table_ref production in a join. */ %left CONDITIONLESS_JOIN -%left JOIN_SYM INNER_SYM STRAIGHT_JOIN CROSS LEFT RIGHT ON_SYM USING - +%left JOIN_SYM INNER_SYM STRAIGHT_JOIN CROSS LEFT RIGHT ON_SYM USING FULL + %left SET_VAR %left OR_SYM OR2_SYM %left XOR @@ -12572,8 +12572,69 @@ join_table: if (unlikely(!($$= lex->current_select->convert_right_join()))) MYSQL_YYABORT; } - ; + /* FULL OUTER JOIN variants */ + | table_ref FULL opt_outer JOIN_SYM table_ref + ON + { + MYSQL_YYABORT_UNLESS($1 && $5); + + Select->add_joined_table($1); + $1->outer_join|= (JOIN_TYPE_LEFT | + JOIN_TYPE_FULL); + + Select->add_joined_table($5); + $5->outer_join|= (JOIN_TYPE_RIGHT | + JOIN_TYPE_FULL); + + /* Change the current name resolution context to a local context. */ + if (unlikely(push_new_name_resolution_context(thd, $1, $5))) + MYSQL_YYABORT; + Select->parsing_place= IN_ON; + } + expr + { + 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->full_join_count; + } + | table_ref FULL opt_outer JOIN_SYM table_factor + { + MYSQL_YYABORT_UNLESS($1 && $5); + Select->add_joined_table($1); + $1->outer_join|= (JOIN_TYPE_LEFT | + JOIN_TYPE_FULL); + + Select->add_joined_table($5); + $5->outer_join|= (JOIN_TYPE_RIGHT | + JOIN_TYPE_FULL); + } + USING '(' using_list ')' + { + add_join_natural($1,$5,$9,Select); + ++Lex->full_join_count; + } + | table_ref NATURAL FULL opt_outer JOIN_SYM table_factor + { + MYSQL_YYABORT_UNLESS($1 && $6); + + Select->add_joined_table($1); + $1->outer_join|= (JOIN_TYPE_LEFT | + JOIN_TYPE_FULL | + JOIN_TYPE_NATURAL); + + Select->add_joined_table($6); + $6->outer_join|= (JOIN_TYPE_RIGHT | + JOIN_TYPE_FULL | + JOIN_TYPE_NATURAL); + + add_join_natural($6,$1,NULL,Select); + ++Lex->full_join_count; + } + ; inner_join: /* $$ set if using STRAIGHT_JOIN, false otherwise */ JOIN_SYM { $$ = 0; } @@ -16916,7 +16977,6 @@ keyword_func_sp_var_and_label: | FILE_SYM | FIRST_SYM | FOUND_SYM - | FULL | GENERAL | GENERATED_SYM | GRANTS @@ -17249,6 +17309,7 @@ reserved_keyword_udt_not_param_type: | FIRST_VALUE_SYM | FOREIGN | FROM + | FULL | FULLTEXT_SYM | GOTO_ORACLE_SYM | GRANT @@ -17996,6 +18057,7 @@ set_expr_or_default: set_expr_misc: ON { $$= new (thd->mem_root) Item_string_sys(thd, "ON", 2); } | ALL { $$= new (thd->mem_root) Item_string_sys(thd, "ALL", 3); } + | FULL { $$= new (thd->mem_root) Item_string_sys(thd, "FULL", 4); } | BINARY { $$= new (thd->mem_root) Item_string_sys(thd, "binary", 6); } ; diff --git a/sql/table.h b/sql/table.h index 2ff473940efb3..d96fde536dd35 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2247,9 +2247,11 @@ class IS_table_read_plan; #define VIEW_ALGORITHM_MERGE_FRM 1U #define VIEW_ALGORITHM_TMPTABLE_FRM 2U -#define JOIN_TYPE_LEFT 1U -#define JOIN_TYPE_RIGHT 2U -#define JOIN_TYPE_OUTER 4U /* Marker that this is an outer join */ +#define JOIN_TYPE_LEFT 1U +#define JOIN_TYPE_RIGHT 2U +#define JOIN_TYPE_FULL 4U +#define JOIN_TYPE_OUTER 8U /* Marker that this is an outer join */ +#define JOIN_TYPE_NATURAL 16U /* view WITH CHECK OPTION parameter options */ #define VIEW_CHECK_NONE 0 From c218056db6f8eab33890aa966467986182a1bff1 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Thu, 30 Oct 2025 14:43:53 -0400 Subject: [PATCH 3/9] MDEV-37995: FULL OUTER JOIN name resolution Allow FULL OUTER JOIN queries to proceed through name resolution. Permits limited EXPLAIN EXTENDED support so tests can prove that the JOIN_TYPE_* table markings are reflected when the query is echoed back by the server. This happens in at least two places: via a Warning message during EXPLAIN EXTENDED and during VIEW .frm file creation. While the query plan output is mostly meaningless at this point, this limited EXPLAIN support improves the SELECT_LEX print function for the new JOIN types. TODO: fix PS protocol before end of FULL OUTER JOIN development --- mysql-test/main/join.result | 101 ++++++++++++++++++++++++++++++++++++ mysql-test/main/join.test | 61 +++++++++++++++++++++- sql/sql_parse.cc | 5 -- sql/sql_select.cc | 40 ++++++++++++-- sql/sql_view.cc | 1 + 5 files changed, 197 insertions(+), 11 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index 40a744398e35e..0f9d45b49cd01 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3666,19 +3666,120 @@ insert into t1 (a) values (1),(2),(3); create table t2 (a int); insert into t2 (a) values (1),(2),(3); # This test only verifies syntax acceptance. +# TODO fix PS protocol before end of FULL OUTER JOIN development select * from t1 full join t2 on t1.a = t2.a; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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`)) select * from t1 full outer join t2 on t1.a = t2.a; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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`)) select * from t1 natural full outer join t2; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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` select * from t1 natural full join t2; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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; +select * from v1; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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`)) +drop view v1; create view v1 as select * 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' +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`)) +drop view v1; +create view v1 as select * from t1 natural full join t2; +select * from v1; +ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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` +drop view v1; +create view v1 as select * from t1 natural full outer join t2; +select * from v1; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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` +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' 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' +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' +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 +2 DERIVED t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +2 DERIVED t2 UNKNOWN NULL NULL NULL NULL 0 0.00 +3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +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' +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 +2 DERIVED t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +2 DERIVED t2 UNKNOWN NULL NULL NULL NULL 0 0.00 +3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +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' +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 +1 SIMPLE t2 UNKNOWN NULL NULL NULL NULL 0 0.00 +Warnings: +Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2`)select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; +ERROR 42S02: Table 'test.t3' doesn't exist +select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; +ERROR 42S02: Table 'test.t3' doesn't exist +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 drop table t1, t2; # End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index 943849f0fd64e..6a77266dc8a50 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2056,20 +2056,79 @@ insert into t1 (a) values (1),(2),(3); create table t2 (a int); insert into t2 (a) values (1),(2),(3); --echo # This test only verifies syntax acceptance. +--echo # TODO fix PS protocol before end of FULL OUTER JOIN development +--disable_ps_protocol --error ER_NOT_SUPPORTED_YET select * from t1 full join t2 on t1.a = t2.a; +explain extended select * from t1 full join t2 on t1.a = t2.a; + --error ER_NOT_SUPPORTED_YET select * from t1 full outer join t2 on t1.a = t2.a; +explain extended select * from t1 full outer join t2 on t1.a = t2.a; + --error ER_NOT_SUPPORTED_YET select * from t1 natural full outer join t2; +explain extended select * from t1 natural full outer join t2; + --error ER_NOT_SUPPORTED_YET 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; --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; --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; +--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; +--error ER_NOT_SUPPORTED_YET +select * from v1; +explain extended select * from v1; +drop view v1; + +--error ER_BAD_FIELD_ERROR +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 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 +select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; +explain extended select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; + +--error ER_NOT_SUPPORTED_YET +select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; +explain extended select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; + --error ER_NOT_SUPPORTED_YET with cte as (select t1.a from t1 natural full join t2) select * from cte; -drop table t1, t2; +explain extended with cte as (select t1.a from t1 natural full join t2) select * from cte; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; +--error ER_NO_SUCH_TABLE +select * from t1, t2 natural full join t3; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 natural full outer join t3; + +drop table t1, t2; +# TODO fix PS protocol before end of FULL OUTER JOIN development +--enable_ps_protocol --echo # End of 12.3 tests diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 135245ff0b8a3..b0affea6e252b 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -10360,11 +10360,6 @@ bool parse_sql(THD *thd, Parser_state *parser_state, bool mysql_parse_status= thd->variables.sql_mode & MODE_ORACLE ? ORAparse(thd) : MYSQLparse(thd); - /* While we accept full join syntax, such joins are not yet supported. */ - mysql_parse_status|= (thd->lex->full_join_count > 0); - if (thd->lex->full_join_count) - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "full join"); - if (mysql_parse_status) /* Restore the original LEX if it was replaced when parsing diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5f2c3714ba44f..4c36fc84fe652 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1886,6 +1886,18 @@ 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->full_join_count > 0) && // 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: @@ -2784,8 +2796,15 @@ JOIN::optimize_inner() with_two_phase_optimization= check_two_phase_optimization(thd); if (with_two_phase_optimization) optimization_state= JOIN::OPTIMIZATION_PHASE_1_DONE; - else + else if (thd->lex->full_join_count == 0) { + /* + Only during the FULL JOIN development cycle, disable second stage + optimization for FULL JOIN queries until the implementation is + mature enough to correctly execute the queries. But for now + this allows for some EXPLAIN EXTENDED support. + */ + if (optimize_stage2()) DBUG_RETURN(1); } @@ -31951,8 +31970,13 @@ static void print_table_array(THD *thd, continue; } - /* JOIN_TYPE_OUTER is just a marker unrelated to real join */ - if (curr->outer_join & (JOIN_TYPE_LEFT|JOIN_TYPE_RIGHT)) + if (curr->outer_join & JOIN_TYPE_FULL) + { + if (curr->outer_join & JOIN_TYPE_NATURAL) + str->append(STRING_WITH_LEN(" natural")); + str->append(STRING_WITH_LEN(" full join ")); + } + else if (curr->outer_join & (JOIN_TYPE_LEFT|JOIN_TYPE_RIGHT)) { /* MySQL converts right to left joins */ str->append(STRING_WITH_LEN(" left join ")); @@ -31963,9 +31987,15 @@ static void print_table_array(THD *thd, str->append(STRING_WITH_LEN(" semi join ")); else str->append(STRING_WITH_LEN(" join ")); - + curr->print(thd, eliminated_tables, str, query_type); - if (curr->on_expr) + /* + NATURAL JOINs don't expose explicit join columns, so don't + print them as they're considered invalid syntax (this is + important for VIEWs as when VIEWs are loaded, their SQL + syntax is parsed again and must be valid). + */ + if (curr->on_expr && !(curr->outer_join & JOIN_TYPE_NATURAL)) { str->append(STRING_WITH_LEN(" on(")); curr->on_expr->print(str, query_type); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 7a32a1b1e5b48..b94f6d3bc4608 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1915,6 +1915,7 @@ bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *view_table_alias, DBUG_ASSERT(view_query_lex == thd->lex); thd->lex= parent_query_lex; // Needed for prepare_security result= !view_table_alias->prelocking_placeholder && view_table_alias->prepare_security(thd); + parent_query_lex->full_join_count+= view_query_lex->full_join_count; lex_end(view_query_lex); end: From d83ded47615f6a9dd5a722893d58dc5d7c08805b Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 17 Nov 2025 15:48:42 -0500 Subject: [PATCH 4/9] 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; SELECT * FROM t1 FULL JOIN t2 ON t1.v = t2.v WHERE t1.a=t2.a; SELECT * FROM t1 INNER JOIN t2 ON t1.v = t2.v WHERE t1.a=t2.a; --- mysql-test/main/join.result | 327 ++++++++++++++++++++++++++--- mysql-test/main/join.test | 117 ++++++++++- sql/share/errmsg-utf8.txt | 2 + sql/sql_list.h | 32 +++ sql/sql_parse.cc | 13 +- sql/sql_select.cc | 405 ++++++++++++++++++++++++++++++------ sql/sql_yacc.yy | 6 + sql/table.cc | 12 +- sql/table.h | 11 +- 9 files changed, 816 insertions(+), 109 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index 0f9d45b49cd01..78c94c76baab9 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3668,83 +3668,83 @@ insert into t2 (a) values (1),(2),(3); # This test only verifies syntax acceptance. # TODO fix PS protocol before end of FULL OUTER JOIN development 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` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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,9 +3753,9 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 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` +Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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,15 +3764,15 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 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` +Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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 1 SIMPLE t2 UNKNOWN NULL NULL NULL NULL 0 0.00 Warnings: -Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2`)select `test`.`t1`.`a` AS `a` from `test`.`t1` natural full join `test`.`t2` +Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)))select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; ERROR 42S02: Table 'test.t3' doesn't exist select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; @@ -3781,5 +3781,276 @@ 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 +6 0 0 +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 +1 -5 -5 +2 -4 -4 +3 -3 -3 +4 -2 -2 +5 -1 -1 +6 0 0 +7 1 1 +8 2 2 +9 3 3 +10 4 4 +11 5 5 +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 +NULL NULL NULL +NULL NULL NULL +NULL NULL NULL +NULL NULL NULL +NULL NULL NULL +6 0 0 +7 1 1 +NULL NULL NULL +NULL NULL NULL +NULL NULL NULL +NULL NULL NULL +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 +# FULL to INNER, two variables; these two queries should have equal results. +select * from (select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1) as t3; +v +2 +select one.v from two inner join one where two.v = one.v and one.v > 1 and one.v > 1; +v +2 +# FULL to INNER with a UNION; these two queries should have equal results. +select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1 union select * from one; +v +2 +1 +3 +select one.v from two inner join one where one.v = two.v and two.v > 1 and two.v > 1 union select * from one; +v +2 +1 +3 +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 6a77266dc8a50..92b59b61f849c 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2074,34 +2074,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 @@ -2128,7 +2128,112 @@ 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; + +--echo # FULL to INNER, two variables; these two queries should have equal results. +select * from (select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1) as t3; +select one.v from two inner join one where two.v = one.v and one.v > 1 and one.v > 1; + +--echo # FULL to INNER with a UNION; these two queries should have equal results. +select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1 union select * from one; +select one.v from two inner join one where one.v = two.v and two.v > 1 and two.v > 1 union select * from one; + +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 4931f4874a1ff..37a7718fae42e 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12392,3 +12392,5 @@ ER_CANNOT_CAST_ON_IDENT1_ASSIGNMENT_FOR_OPERATION eng "Cannot cast '%s' as '%s' in assignment of %sQ for operation %s" ER_CANNOT_CAST_ON_IDENT2_ASSIGNMENT_FOR_OPERATION eng "Cannot cast '%s' as '%s' in assignment of %sQ.%sQ for operation %s" +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_list.h b/sql/sql_list.h index 77fd69402e9bf..c53c8da283d66 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -432,6 +432,10 @@ class base_list_iterator { return (*el)->info; } + inline void *peek_ref() + { + return &(*el)->info; + } inline void *next_fast(void) { list_node *tmp; @@ -613,6 +617,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(); } + inline T** peek_ref() { return (T**) base_list_iterator::peek_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* next= peek(); + T* cur= replace(next); + *peek_ref()= cur; + return next; + } }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b0affea6e252b..9a413675ec712 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8726,12 +8726,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); @@ -8996,9 +8994,7 @@ Item *normalize_cond(THD *thd, Item *cond) /** - Add an ON condition to the second operand of a JOIN ... ON. - - Add an ON condition to the right operand of a JOIN ... ON clause. + Add an ON condition to the second (right) operand of a JOIN ... ON. @param b the second operand of a JOIN ... ON @param expr the condition to be added to the ON clause @@ -9026,6 +9022,7 @@ void add_join_on(THD *thd, TABLE_LIST *b, Item *expr) b->on_expr= new (thd->mem_root) Item_cond_and(thd, b->on_expr,expr); } b->on_expr->top_level_item(); + b->on_expr->base_flags|= item_base_t::IS_COND; } } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4c36fc84fe652..0756a72d59e59 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1886,18 +1886,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->full_join_count > 0) && // 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: @@ -19801,39 +19789,315 @@ propagate_cond_constants(THD *thd, I_List *save_list, } } + +/** + 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 in_sj, + table_map *used_tables, + table_map *not_null_tables) +{ + DBUG_ASSERT(used_tables); + DBUG_ASSERT(not_null_tables); + + 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, + in_sj || table->sj_on_expr); + if (!conds && join->thd->is_error()) + return nullptr; + *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; +} + + +/** + 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; + + if (!(right_table->outer_join & JOIN_TYPE_NATURAL)) + { + DBUG_ASSERT((right_table->on_expr->base_flags & + item_base_t::IS_COND) == 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 from setup_natural_join_row_types(). + + 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 WHERE expressions. Will be AND'ed with ON expressions + if rewrite happens. + @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 in_sj, + TABLE_LIST **right_table, + List_iterator *li, + table_map *used_tables, + table_map *not_null_tables) +{ + DBUG_ENTER("rewrite_full_outer_joins"); + + /* + 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) + DBUG_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(ME_FATAL), + "FULL JOINs that cannot be converted to LEFT, RIGHT, or " + "INNER JOINs"); + DBUG_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. Otherwise continue to attempt to rewrite + in the base case. + */ + TABLE_LIST *left_table= li->peek(); + table_map left_used_tables= 0; + table_map left_not_null_tables= 0; + DBUG_ASSERT(test_all_bits(left_table->outer_join, + JOIN_TYPE_FULL | JOIN_TYPE_LEFT)); + if (left_table->nested_join) + { + conds= simplify_nested_join(join, left_table, conds, in_sj, + &left_used_tables, &left_not_null_tables); + if (!conds && join->thd->is_error()) + DBUG_RETURN(nullptr); + } + else + { + left_used_tables= left_table->get_map(); + left_not_null_tables= *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 does 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_used_tables; + + /* + Swap myself with the left as though we did convert_right_join(). + Then we will have effectively done the following transformation: + FULL -> RIGHT -> LEFT. + Again, RIGHT JOINs don't actually exist in MariaDB! + */ + *right_table= li->swap_next(); + --join->thd->lex->full_join_count; + } + else + { + /* + If the left table, be it a nested join or not, rejects nulls for + the WHERE condition, then rewrite. + */ + *not_null_tables= left_not_null_tables; + if (left_used_tables & *not_null_tables) + { + rewrite_full_to_left(left_table, *right_table); + --join->thd->lex->full_join_count; + } + // else the FULL JOIN cannot be rewritten, pass it along. + } + + DBUG_RETURN(conds); +} + + /** Simplify joins replacing outer joins by inner joins whenever it's possible. - The function, during a retrieval of join_list, eliminates those - outer joins that can be converted into inner join, possibly nested. - It also moves the on expressions for the converted outer joins - and from inner joins to conds. + The function, during a retrieval of join_list, eliminates those + OUTER JOINs that can be converted into INNER JOIN, possibly nested. + It also moves the ON expressions for the converted OUTER JOINs + and from INNER JOINs to conds. The function also calculates some attributes for nested joins: - - used_tables - - not_null_tables - - dep_tables. - - on_expr_dep_tables - The first two attributes are used to test whether an outer join can - be substituted for an inner join. The third attribute represents the + - used_tables + - not_null_tables + - dep_tables. + - on_expr_dep_tables + used_tables and not_null_tables are used to test whether an outer join can + be substituted for an INNER JOIN. dep_tables represents the relation 'to be dependent on' for tables. If table t2 is dependent on table t1, then in any evaluated execution plan table access to table t2 must precede access to table t2. This relation is used also - to check whether the query contains invalid cross-references. - The forth attribute is an auxiliary one and is used to calculate + to check whether the query contains invalid cross-references. + on_expr_dep_tables is an auxiliary one and is used to calculate dep_tables. As the attribute dep_tables qualifies possible orders of tables in the - execution plan, the dependencies required by the straight join + execution plan, the dependencies required by the STRAIGHT JOIN modifiers are reflected in this attribute as well. The function also removes all braces that can be removed from the join expression without changing its meaning. @note - An outer join can be replaced by an inner join if the where condition - or the on expression for an embedding nested join contains a conjunctive - predicate rejecting null values for some attribute of the inner tables. + An OUTER JOIN can be replaced by an INNER JOIN if the WHERE condition + or the ON expression for an embedding nested join contains a conjunctive + predicate rejecting NULL values for some attribute of the inner tables. - E.g. in the query: + E.g. in the query: @code SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5 @endcode @@ -19851,12 +20115,11 @@ propagate_cond_constants(THD *thd, I_List *save_list, Similarly the following query: @code SELECT * from t1 LEFT JOIN (t2, t3) ON t2.a=t1.a t3.b=t1.b - WHERE t2.c < 5 + WHERE t2.c < 5 @endcode is converted to: @code - SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a t3.b=t1.b - + SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a t3.b=t1.b @endcode One conversion might trigger another: @@ -19865,10 +20128,10 @@ propagate_cond_constants(THD *thd, I_List *save_list, LEFT JOIN t3 ON t3.b=t2.b WHERE t3 IS NOT NULL => SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a, t3 - WHERE t3 IS NOT NULL AND t3.b=t2.b => + WHERE t3 IS NOT NULL AND t3.b=t2.b => SELECT * FROM t1, t2, t3 WHERE t3 IS NOT NULL AND t3.b=t2.b AND t2.a=t1.a - @endcode + @endcode The function removes all unnecessary braces from the expression produced by the conversions. @@ -19876,10 +20139,9 @@ propagate_cond_constants(THD *thd, I_List *save_list, @code SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b @endcode - finally is converted to: + finally is converted to: @code SELECT * FROM t1, t2, t3 WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b - @endcode @@ -19889,27 +20151,39 @@ propagate_cond_constants(THD *thd, I_List *save_list, SELECT * from (t1, (t2,t3)) WHERE t1.a=t2.a AND t2.b=t3.b. @endcode - The benefit of this simplification procedure is that it might return + + Here's an example where the converted OUTER JOIN has its ON + conditions migrated to the ON condition for the INNER JOIN. + @code + SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t3.a=t2.a) ON t3.a=t1.a; + @endcode + becomes + @code + SELECT * FROM t1 LEFT JOIN (t2 INNER JOIN t3) ON t3.a=t2.a AND t3.a=t1.a; + #endcode + + + The benefit of this simplification procedure is that it might return a query for which the optimizer can evaluate execution plan with more - join orders. With a left join operation the optimizer does not + join orders. With a LEFT JOIN operation the optimizer does not consider any plan where one of the inner tables is before some of outer tables. IMPLEMENTATION The function is implemented by a recursive procedure. On the recursive - ascent all attributes are calculated, all outer joins that can be + ascent all attributes are calculated, all OUTER JOINs that can be converted are replaced and then all unnecessary braces are removed. As join list contains join tables in the reverse order sequential elimination of outer joins does not require extra recursive calls. SEMI-JOIN NOTES - Remove all semi-joins that have are within another semi-join (i.e. have + Remove all semi-joins that are within another semi-join (i.e. have an "ancestor" semi-join nest) EXAMPLES Here is an example of a join query with invalid cross references: @code - SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b + SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b @endcode @param join reference to the query info @@ -19918,7 +20192,7 @@ propagate_cond_constants(THD *thd, I_List *save_list, @param in_sj TRUE <=> processing semi-join nest's children @return - The new condition, if success - - 0, otherwise + - nullptr otherwise */ static COND * @@ -19937,6 +20211,15 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) */ 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; @@ -19959,6 +20242,8 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) */ expr= simplify_joins(join, &nested_join->join_list, expr, in_sj || table->sj_on_expr); + if (!expr && join->thd->is_error()) + DBUG_RETURN(nullptr); if (!table->prep_on_expr || expr != table->on_expr) { @@ -19968,14 +20253,10 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) 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, - 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, in_sj, + &used_tables, ¬_null_tables); + if (!conds && join->thd->is_error()) + DBUG_RETURN(nullptr); } else { @@ -19985,7 +20266,18 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) 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. + */ + if (table->outer_join & JOIN_TYPE_FULL) + conds= rewrite_full_outer_joins(join, conds, in_sj, &table, + &li, &used_tables, + ¬_null_tables); + if (!conds && join->thd->is_error()) + DBUG_RETURN(nullptr); + if (table->embedding) { table->embedding->nested_join->used_tables|= used_tables; @@ -31972,8 +32264,6 @@ static void print_table_array(THD *thd, if (curr->outer_join & JOIN_TYPE_FULL) { - if (curr->outer_join & JOIN_TYPE_NATURAL) - str->append(STRING_WITH_LEN(" natural")); str->append(STRING_WITH_LEN(" full join ")); } else if (curr->outer_join & (JOIN_TYPE_LEFT|JOIN_TYPE_RIGHT)) @@ -31989,13 +32279,8 @@ static void print_table_array(THD *thd, str->append(STRING_WITH_LEN(" join ")); curr->print(thd, eliminated_tables, str, query_type); - /* - NATURAL JOINs don't expose explicit join columns, so don't - print them as they're considered invalid syntax (this is - important for VIEWs as when VIEWs are loaded, their SQL - syntax is parsed again and must be valid). - */ - if (curr->on_expr && !(curr->outer_join & JOIN_TYPE_NATURAL)) + + if (curr->on_expr) { str->append(STRING_WITH_LEN(" on(")); curr->on_expr->print(str, query_type); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 76cacfb4c5bd4..00f84827fec17 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12582,10 +12582,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))) @@ -12607,10 +12609,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 ')' { @@ -12625,11 +12629,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.cc b/sql/table.cc index f527ad13c1dd0..6f3ba1a5fb7a0 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -6831,7 +6831,7 @@ bool TABLE_LIST::set_insert_values(MEM_ROOT *mem_root) RETURN TRUE if a leaf, FALSE otherwise. */ -bool TABLE_LIST::is_leaf_for_name_resolution() +bool TABLE_LIST::is_leaf_for_name_resolution() const { return (is_merged_derived() || is_natural_join || is_join_columns_complete || !nested_join); @@ -6861,13 +6861,13 @@ bool TABLE_LIST::is_leaf_for_name_resolution() else return 'this' */ -TABLE_LIST *TABLE_LIST::first_leaf_for_name_resolution() +TABLE_LIST *TABLE_LIST::first_leaf_for_name_resolution() const { TABLE_LIST *UNINIT_VAR(cur_table_ref); NESTED_JOIN *cur_nested_join; if (is_leaf_for_name_resolution()) - return this; + return const_cast(this); DBUG_ASSERT(nested_join); for (cur_nested_join= nested_join; @@ -6882,7 +6882,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++)) @@ -6937,7 +6938,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; diff --git a/sql/table.h b/sql/table.h index d96fde536dd35..b474271f82826 100644 --- a/sql/table.h +++ b/sql/table.h @@ -3069,7 +3069,7 @@ struct TABLE_LIST bool set_insert_values(MEM_ROOT *mem_root); void replace_view_error_with_generic(THD *thd); TABLE_LIST *find_underlying_table(TABLE *table); - TABLE_LIST *first_leaf_for_name_resolution(); + TABLE_LIST *first_leaf_for_name_resolution() const; TABLE_LIST *last_leaf_for_name_resolution(); /* System Versioning */ @@ -3102,7 +3102,7 @@ struct TABLE_LIST return tbl; } TABLE *get_real_join_table(); - bool is_leaf_for_name_resolution(); + bool is_leaf_for_name_resolution() const; inline TABLE_LIST *top_table() { return belong_to_view ? belong_to_view : this; } inline bool prepare_check_option(THD *thd) @@ -3301,6 +3301,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); From f3686407bb7c46218c5abf8d0fe5a2ca12e66adb Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 28 Jan 2026 16:28:54 -0500 Subject: [PATCH 5/9] MDEV-38692: COALESCE() on NATURAL FULL JOIN result sets FULL JOIN yields result sets with columns from both tables participating in the join (for the sake of explanation, assume base tables). However, NATURAL FULL JOIN should show unique columns in the output. Given the following query: SELECT * FROM t1 NATURAL JOIN t2; transform it into: SELECT COALESCE(t1.f_1, t2.f_1), ..., COALESCE(t1.f_n, t2.f_n) FROM t1 NATURAL JOIN t2; This change applies only in the case of NATURAL FULL JOIN. Otherwise, NATURAL JOINs work as they have in the past, which is using columns from the left table for the resulting column set. --- mysql-test/main/join.result | 147 +++++++++++++++++++++++++++++++++--- mysql-test/main/join.test | 76 +++++++++++++++++++ sql/sql_base.cc | 83 ++++++++++++++++++-- sql/sql_select.cc | 6 -- sql/table.cc | 20 ++++- sql/table.h | 3 + 6 files changed, 310 insertions(+), 25 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index 78c94c76baab9..e0e13ca0eb50a 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3690,7 +3690,7 @@ 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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) select * from t1 natural full join t2; 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; @@ -3698,7 +3698,7 @@ 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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) 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 JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' @@ -3934,17 +3934,17 @@ pk x y pk x y # FULL NATURAL to RIGHT JOIN select * from x natural full join xsq where xsq.pk is not null; pk x y -NULL NULL NULL -NULL NULL NULL -NULL NULL NULL -NULL NULL NULL -NULL NULL NULL +1 -5 25 +2 -4 16 +3 -3 9 +4 -2 4 +5 -1 1 6 0 0 7 1 1 -NULL NULL NULL -NULL NULL NULL -NULL NULL NULL -NULL NULL NULL +8 2 4 +9 3 9 +10 4 16 +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 @@ -4053,4 +4053,129 @@ v 1 3 drop table one, two, three; +# NATURAL FULL JOIN COALESCE() for unique column sets. +create table t1 (a int, b int); +create table t2 (a int, b int); +create table t3 (a int, b int); +insert into t1 (a,b) values (1,1),(2,2); +insert into t2 (a,b) values (1,1),(3,3); +insert into t3 (a,b) values (3,3),(4,4); +select * from t1 natural full join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null; +a b +1 1 +explain extended +select * from t1 natural full join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` join `test`.`t1` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t2 join t1 where +t2.a = t1.a and t2.b = t1.b and +t1.a is not null and t1.b is not null; +a b +1 1 +select * from t1 natural full join t2 where t1.a is not null; +a b +1 1 +2 2 +explain extended select * from t1 natural full join t2 where t1.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; +a b +1 1 +2 2 +select * from t1 natural full join t2 where t2.a is not null; +a b +1 1 +3 3 +explain extended select * from t1 natural full join t2 where t2.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` left join `test`.`t1` on(`test`.`t1`.`a` = `test`.`t2`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`) where `test`.`t2`.`a` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; +a b +1 1 +3 3 +select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; +a b a b +1 1 1 1 +NULL NULL 3 3 +explain extended select * from +(t1 natural join t2) right join t2 t3 on t1.a=t3.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t2` `t3` left join (`test`.`t1` join `test`.`t2`) on(`test`.`t1`.`a` = `test`.`t3`.`a` and `test`.`t2`.`a` = `test`.`t3`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where 1 +select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; +a b a b +1 1 1 1 +NULL NULL 3 3 +explain extended select * from +(t1 natural full join t2) right join t2 t3 on t1.a=t3.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t2` `t3` left join (`test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t3`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`)) on(`test`.`t1`.`a` = `test`.`t3`.`a`) where 1 +select t1.a from t1 natural full join (t2 natural join t3) where +t1.a is not null; +a +1 +2 +explain extended select t1.a from +t1 natural full join (t2 natural join t3) where t1.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` left join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t3`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null +select t1.a AS a from t1 left join (t2 join t3) on +t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where +t1.a is not null; +a +1 +2 +explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.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 coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t1`.`b`)) group by `test`.`t1`.`a` +select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; +a b avg(t2.a) +1 1 1.0000 +2 2 NULL +explain extended select *, avg(t2.a) from t1 natural full join t2 where +t1.a is not null group by t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null group by `test`.`t1`.`a` +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, +avg(t2.a) AS avg_t2_a from +t1 left join t2 on t2.a = t1.a and t2.b = t1.b where +t1.a is not null group by t1.a; +a b avg_t2_a +1 1 1.0000 +2 2 NULL +drop table t1, t2, t3; # End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index 92b59b61f849c..094971af95e73 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2234,6 +2234,82 @@ select one.v from two inner join one where one.v = two.v and two.v > 1 and two.v drop table one, two, three; +--echo # NATURAL FULL JOIN COALESCE() for unique column sets. +create table t1 (a int, b int); +create table t2 (a int, b int); +create table t3 (a int, b int); +insert into t1 (a,b) values (1,1),(2,2); +insert into t2 (a,b) values (1,1),(3,3); +insert into t3 (a,b) values (3,3),(4,4); + +select * from t1 natural full join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null; + +explain extended +select * from t1 natural full join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t2 join t1 where + t2.a = t1.a and t2.b = t1.b and + t1.a is not null and t1.b is not null; + + +select * from t1 natural full join t2 where t1.a is not null; + +explain extended select * from t1 natural full join t2 where t1.a is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; + + +select * from t1 natural full join t2 where t2.a is not null; + +explain extended select * from t1 natural full join t2 where t2.a is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; + + +select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; + +explain extended select * from + (t1 natural join t2) right join t2 t3 on t1.a=t3.a; + +select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; + +explain extended select * from + (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; + + +select t1.a from t1 natural full join (t2 natural join t3) where + t1.a is not null; + +explain extended select t1.a from + t1 natural full join (t2 natural join t3) where t1.a is not null; + +select t1.a AS a from t1 left join (t2 join t3) on + t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where + t1.a is not null; + + +explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.a; + + +select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; + +explain extended select *, avg(t2.a) from t1 natural full join t2 where + t1.a is not null group by t1.a; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, + avg(t2.a) AS avg_t2_a from + t1 left join t2 on t2.a = t1.a and t2.b = t1.b where + t1.a is not null group by t1.a; + +drop table t1, t2, t3; + # TODO fix PS protocol before end of FULL OUTER JOIN development --enable_ps_protocol --echo # End of 12.3 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 059cf17e75bf6..3606e46ed1733 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7562,8 +7562,15 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, /* Create non-fixed fully qualified field and let fix_fields to - resolve it. + resolve it. Clear the natural_full_join_field pointer--we recursed + on the nested join tree (store_top_level_join_columns) and picked + new common columns, so these natural_full_join_field items are invalid. + They will be updated to valid fields shortly, on the next call to + coalesce_natural_full_join via store_natural_using_join_columns + (case of NATURAL FULL JOIN only). */ + nj_col_1->natural_full_join_field= nullptr; + nj_col_2->natural_full_join_field= nullptr; Item *item_1= nj_col_1->create_item(thd); Item *item_2= nj_col_2->create_item(thd); Item_ident *item_ident_1, *item_ident_2; @@ -7648,6 +7655,56 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, } +/* + For some pair of tables (t1, t2) such that + t1 NATURAL FULL JOIN t2 + generate a set of output columns + COALESCE(t1.x_1, t2.y_1), ..., COALESCE(t1.x_n, t2.y_n) + such that NULL results won't appear in the NATURAL FULL JOIN. + + @parma thd the current thread + @param left_join_columns common columns originating in t1 + @param right_join_columns common columns originating in t2 + */ +void coalesce_natural_full_join(THD *thd, + List *left_join_columns, + List *right_join_columns) +{ + /* + It's a NATURAL JOIN so the number of columns from the left table better + match the number from the right table. + */ + DBUG_ASSERT(left_join_columns->elements == right_join_columns->elements); + + /* + Walk the left table and right table columns in lock-step, creating a + new COALESCE() over each pair of columns. The calling function relies + on the state of left_join_columns, so set the COALESCE() item instance + on members of that list. + */ + List_iterator left(*left_join_columns); + List_iterator right(*right_join_columns); + Natural_join_column *left_col= left++; + Natural_join_column *right_col= right++; + while (!left.at_end() && !right.at_end()) + { + Item *left_field= left_col->get_item(); + Item *right_field= right_col->get_item(); + Item_func_coalesce *coal= new (thd->mem_root) Item_func_coalesce + (thd, left_field, right_field); + DBUG_ASSERT(coal); + + // Makes the field `COALESCE(left, right) AS left`. + coal->set_name(thd, left_field->name); + + // Save the result into the set of left_join_columns. + left_col->natural_full_join_field= coal; + + left_col= left++; + right_col= right++; + } +} + /* Materialize and store the row type of NATURAL/USING join. @@ -7696,7 +7753,8 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, Query_arena *arena, backup; bool result= TRUE; List *non_join_columns; - List *join_columns; + List *left_join_columns; + List *right_join_columns; DBUG_ENTER("store_natural_using_join_columns"); DBUG_ASSERT(!natural_using_join->join_columns); @@ -7704,7 +7762,8 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, arena= thd->activate_stmt_arena_if_needed(&backup); if (!(non_join_columns= new List) || - !(join_columns= new List)) + !(left_join_columns= new List) || + !(right_join_columns= new List)) goto err; /* Append the columns of the first join operand. */ @@ -7713,7 +7772,7 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, nj_col_1= it_1.get_natural_column_ref(); if (nj_col_1->is_common) { - join_columns->push_back(nj_col_1, thd->mem_root); + left_join_columns->push_back(nj_col_1, thd->mem_root); /* Reset the common columns for the next call to mark_common_columns. */ nj_col_1->is_common= FALSE; } @@ -7733,7 +7792,7 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, while ((using_field_name= using_fields_it++)) { List_iterator_fast - it(*join_columns); + it(*left_join_columns); Natural_join_column *common_field; for (;;) @@ -7760,14 +7819,24 @@ store_natural_using_join_columns(THD *thd, TABLE_LIST *natural_using_join, non_join_columns->push_back(nj_col_2, thd->mem_root); else { + right_join_columns->push_back(nj_col_2, thd->mem_root); + /* Reset the common columns for the next call to mark_common_columns. */ nj_col_2->is_common= FALSE; } } + /* + If this is a NATURAL FULL JOIN, then create a COALESCE() for each pair of + columns from the two joined tables. + */ + if ((table_ref_1->outer_join & JOIN_TYPE_FULL) && + (table_ref_2->outer_join & JOIN_TYPE_FULL)) + coalesce_natural_full_join(thd, left_join_columns, right_join_columns); + if (non_join_columns->elements > 0) - join_columns->append(non_join_columns); - natural_using_join->join_columns= join_columns; + left_join_columns->append(non_join_columns); + natural_using_join->join_columns= left_join_columns; natural_using_join->is_join_columns_complete= TRUE; result= FALSE; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 0756a72d59e59..c6d143918b14f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -19859,12 +19859,6 @@ static void rewrite_full_to_left(TABLE_LIST *left_table, // Only the right table in a LEFT JOIN has the naming context in the grammar left_table->on_context= nullptr; - - if (!(right_table->outer_join & JOIN_TYPE_NATURAL)) - { - DBUG_ASSERT((right_table->on_expr->base_flags & - item_base_t::IS_COND) == item_base_t::IS_COND); - } } diff --git a/sql/table.cc b/sql/table.cc index 6f3ba1a5fb7a0..6155d2aa28f2b 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -7246,8 +7246,9 @@ Natural_join_column::Natural_join_column(Field_translator *field_param, { DBUG_ASSERT(tab->field_translation); view_field= field_param; - table_field= NULL; + table_field= nullptr; table_ref= tab; + natural_full_join_field= nullptr; is_common= FALSE; } @@ -7259,12 +7260,16 @@ Natural_join_column::Natural_join_column(Item_field *field_param, table_field= field_param; view_field= NULL; table_ref= tab; + natural_full_join_field= nullptr; is_common= FALSE; } const Lex_ident_column Natural_join_column::name() { + if (natural_full_join_field) + return natural_full_join_field->name; + if (view_field) { DBUG_ASSERT(table_field == NULL); @@ -7277,12 +7282,25 @@ const Lex_ident_column Natural_join_column::name() Item *Natural_join_column::create_item(THD *thd) { + if (natural_full_join_field) + return natural_full_join_field; + if (view_field) { DBUG_ASSERT(table_field == NULL); return create_view_field(thd, table_ref, &view_field->item, &view_field->name); } + + return table_field; +} + + +Item *Natural_join_column::get_item() +{ + if (view_field) + return view_field->item; + return table_field; } diff --git a/sql/table.h b/sql/table.h index b474271f82826..e78cbc1537e96 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2300,12 +2300,14 @@ struct Field_translator Field (for tables), or a Field_translator (for views). */ +class Item_func_coalesce; class Natural_join_column: public Sql_alloc { public: Field_translator *view_field; /* Column reference of merge view. */ Item_field *table_field; /* Column reference of table or temp view. */ TABLE_LIST *table_ref; /* Original base table/view reference. */ + Item_func_coalesce *natural_full_join_field; /* True if a common join column of two NATURAL/USING join operands. Notice that when we have a hierarchy of nested NATURAL/USING joins, a column can @@ -2319,6 +2321,7 @@ class Natural_join_column: public Sql_alloc Natural_join_column(Item_field *field_param, TABLE_LIST *tab); const Lex_ident_column name(); Item *create_item(THD *thd); + Item *get_item(); Field *field(); const Lex_ident_table safe_table_name() const; const Lex_ident_db safe_db_name() const; From 6aabe2910f54bc0a819f89aa849b1f353e55a787 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Fri, 21 Nov 2025 15:27:47 -0500 Subject: [PATCH 6/9] MDEV-38136: Prevent elimination of tables in a FULL OUTER JOIN Prevent elimination of tables participating in a FULL OUTER JOIN during eliminate_tables as part of phase one FULL OUTER JOIN development. Move the functionality gate for FULL JOIN further into the codebase: convert LEX::has_full_outer_join to a counter so we can see how many FULL JOINs remain which makes the gate work correctly after simplify_joins and eliminate_tables are called. Fixes an old bug where, when running the server as a debug build and in debug mode, a null pointer deference in Dep_analysis_context::dbug_print_deps would cause a crash. --- mysql-test/main/table_elim.result | 32 +++++++++++++++++++++++++++++++ mysql-test/main/table_elim.test | 21 ++++++++++++++++++++ sql/opt_table_elimination.cc | 9 ++++++--- sql/sql_select.cc | 22 +++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index 5c78d296b6b12..e4f3e662513aa 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1112,3 +1112,35 @@ DROP TABLE t1, t2; # # End of 10.11 tests # +# +# MDEV-38136 Prevent elimination of tables in a FULL OUTER JOIN +# +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); +create table t2 (a int primary key, b int) +as select a, a as b from t1 where a in (1,2); +create table t3 (a int primary key, b int) +as select a, a as b from t1 where a in (1,3); +# These will not be eliminated because contains a FULL OUTER JOIN. +explain extended select t1.a from t1 full join t2 on t2.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +drop table t1, t2, t3; +# End of 12.3 tests diff --git a/mysql-test/main/table_elim.test b/mysql-test/main/table_elim.test index 4158d2ca5ac8c..986c7e037e8c8 100644 --- a/mysql-test/main/table_elim.test +++ b/mysql-test/main/table_elim.test @@ -844,3 +844,24 @@ DROP TABLE t1, t2; --echo # --echo # End of 10.11 tests --echo # + +--echo # +--echo # MDEV-38136 Prevent elimination of tables in a FULL OUTER JOIN +--echo # +create table t1 (a int); +insert into t1 values (0),(1),(2),(3); + +create table t2 (a int primary key, b int) + as select a, a as b from t1 where a in (1,2); + +create table t3 (a int primary key, b int) + as select a, a as b from t1 where a in (1,3); + +--echo # These will not be eliminated because contains a FULL OUTER JOIN. +explain extended select t1.a from t1 full join t2 on t2.a=t1.a; +explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; +explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; + +drop table t1, t2, t3; + +--echo # End of 12.3 tests diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 812c206540f17..cc07903d0da78 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -825,7 +825,8 @@ eliminate_tables_for_list(JOIN *join, List *join_list, if (tbl->nested_join) { /* This is "... LEFT JOIN (join_nest) ON cond" */ - if (eliminate_tables_for_list(join, + if (!(tbl->outer_join & JOIN_TYPE_FULL) && + eliminate_tables_for_list(join, &tbl->nested_join->join_list, tbl->nested_join->used_tables, tbl->on_expr, @@ -840,7 +841,8 @@ eliminate_tables_for_list(JOIN *join, List *join_list, else { /* This is "... LEFT JOIN tbl ON cond" */ - if (!(tbl->table->map & outside_used_tables) && + if (!(tbl->outer_join & JOIN_TYPE_FULL) && + !(tbl->table->map & outside_used_tables) && check_func_dependency(join, tbl->table->map, NULL, tbl, tbl->on_expr)) { @@ -2105,7 +2107,8 @@ void Dep_analysis_context::dbug_print_deps() char buf[128]; String str(buf, sizeof(buf), &my_charset_bin); str.length(0); - eq_mod->expr->print(&str, QT_ORDINARY); + if (eq_mod->expr) + eq_mod->expr->print(&str, QT_ORDINARY); if (eq_mod->field) { fprintf(DBUG_FILE, " equality%ld: %s -> %s.%s\n", diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c6d143918b14f..d9713175d5099 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -5665,7 +5665,16 @@ make_join_statistics(JOIN *join, List &tables_list, TABLE **table_vector; JOIN_TAB *stat,*stat_end,*s,**stat_ref, **stat_vector; KEYUSE *keyuse,*start_keyuse; + + /* + outer_join here does not have the same meaning as TABLE_LIST::outer_join. + Here, outer_join is the union of all table numbers representing tables + that participate in this join. TABLE_LIST::outer_join marks how a + TABLE_LIST participates in a particular JOIN (as a right table, left table, + as part of a FULL JOIN, etc). + */ table_map outer_join=0; + table_map no_rows_const_tables= 0; SARGABLE_PARAM *sargables= 0; List_iterator ti(tables_list); @@ -5879,6 +5888,19 @@ make_join_statistics(JOIN *join, List &tables_list, join->const_table_map= no_rows_const_tables; 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; From 1c8f02a51f42ed176eb86cde54e75a7b2e6081a7 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Thu, 8 Jan 2026 13:57:41 -0500 Subject: [PATCH 7/9] MDEV-38502: FULL OUTER JOIN get correct sargable condition Move the temporary gate against FULL OUTER JOIN deeper into the codebase, which causes the FULL OUTER JOIN query plans to have more relevant information (hence the change). In some cases, the join order of nested INNER JOINs within the FULL OUTER JOIN changed. Small cleanups in get_sargable_cond ahead of the feature work in the next commit. --- mysql-test/main/join.result | 80 +++++++++++++++---------------- mysql-test/main/table_elim.result | 22 ++++----- sql/opt_range.cc | 5 ++ sql/sql_select.cc | 38 +++++++++------ 4 files changed, 79 insertions(+), 66 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index e0e13ca0eb50a..6bee1bc7ea8ab 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3671,73 +3671,73 @@ select * from t1 full join t2 on t1.a = t2.a; 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -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`)) +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 select * from t1 full outer join t2 on t1.a = t2.a; 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -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`)) +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 select * from t1 natural full outer join t2; 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 select * from t1 natural full join t2; 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -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`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 drop view v1; 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 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -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`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 drop view v1; 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 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 drop view v1; 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 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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 drop view v1; select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; ERROR 42000: This version of MariaDB doesn't yet support 'FULL JOINs that cannot be converted to LEFT, RIGHT, or INNER JOINs' @@ -3747,32 +3747,32 @@ select * from (select t1.a from t1 natural full join t2 union select * from t1) 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 -2 DERIVED t1 UNKNOWN NULL NULL NULL NULL 0 0.00 -2 DERIVED t2 UNKNOWN NULL NULL NULL NULL 0 0.00 -3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 PRIMARY ALL NULL NULL NULL NULL 12 100.00 +2 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +3 UNION t1 ALL NULL NULL NULL NULL 3 100.00 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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) union /* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1`) `dt` +Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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 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 -2 DERIVED t1 UNKNOWN NULL NULL NULL NULL 0 0.00 -2 DERIVED t2 UNKNOWN NULL NULL NULL NULL 0 0.00 -3 UNION t1 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 PRIMARY ALL NULL NULL NULL NULL 12 100.00 +2 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +3 UNION t1 ALL NULL NULL NULL NULL 3 100.00 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` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) union /* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1`) `dt` +Note 1003 /* select#1 */ select `dt`.`a` AS `a` from (/* select#2 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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 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 -1 SIMPLE t2 UNKNOWN NULL NULL NULL NULL 0 0.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Warnings: -Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)))select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`))select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; ERROR 42S02: Table 'test.t3' doesn't exist select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; @@ -4155,10 +4155,10 @@ a 2 explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.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 +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t1`.`b`)) group by `test`.`t1`.`a` +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where 1 group by `test`.`t1`.`a` select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; a b avg(t2.a) 1 1 1.0000 diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index e4f3e662513aa..e7ae65e63d9f3 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1124,23 +1124,23 @@ as select a, a as b from t1 where a in (1,3); # These will not be eliminated because contains a FULL OUTER JOIN. explain extended select t1.a from t1 full join t2 on t2.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`)) +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 -1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b`) where 1 explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.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 PRIMARY NULL NULL NULL 0 0.00 -1 SIMPLE t3 UNKNOWN PRIMARY NULL NULL NULL 0 0.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(multiple equal(`test`.`t2`.`a`, `test`.`t1`.`a`, `test`.`t3`.`a`) and multiple equal(`test`.`t2`.`b`, `test`.`t3`.`b`)) +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b`) where 1 drop table t1, t2, t3; # End of 12.3 tests diff --git a/sql/opt_range.cc b/sql/opt_range.cc index d1e3fa04792cc..4ab140b04978d 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -1216,6 +1216,11 @@ SQL_SELECT *make_select(TABLE *head, table_map const_tables, *error=0; + /* + If there's no condition at all then NULLs could end up in + the result set. However, we can disallow that with + allow_null_cond == false. + */ if (!conds && !allow_null_cond) DBUG_RETURN(0); if (!(select= new (head->in_use->mem_root) SQL_SELECT)) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d9713175d5099..6c4fbb04f12d8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2784,15 +2784,8 @@ JOIN::optimize_inner() with_two_phase_optimization= check_two_phase_optimization(thd); if (with_two_phase_optimization) optimization_state= JOIN::OPTIMIZATION_PHASE_1_DONE; - else if (thd->lex->full_join_count == 0) + else { - /* - Only during the FULL JOIN development cycle, disable second stage - optimization for FULL JOIN queries until the implementation is - mature enough to correctly execute the queries. But for now - this allows for some EXPLAIN EXTENDED support. - */ - if (optimize_stage2()) DBUG_RETURN(1); } @@ -3073,6 +3066,19 @@ int JOIN::optimize_stage2() if (setup_semijoin_loosescan(this)) DBUG_RETURN(1); + /* + 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) + { + if (!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(0); + } + if (make_join_select(this, select, conds)) { if (thd->is_error()) @@ -5616,23 +5622,25 @@ void mark_join_nest_as_const(JOIN *join, static Item **get_sargable_cond(JOIN *join, TABLE *table) { - Item **retval; - if (table->pos_in_table_list->on_expr) + Item **retval= nullptr; + TABLE_LIST *sql_table= table->pos_in_table_list; + + if (sql_table->on_expr) { /* This is an inner table from a single-table LEFT JOIN, "t1 LEFT JOIN t2 ON cond". Use the condition cond. */ - retval= &table->pos_in_table_list->on_expr; + retval= &sql_table->on_expr; } - else if (table->pos_in_table_list->embedding && - !table->pos_in_table_list->embedding->sj_on_expr) + else if (sql_table->embedding && + !sql_table->embedding->sj_on_expr) { /* This is the inner side of a multi-table outer join. Use the - appropriate ON expression. + ON expression from the nested join containing the table. */ - retval= &(table->pos_in_table_list->embedding->on_expr); + retval= &(sql_table->embedding->on_expr); } else { From cc439d57830c85eb2adc5a5eed1ffc9bfea7de72 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 7 Jan 2026 16:03:50 -0500 Subject: [PATCH 8/9] MDEV-38502: FULL OUTER JOIN get correct sargable condition Fetches the ON condition from the FULL OUTER JOIN as the sargable condition. We ignore the WHERE clause here because we don't want accidental conversions from FULL JOIN to INNER JOIN during, for example, range analysis, as that would produce wrong results. GCOV shows that existing FULL OUTER JOIN tests exercise this new codepath. --- sql/sql_select.cc | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6c4fbb04f12d8..92dcae6df99f2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -5615,9 +5615,11 @@ void mark_join_nest_as_const(JOIN *join, @detail Figure out which condition we can use: - - For INNER JOIN, we use the WHERE, - - "t1 LEFT JOIN t2 ON ..." uses t2's ON expression + - For INNER JOIN, we use the WHERE. + - "t1 LEFT JOIN t2 ON ..." uses t2's ON expression. - "t1 LEFT JOIN (...) ON ..." uses the join nest's ON expression. + - "t1 FULL OUTER JOIN t2 ON ..." uses the ON expression. + - "t1 FULL OUTER (...) ON ..." uses the join nest's ON expression. */ static Item **get_sargable_cond(JOIN *join, TABLE *table) @@ -5625,7 +5627,31 @@ static Item **get_sargable_cond(JOIN *join, TABLE *table) Item **retval= nullptr; TABLE_LIST *sql_table= table->pos_in_table_list; - if (sql_table->on_expr) + if (sql_table->outer_join & JOIN_TYPE_FULL) + { + /* + 1. FULL OUTER JOIN requires an ON condition, so someone must have it + 2. Disregard the WHERE clause at this point, using only the ON + condition because we don't want to range analysis to + accidentally turn the FULL JOIN into an INNER JOIN. + 3. The ON condition holds for both tables so if we don't find it + associated with one table, then look it on the partner table. + */ + if (sql_table->on_expr) + return &sql_table->on_expr; + + TABLE_LIST *foj_partner= sql_table->foj_partner; + DBUG_ASSERT(foj_partner->outer_join & JOIN_TYPE_FULL); + if (foj_partner->on_expr) + return &foj_partner->on_expr; + + /* + We cannot end up here, otherwise the ON condition for the FULL + OUTER JOIN was lost. + */ + DBUG_ASSERT(false); + } + else if (sql_table->on_expr) { /* This is an inner table from a single-table LEFT JOIN, "t1 LEFT JOIN From 1c8dccc1d880008eefd9512aa0b97a391ec8ce00 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 13 Apr 2026 09:34:55 -0400 Subject: [PATCH 9/9] MDEV-39014: FULL JOIN Phase 2 In phase 1, FULL [OUTER] JOIN was only supported when simplify_joins() could rewrite it into an equivalent LEFT, RIGHT, or INNER JOIN based on NULL-rejecting WHERE predicates. Queries that could not be rewritten raised ER_NOT_SUPPORTED_YET. (Phase 1 was not released.) This commit removes that restriction by adding proper support for FULL JOIN by executing a 'LEFT JOIN pass' that emits matched rows and left null-complemented rows, then a second "null-complement" pass which rescans the right table to emit null-complement rows that were never matched. FULL JOIN supports nested joins on the left of the FULL JOIN, NATURAL FULL JOIN, semi-joins, CTEs / derived tables (kept materialized when they participate in a FULL JOIN), prepared statements, stored procedures, and aggregates. Examples: SELECT * FROM (d1 FULL JOIN d2 ON d1.a = d2.a) FULL JOIN t3 ON d1.a = t3.a; SELECT * FROM t1 NATURAL FULL JOIN t2; SELECT * FROM t1 INNER JOIN t2 FULL JOIN t3 ON t1.a = t3.a; PREPARE st FROM 'SELECT COUNT(*) FROM t1 FULL JOIN t2 ON t1.a = t2.a'; Limitations: - The join cache is disabled whenever a FULL JOIN is present, which can regress plans for large FULL JOINs compared to the rewritten cases. A follow-up will re-enable it where safe. - Statistics and cost estimates for the null-complement pass have not been fully implemented; the optimizer may under- or over-estimate FULL JOIN costs in plans involving multiple FULL JOINs. Again, a follow-up will optimize the cost calculations. - Optimizations for constant tables not fully supported. - Nested tables on the right side of a FULL JOIN are not yet supported. --- mysql-test/main/full_join.result | 2692 +++++++++++++++++++++++++++++ mysql-test/main/full_join.test | 1707 ++++++++++++++++++ mysql-test/main/join.result | 522 ------ mysql-test/main/join.test | 267 --- mysql-test/main/table_elim.result | 16 +- sql/share/errmsg-utf8.txt | 2 +- sql/sql_base.cc | 50 +- sql/sql_lex.cc | 21 +- sql/sql_parse.cc | 14 +- sql/sql_select.cc | 903 ++++++++-- sql/sql_select.h | 42 +- sql/sql_yacc.yy | 1 + sql/table.cc | 10 +- 13 files changed, 5309 insertions(+), 938 deletions(-) create mode 100644 mysql-test/main/full_join.result create mode 100644 mysql-test/main/full_join.test diff --git a/mysql-test/main/full_join.result b/mysql-test/main/full_join.result new file mode 100644 index 0000000000000..f14d01b000878 --- /dev/null +++ b/mysql-test/main/full_join.result @@ -0,0 +1,2692 @@ +# +# MDEV-37932 / MDEV-39014: FULL [OUTER] JOIN +# +# Tests are grouped by feature. Within each group, a FULL JOIN +# query is generally paired with an equivalent LEFT JOIN UNION +# RIGHT JOIN formulation to verify correctness. +# +# ======================================================== +# Section 1: Parser and syntax acceptance +# +# FULL JOIN, FULL OUTER JOIN, NATURAL FULL [OUTER] JOIN, and +# their appearance inside views, derived tables, UNIONs, and +# CTEs. +# ======================================================== +create table t1 (a int); +insert into t1 (a) values (1),(2); +create table t2 (a int); +insert into t2 (a) values (1),(3); +create table t3 (a int); +insert into t3 (a) values (1),(4); +# Basic FULL [OUTER] JOIN syntax. +select * from t1 full join t2 on t1.a = t2.a; +a a +1 1 +2 NULL +NULL 3 +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +a a +1 1 +2 NULL +NULL 3 +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 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +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` = `test`.`t1`.`a`) where 1 +select * from t1 full outer join t2 on t1.a = t2.a; +a a +1 1 +2 NULL +NULL 3 +select * from t1 left outer join t2 on t1.a = t2.a union select * from t1 right outer join t2 on t1.a = t2.a; +a a +1 1 +2 NULL +NULL 3 +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 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +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` = `test`.`t1`.`a`) where 1 +# NATURAL FULL [OUTER] JOIN. +select * from t1 natural full outer join t2; +a +1 +2 +3 +select * from t1 natural left outer join t2 union select * from t1 natural right outer join t2; +a +1 +2 +3 +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 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +select * from t1 natural full join t2; +a +1 +2 +3 +select * from t1 natural left join t2 union select * from t1 natural right join t2; +a +1 +2 +3 +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 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +# FULL JOIN inside a view. +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; +t1a t2a +1 1 +2 NULL +NULL 3 +select t1.a as t1a, t2.a as t2a from t1 left join t2 on t1.a = t2.a union select t1.a as t1a, t2.a as t2a from t1 right join t2 on t1.a = t2.a; +t1a t2a +1 1 +2 NULL +NULL 3 +explain extended select * from v1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +drop view v1; +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; +t1a t2a +1 1 +2 NULL +NULL 3 +select t1.a as t1a, t2.a as t2a from t1 left outer join t2 on t1.a = t2.a union select t1.a as t1a, t2.a as t2a from t1 right outer join t2 on t1.a = t2.a; +t1a t2a +1 1 +2 NULL +NULL 3 +explain extended select * from v1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +drop view v1; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full join t2; +select * from v1; +t1a t2a +1 1 +2 NULL +NULL 3 +select t1.a as t1a, t2.a as t2a from t1 natural left join t2 union select t1.a as t1a, t2.a as t2a from t1 natural right join t2; +t1a t2a +1 1 +2 NULL +NULL 3 +explain extended select * from v1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +drop view v1; +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full outer join t2; +select * from v1; +t1a t2a +1 1 +2 NULL +NULL 3 +select t1.a as t1a, t2.a as t2a from t1 natural left outer join t2 union select t1.a as t1a, t2.a as t2a from t1 natural right outer join t2; +t1a t2a +1 1 +2 NULL +NULL 3 +explain extended select * from v1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +drop view v1; +# FULL JOIN inside a derived table combined with UNION. +select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; +a +1 +2 +NULL +select * from (select t1.a from t1 left join t2 on t1.a = t2.a union select t1.a from t1 right join t2 on t1.a = t2.a union select * from t1) dt; +a +1 +2 +NULL +select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt; +a +1 +2 +NULL +select * from (select t1.a from t1 left outer join t2 on t1.a = t2.a union select t1.a from t1 right outer join t2 on t1.a = t2.a union select * from t1) dt; +a +1 +2 +NULL +select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; +a +1 +2 +NULL +select * from (select t1.a from t1 natural left join t2 union select t1.a from t1 natural right join t2 union select * from t1) dt; +a +1 +2 +NULL +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 ALL NULL NULL NULL NULL 6 100.00 +2 DERIVED t1 ALL NULL NULL NULL NULL 2 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 2 100.00 Using where +3 UNION t1 ALL NULL NULL NULL NULL 2 100.00 +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` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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; +a +1 +2 +NULL +select * from (select t1.a from t1 natural left outer join t2 union select t1.a from t1 natural right outer join t2 union select * from t1) dt; +a +1 +2 +NULL +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 ALL NULL NULL NULL NULL 6 100.00 +2 DERIVED t1 ALL NULL NULL NULL NULL 2 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 2 100.00 Using where +3 UNION t1 ALL NULL NULL NULL NULL 2 100.00 +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` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 union /* select#3 */ select `test`.`t1`.`a` AS `a` from `test`.`t1`) `dt` +# FULL JOIN inside a CTE. +with cte as (select t1.a from t1 natural full join t2) select * from cte; +a +1 +2 +NULL +with cte as (select t1.a from t1 natural left join t2 union select t1.a from t1 natural right join t2) select * from cte; +a +1 +2 +NULL +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 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`))select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +# FULL JOIN referencing a missing table must error cleanly. +select * from t1, t2 full join t_not_exist on t2.c=t_not_exist.e and t_not_exist.f=t1.a; +ERROR 42S02: Table 'test.t_not_exist' doesn't exist +select * from t1, t2 full outer join t_not_exist on t2.c=t_not_exist.e and t_not_exist.f=t1.a; +ERROR 42S02: Table 'test.t_not_exist' doesn't exist +select * from t1, t2 natural full join t_not_exist; +ERROR 42S02: Table 'test.t_not_exist' doesn't exist +select * from t1, t2 natural full outer join t_not_exist; +ERROR 42S02: Table 'test.t_not_exist' doesn't exist +# FULL JOIN where one operand is a derived table (on the left). +select * from (select * from t1) dt natural full join t2; +a +1 +2 +3 +select * from (select * from t1) dt natural left join t2 union select * from (select * from t1) dt natural right join t2; +a +1 +2 +3 +select * from (select * from t2) du natural full join t1; +a +1 +3 +2 +select * from (select * from t2) du natural left join t1 union select * from (select * from t2) du natural right join t1; +a +1 +3 +2 +# FULL JOIN with a constant ON clause. +select * from t1 full join t2 on true; +a a +1 1 +1 3 +2 1 +2 3 +select * from t1 left join t2 on true union select * from t1 right join t2 on true; +a a +1 1 +2 1 +1 3 +2 3 +# ======================================================== +# Section 2: Basic FULL JOIN with nested joins on the left +# +# Each FULL JOIN query is followed by an equivalent +# LEFT/RIGHT/UNION formulation; the two must match. +# ======================================================== +select * from t1 inner join t2 full join t3 on t1.a=t3.a; +a a a +1 1 1 +1 3 1 +2 1 NULL +2 3 NULL +NULL NULL 4 +select * from t1 inner join t2 left join t3 on t1.a=t3.a union select * from t1 inner join t2 right join t3 on t1.a=t3.a; +a a a +1 1 1 +1 3 1 +2 1 NULL +2 3 NULL +NULL NULL 4 +select * from t1 inner join t2 on t1.a=t2.a full join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL NULL 4 +select * from t1 inner join t2 on t1.a=t2.a left join t3 on t1.a=t3.a union select * from t1 inner join t2 on t1.a=t2.a right join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL NULL 4 +select * from t1 cross join t2 full join t3 on t1.a=t3.a; +a a a +1 1 1 +1 3 1 +2 1 NULL +2 3 NULL +NULL NULL 4 +select * from t1 cross join t2 left join t3 on t1.a=t3.a union select * from t1 cross join t2 right join t3 on t1.a=t3.a; +a a a +1 1 1 +1 3 1 +2 1 NULL +2 3 NULL +NULL NULL 4 +select * from t1 cross join t2 on t1.a=t2.a full join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL NULL 4 +select * from t1 cross join t2 on t1.a=t2.a left join t3 on t1.a=t3.a union select * from t1 cross join t2 on t1.a=t2.a right join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL NULL 4 +select * from (t1 left join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL NULL 4 +select * from (t1 left join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 left join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL NULL 4 +select * from (t1 right join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL 3 NULL +NULL NULL 4 +select * from (t1 right join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; +a a a +1 1 1 +NULL 3 NULL +NULL NULL 4 +# Nested NATURAL JOIN on the left of FULL JOIN. +select * from (t1 natural join t2) full join t3 on t1.a=t3.a; +a a +1 1 +NULL 4 +select * from (t1 natural join t2) left join t3 on t1.a=t3.a union select * from (t1 natural join t2) right join t3 on t1.a=t3.a; +a a +1 1 +NULL 4 +# Nested FULL JOIN on the left of FULL JOIN. +# The inner FULL JOIN's unmatched right-side rows must appear +# in the result even when the outer FULL JOIN condition does +# not reference the inner right-side table. +# Data: t1(1,2) t2(1,3) t3(1,4) +select * from (t1 full join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL NULL 4 +select * from (t1 left join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL NULL 4 +# Chained FULL JOINs with the second ON referencing the middle table. +select * from t1 full join t2 on t1.a=t2.a full join t3 on t2.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL NULL 4 +select * from t1 left join t2 on t1.a=t2.a left join t3 on t2.a=t3.a union select * from t1 right join t2 on t1.a=t2.a left join t3 on t2.a=t3.a union select * from (t1 left join t2 on t1.a=t2.a) right join t3 on t2.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t2.a=t3.a; +a a a +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL NULL 4 +# Nested FULL JOIN with duplicate rows. +create table d1 (a int); +insert into d1 values (1),(1),(2); +create table d2 (a int); +insert into d2 values (1),(3),(3); +select * from (d1 full join d2 on d1.a=d2.a) full join t3 on d1.a=t3.a; +a a a +1 1 1 +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL 3 NULL +NULL NULL 4 +select * from (d1 left join d2 on d1.a=d2.a) left join t3 on d1.a=t3.a union all select * from (d1 right join d2 on d1.a=d2.a) left join t3 on d1.a=t3.a where d1.a is null union all select * from (d1 right join d2 on d1.a=d2.a) right join t3 on d1.a=t3.a where d1.a is null and d2.a is null; +a a a +1 1 1 +1 1 1 +2 NULL NULL +NULL 3 NULL +NULL 3 NULL +NULL NULL 4 +drop table d1, d2; +drop table t1, t2, t3; +# ======================================================== +# Section 3: FULL JOIN rewrites to LEFT, RIGHT, and INNER +# +# When a NULL-rejecting WHERE predicate selects one or both +# sides, simplify_joins() rewrites the FULL JOIN accordingly. +# The (re)written form must produce the same result as the +# direct LEFT/RIGHT/INNER formulation. +# ======================================================== +create table t1 (pk int auto_increment, x int, y int, primary key (pk)); +create table t2 (pk int auto_increment, x int, y int, primary key (pk)); +insert into t1 (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 t2 (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 t1 full join t2 on t1.y = t2.y where t2.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 t1 right join t2 on t1.y = t2.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 t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t2.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 t1 right join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.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 t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t1.pk is not null and t2.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 t1 inner join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.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 t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t1.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 t1 left join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.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 t1 natural full join t2 where t1.pk is not null and t2.pk is not null; +pk x y +6 0 0 +7 1 1 +select * from t1 inner join t2 on t1.x = t2.x and t1.y = t2.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 t1 natural full join t2 where t1.pk is not null; +pk x y +1 -5 -5 +2 -4 -4 +3 -3 -3 +4 -2 -2 +5 -1 -1 +6 0 0 +7 1 1 +8 2 2 +9 3 3 +10 4 4 +11 5 5 +select * from t1 left join t2 on t2.pk = t1.pk and t2.x = t1.x and t2.y = t1.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 t1 natural full join t2 where t2.pk is not null; +pk x y +1 -5 25 +2 -4 16 +3 -3 9 +4 -2 4 +5 -1 1 +6 0 0 +7 1 1 +8 2 4 +9 3 9 +10 4 16 +11 5 25 +select * from t1 right join t2 on t1.pk = t2.pk and t1.x = t2.x and t1.y = t2.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 +select * from t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; +pk x y pk x y +1 -5 -5 NULL NULL NULL +10 4 4 NULL NULL NULL +11 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 +6 0 0 7 1 1 +7 1 1 6 0 0 +7 1 1 7 1 1 +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +NULL NULL NULL 1 -5 25 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 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 +select * from t1 left join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 union select * from t1 right join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; +pk x y pk x y +1 -5 -5 NULL NULL NULL +10 4 4 NULL NULL NULL +11 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 +6 0 0 7 1 1 +7 1 1 6 0 0 +7 1 1 7 1 1 +8 2 2 NULL NULL NULL +9 3 3 NULL NULL NULL +NULL NULL NULL 1 -5 25 +NULL NULL NULL 10 4 16 +NULL NULL NULL 11 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 +select * from t1 natural full join t2; +pk x y +1 -5 -5 +1 -5 25 +10 4 16 +10 4 4 +11 5 25 +11 5 5 +2 -4 -4 +2 -4 16 +3 -3 -3 +3 -3 9 +4 -2 -2 +4 -2 4 +5 -1 -1 +5 -1 1 +6 0 0 +7 1 1 +8 2 2 +8 2 4 +9 3 3 +9 3 9 +select * from t1 natural left join t2 union select * from t1 natural right join t2; +pk x y +1 -5 -5 +1 -5 25 +10 4 16 +10 4 4 +11 5 25 +11 5 5 +2 -4 -4 +2 -4 16 +3 -3 -3 +3 -3 9 +4 -2 -2 +4 -2 4 +5 -1 -1 +5 -1 1 +6 0 0 +7 1 1 +8 2 2 +8 2 4 +9 3 3 +9 3 9 +drop table t1, t2; +# Rewrites with nested joins. +create table t1 (v int); +insert into t1 (v) values (1); +create table t2 (v int); +insert into t2 (v) values (2); +create table t3 (v int); +insert into t3 (v) values (3); +# (FULL)FULL to (INNER)INNER JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t1.v is not null and t2.v is not null and t3.v is not null; +v v v +select * from t1 inner join t2 on t1.v = t2.v inner join t3 on t2.v = t3.v; +v v v +# (FULL)FULL to (RIGHT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t1.v = t3.v where t2.v is not null; +v v v +NULL 2 NULL +select * from t1 right join t2 on t1.v = t2.v left join t3 on t1.v = t3.v; +v v v +NULL 2 NULL +# (FULL)FULL to (LEFT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t1.v = t3.v where t1.v is not null; +v v v +1 NULL NULL +select * from t1 left join t2 on t2.v = t1.v left join t3 on t3.v = t1.v; +v v v +1 NULL NULL +# (FULL)LEFT to (LEFT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v left join t3 on t2.v = t3.v where t1.v is not null; +v v v +1 NULL NULL +select * from t1 left join t2 on t1.v = t2.v left join t3 on t2.v = t3.v; +v v v +1 NULL NULL +# (FULL)LEFT to (RIGHT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v left join t3 on t2.v = t3.v where t2.v is not null; +v v v +NULL 2 NULL +select * from t1 right join t2 on t1.v = t2.v left join t3 on t3.v = t2.v; +v v v +NULL 2 NULL +# (LEFT)FULL to (LEFT)RIGHT JOIN +select * from t1 left join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t3.v is not null; +v v v +NULL NULL 3 +select * from t1 left join t2 on t1.v = t2.v right join t3 on t2.v = t3.v; +v v v +NULL NULL 3 +# (LEFT)FULL to (LEFT)LEFT JOIN +insert into t1 (v) values (2),(3); +insert into t2 (v) values (1); +truncate t3; +insert into t3 (v) values (1); +select * from t1; +v +1 +2 +3 +select * from t2; +v +2 +1 +select * from t3; +v +1 +select * from t1 left join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t3.v = 1; +v v v +1 1 1 +select * from t3 left join t1 on t1.v = 1 left join t2 on t2.v = 1; +v v v +1 1 1 +# FULL to INNER, two variables. +select * from (select t1.v from t1 full join t2 on t1.v = t2.v where t1.v > 1 and t2.v > 1) as dt; +v +2 +select t1.v from t2 inner join t1 where t2.v = t1.v and t1.v > 1 and t1.v > 1; +v +2 +# FULL to INNER with a UNION. +select t1.v from t1 full join t2 on t1.v = t2.v where t1.v > 1 and t2.v > 1 union select * from t1; +v +2 +1 +3 +select t1.v from t2 inner join t1 where t1.v = t2.v and t2.v > 1 and t2.v > 1 union select * from t1; +v +2 +1 +3 +drop table t1, t2, t3; +# ======================================================== +# Section 4: NATURAL FULL JOIN and COALESCE +# +# Common columns surface as COALESCE expressions rather than +# plain fields. +# ======================================================== +create table t1 (a int, b int); +create table t2 (a int, b int); +create table t3 (a int, b int); +insert into t1 (a,b) values (1,1),(2,2); +insert into t2 (a,b) values (1,1),(3,3); +insert into t3 (a,b) values (3,3),(4,4); +select * from t1 natural full join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null; +a b +1 1 +select * from t1 natural left join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null +union +select * from t1 natural right join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null; +a b +1 1 +explain extended +select * from t1 natural full join t2 where +t1.a is not null and t1.b is not null and +t2.a is not null and t2.b is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` join `test`.`t1` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t2 join t1 where +t2.a = t1.a and t2.b = t1.b and +t1.a is not null and t1.b is not null; +a b +1 1 +select * from t1 natural full join t2 where t1.a is not null; +a b +1 1 +2 2 +select * from t1 natural left join t2 where t1.a is not null +union +select * from t1 natural right join t2 where t1.a is not null; +a b +1 1 +2 2 +explain extended select * from t1 natural full join t2 where t1.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; +a b +1 1 +2 2 +select * from t1 natural full join t2 where t2.a is not null; +a b +1 1 +3 3 +select * from t1 natural left join t2 where t2.a is not null +union +select * from t1 natural right join t2 where t2.a is not null; +a b +1 1 +3 3 +explain extended select * from t1 natural full join t2 where t2.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` left join `test`.`t1` on(`test`.`t1`.`a` = `test`.`t2`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`) where `test`.`t2`.`a` is not null +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from +t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; +a b +1 1 +3 3 +select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; +a b a b +1 1 1 1 +NULL NULL 3 3 +select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; +a b a b +1 1 1 1 +NULL NULL 3 3 +select * from (t1 natural left join t2) right join t2 t3 on t1.a=t3.a +union +select * from (t1 natural right join t2) right join t2 t3 on t1.a=t3.a; +a b a b +1 1 1 1 +NULL NULL 3 3 +select t1.a from t1 natural full join (t2 natural join t3) where +t1.a is not null; +a +1 +2 +select t1.a from t1 natural left join (t2 natural join t3) where t1.a is not null +union +select t1.a from t1 natural right join (t2 natural join t3) where t1.a is not null; +a +1 +2 +explain extended select t1.a from +t1 natural full join (t2 natural join t3) where t1.a is not null; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` left join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t3`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null +select t1.a AS a from t1 left join (t2 join t3) on +t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where +t1.a is not null; +a +1 +2 +explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where 1 group by `test`.`t1`.`a` +select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; +a b avg(t2.a) +1 1 1.0000 +2 2 NULL +# UNION equivalent of the aggregate above. +select dt.a, dt.b, avg(dt.t2a) as `avg(t2.a)` from ( +select t1.a as a, t1.b as b, t2.a as t2a from t1 natural left join t2 +union +select t1.a, t1.b, t2.a from t1 natural right join t2) dt +where dt.a is not null group by dt.a; +a b avg(t2.a) +1 1 1.0000 +2 2 NULL +explain extended select *, avg(t2.a) from t1 natural full join t2 where +t1.a is not null group by t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +Warnings: +Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null group by `test`.`t1`.`a` +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, +avg(t2.a) AS avg_t2_a from +t1 left join t2 on t2.a = t1.a and t2.b = t1.b where +t1.a is not null group by t1.a; +a b avg_t2_a +1 1 1.0000 +2 2 NULL +drop table t1, t2, t3; +# ======================================================== +# Section 5: NULL handling +# +# NULL = NULL is false, so rows whose join key is NULL never +# match and must surface from their side unmatched. The +# NULL-safe <=> operator matches NULL to NULL. +# ======================================================== +create table t1 (a int, b int); +insert into t1 values (NULL, NULL), (1, 10), (NULL, NULL); +create table t2 (a int, b int); +insert into t2 values (NULL, NULL), (2, 20), (NULL, NULL); +# Both sides have all-NULL rows; no match possible. +select * from t1 full join t2 on t1.a = t2.a; +a b a b +1 10 NULL NULL +NULL NULL 2 20 +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +# The UNION formulation eliminates duplicate all-NULL rows; this +# is expected to differ. PostgreSQL agrees with the FULL JOIN +# result above. +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +a b a b +NULL NULL NULL NULL +1 10 NULL NULL +NULL NULL 2 20 +# IS NULL in the ON clause — all-NULL rows now match. +select * from t1 full join t2 on t1.a is null and t2.a is null; +a b a b +1 10 NULL NULL +NULL NULL 2 20 +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +select * from t1 left join t2 on t1.a is null and t2.a is null union select * from t1 right join t2 on t1.a is null and t2.a is null; +a b a b +1 10 NULL NULL +NULL NULL 2 20 +NULL NULL NULL NULL +# NULL-safe equality operator (<=>). +select * from t1 full join t2 on t1.a <=> t2.a; +a b a b +1 10 NULL NULL +NULL NULL 2 20 +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +select * from t1 left join t2 on t1.a <=> t2.a union select * from t1 right join t2 on t1.a <=> t2.a; +a b a b +1 10 NULL NULL +NULL NULL 2 20 +NULL NULL NULL NULL +# Table with only all-NULL rows on one side. +create table t3 (a int, b int); +insert into t3 values (NULL, NULL), (NULL, NULL); +select * from t1 full join t3 on t1.a = t3.a; +a b a b +1 10 NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +NULL NULL NULL NULL +select * from t1 left join t3 on t1.a = t3.a union select * from t1 right join t3 on t1.a = t3.a; +a b a b +1 10 NULL NULL +NULL NULL NULL NULL +drop table t1, t2, t3; +# ======================================================== +# Section 6: Deeply nested FULL JOINs +# ======================================================== +create table t1 (a int); +insert into t1 values (1), (2); +create table t2 (a int); +insert into t2 values (2), (3); +create table t3 (a int); +insert into t3 values (3), (4); +create table t4 (a int); +insert into t4 values (1), (4); +create table t5 (a int); +insert into t5 values (2), (5); +# The LEFT/RIGHT-permutation UNION form is not a valid oracle +# for chained FULL JOINs: it over-approximates by emitting a +# right-side null-complement row for C rows that were already +# matched against the inner FJ's own null-complement row. Per +# SQL:2016 §7.10, (A FJ B) FJ C is left-associative and treats +# R1 = (A FJ B) as a single relation; a C row matched against +# any R1 row (including a null-complement row) is matched, and +# must not appear again as unmatched. Therefore the chained +# cases below have no UNION companion; the recorded result is +# the oracle. +# Three-level nested FULL JOINs. +select * from t1 +full join t2 on t1.a = t2.a +full join t3 on t2.a = t3.a; +a a a +1 NULL NULL +2 2 NULL +NULL 3 3 +NULL NULL 4 +# Four-level chained FULL JOINs. +select * from t1 +full join t2 on t1.a = t2.a +full join t3 on t2.a = t3.a +full join t4 on t3.a = t4.a; +a a a a +1 NULL NULL NULL +2 2 NULL NULL +NULL 3 3 NULL +NULL NULL 4 4 +NULL NULL NULL 1 +# Mixed FULL and INNER joins, deeply nested. +select * from t1 +inner join t2 on t1.a = t2.a +full join t3 on t2.a = t3.a +full join t4 on t3.a = t4.a; +a a a a +2 2 NULL NULL +NULL NULL 3 NULL +NULL NULL 4 4 +NULL NULL NULL 1 +drop table t1, t2, t3, t4, t5; +# ======================================================== +# Section 7: Mixed data types +# ======================================================== +create table t1 ( +id int, +str_val varchar(20), +dec_val decimal(10,2), +dt_val date +); +insert into t1 values +(1, 'hello', 10.50, '2024-01-01'), +(2, 'world', 20.75, '2024-06-15'), +(3, NULL, NULL, NULL); +create table t2 ( +id int, +str_val varchar(20), +dec_val decimal(10,2), +dt_val date +); +insert into t2 values +(2, 'WORLD', 20.75, '2024-06-15'), +(4, 'test', 99.99, '2025-12-31'), +(NULL, NULL, NULL, NULL); +# FULL JOIN on integer column with mixed-type rows. +select * from t1 full join t2 on t1.id = t2.id; +id str_val dec_val dt_val id str_val dec_val dt_val +1 hello 10.50 2024-01-01 NULL NULL NULL NULL +2 world 20.75 2024-06-15 2 WORLD 20.75 2024-06-15 +3 NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL 4 test 99.99 2025-12-31 +NULL NULL NULL NULL NULL NULL NULL NULL +select * from t1 left join t2 on t1.id = t2.id union select * from t1 right join t2 on t1.id = t2.id; +id str_val dec_val dt_val id str_val dec_val dt_val +1 hello 10.50 2024-01-01 NULL NULL NULL NULL +2 world 20.75 2024-06-15 2 WORLD 20.75 2024-06-15 +3 NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL 4 test 99.99 2025-12-31 +NULL NULL NULL NULL NULL NULL NULL NULL +# FULL JOIN on varchar column (case-sensitive match depends on collation). +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 full join t2 on t1.str_val = t2.str_val; +id1 sv1 id2 sv2 +1 hello NULL NULL +2 world 2 WORLD +3 NULL NULL NULL +NULL NULL 4 test +NULL NULL NULL NULL +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 left join t2 on t1.str_val = t2.str_val +union +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 right join t2 on t1.str_val = t2.str_val; +id1 sv1 id2 sv2 +1 hello NULL NULL +2 world 2 WORLD +3 NULL NULL NULL +NULL NULL 4 test +NULL NULL NULL NULL +# FULL JOIN on decimal column. +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 full join t2 on t1.dec_val = t2.dec_val; +id1 d1 id2 d2 +1 10.50 NULL NULL +2 20.75 2 20.75 +3 NULL NULL NULL +NULL NULL 4 99.99 +NULL NULL NULL NULL +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 left join t2 on t1.dec_val = t2.dec_val +union +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 right join t2 on t1.dec_val = t2.dec_val; +id1 d1 id2 d2 +1 10.50 NULL NULL +2 20.75 2 20.75 +3 NULL NULL NULL +NULL NULL 4 99.99 +NULL NULL NULL NULL +# FULL JOIN on date column. +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 full join t2 on t1.dt_val = t2.dt_val; +id1 dt1 id2 dt2 +1 2024-01-01 NULL NULL +2 2024-06-15 2 2024-06-15 +3 NULL NULL NULL +NULL NULL 4 2025-12-31 +NULL NULL NULL NULL +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 left join t2 on t1.dt_val = t2.dt_val +union +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 right join t2 on t1.dt_val = t2.dt_val; +id1 dt1 id2 dt2 +1 2024-01-01 NULL NULL +2 2024-06-15 2 2024-06-15 +3 NULL NULL NULL +NULL NULL 4 2025-12-31 +NULL NULL NULL NULL +# FULL JOIN with cross-type comparison (int vs decimal). +create table t3 (a int); +insert into t3 values (1), (2), (3); +create table t4 (a decimal(5,1)); +insert into t4 values (1.0), (2.5), (3.0); +select * from t3 full join t4 on t3.a = t4.a; +a a +1 1.0 +2 NULL +3 3.0 +NULL 2.5 +select * from t3 left join t4 on t3.a = t4.a union select * from t3 right join t4 on t3.a = t4.a; +a a +1 1.0 +2 NULL +3 3.0 +NULL 2.5 +# FULL JOIN with cross-type comparison (int vs varchar). +create table t5 (a varchar(10)); +insert into t5 values ('1'), ('2'), ('four'); +select * from t3 full join t5 on t3.a = t5.a; +a a +1 1 +2 2 +3 NULL +NULL four +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +select * from t3 left join t5 on t3.a = t5.a union select * from t3 right join t5 on t3.a = t5.a; +a a +1 1 +2 2 +3 NULL +NULL four +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +Warning 1292 Truncated incorrect DECIMAL value: 'four' +# FULL JOIN on multiple mixed-type columns simultaneously. +select * from t1 full join t2 +on t1.id = t2.id and t1.dec_val = t2.dec_val; +id str_val dec_val dt_val id str_val dec_val dt_val +1 hello 10.50 2024-01-01 NULL NULL NULL NULL +2 world 20.75 2024-06-15 2 WORLD 20.75 2024-06-15 +3 NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL 4 test 99.99 2025-12-31 +NULL NULL NULL NULL NULL NULL NULL NULL +select * from t1 left join t2 +on t1.id = t2.id and t1.dec_val = t2.dec_val +union +select * from t1 right join t2 +on t1.id = t2.id and t1.dec_val = t2.dec_val; +id str_val dec_val dt_val id str_val dec_val dt_val +1 hello 10.50 2024-01-01 NULL NULL NULL NULL +2 world 20.75 2024-06-15 2 WORLD 20.75 2024-06-15 +3 NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL 4 test 99.99 2025-12-31 +NULL NULL NULL NULL NULL NULL NULL NULL +drop table t1, t2, t3, t4, t5; +# ======================================================== +# Section 8: Aggregates with FULL JOIN +# ======================================================== +create table t1 (grp char(1), val int); +insert into t1 values ('a',10), ('a',20), ('b',30), ('c',40); +create table t2 (grp char(1), val int); +insert into t2 values ('b',100), ('c',200), ('c',300), ('d',400); +# COUNT and SUM over a FULL JOIN. +select coalesce(t1.grp, t2.grp) as grp, +count(*) as cnt, +sum(t1.val) as s1, +sum(t2.val) as s2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +grp cnt s1 s2 +a 2 30 NULL +b 1 30 100 +c 2 80 500 +d 1 NULL 400 +select coalesce(dt.grp1, dt.grp2) as grp, +count(*) as cnt, +sum(dt.val1) as s1, +sum(dt.val2) as s2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.grp, t1.val, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); +grp cnt s1 s2 +a 2 30 NULL +b 1 30 100 +c 2 80 500 +d 1 NULL 400 +# AVG and MAX over a FULL JOIN. +select coalesce(t1.grp, t2.grp) as grp, +avg(t1.val) as avg1, +max(t2.val) as max2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +grp avg1 max2 +a 15.0000 NULL +b 30.0000 100 +c 40.0000 300 +d NULL 400 +select coalesce(dt.grp1, dt.grp2) as grp, +avg(dt.val1) as avg1, +max(dt.val2) as max2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.grp, t1.val, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); +grp avg1 max2 +a 15.0000 NULL +b 30.0000 100 +c 40.0000 300 +d NULL 400 +# HAVING clause with FULL JOIN aggregate. +select coalesce(t1.grp, t2.grp) as grp, +count(*) as cnt +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp) +having count(*) > 1; +grp cnt +a 2 +c 2 +select coalesce(t1.grp, t2.grp) as grp, +count(*) as cnt from t1 +left join t2 on t1.grp = t2.grp group by +coalesce(t1.grp, t2.grp) having count(*) > 1 +union +select coalesce(t1.grp, t2.grp) as grp, +count(*) as cnt from t1 +right join t2 on t1.grp = t2.grp group by +coalesce(t1.grp, t2.grp) having count(*) > 1; +grp cnt +a 2 +c 2 +# COUNT(*) with no GROUP BY — total row count of the FULL JOIN. +select count(*) from t1 full join t2 on t1.grp = t2.grp; +count(*) +6 +select count(*) from (select t1.grp as g1, t1.val as v1, t2.grp as g2, t2.val as v2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.grp, t1.val, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt; +count(*) +6 +# GROUP_CONCAT over a FULL JOIN. +select coalesce(t1.grp, t2.grp) as grp, +group_concat(t1.val order by t1.val) as vals1, +group_concat(t2.val order by t2.val) as vals2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +grp vals1 vals2 +a 10,20 NULL +b 30 100 +c 40,40 200,300 +d NULL 400 +select coalesce(dt.grp1, dt.grp2) as grp, +group_concat(dt.val1 order by dt.val1) as vals1, +group_concat(dt.val2 order by dt.val2) as vals2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.grp, t1.val, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); +grp vals1 vals2 +a 10,20 NULL +b 30 100 +c 40,40 200,300 +d NULL 400 +drop table t1, t2; +# ======================================================== +# Section 9: Window functions with FULL JOIN +# +# Adapted from main.win: ROW_NUMBER, RANK, DENSE_RANK, +# LEAD/LAG, and aggregate windows (SUM/COUNT with frames) +# applied to FULL JOIN result sets. +# ======================================================== +create table t1 (a int, grp int, val int); +insert into t1 values (1,10,100), (2,10,200), (3,20,300), (4,20,400); +create table t2 (a int, grp int, val int); +insert into t2 values (3,20,3000), (4,20,4000), (5,30,5000), (6,30,6000); +# ROW_NUMBER() over a FULL JOIN ordered by the coalesced key. +select coalesce(t1.a, t2.a) as a, +row_number() over (order by coalesce(t1.a, t2.a)) as rn +from t1 full join t2 on t1.a = t2.a +order by a; +a rn +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +# Equivalent: row-number over LEFT UNION RIGHT. +select a, row_number() over (order by a) as rn +from (select coalesce(t1.a, t2.a) as a +from t1 left join t2 on t1.a = t2.a +union +select coalesce(t1.a, t2.a) +from t1 right join t2 on t1.a = t2.a) u +order by a; +a rn +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +# RANK() with PARTITION BY over FULL JOIN. +select coalesce(t1.grp, t2.grp) as grp, +coalesce(t1.val, 0) + coalesce(t2.val, 0) as v, +rank() over (partition by coalesce(t1.grp, t2.grp) +order by coalesce(t1.val, 0) + coalesce(t2.val, 0)) as rk +from t1 full join t2 on t1.a = t2.a; +grp v rk +10 100 1 +10 200 2 +20 3300 1 +20 4400 2 +30 5000 1 +30 6000 2 +select coalesce(t1grp, t2grp) as grp, +coalesce(t1val, 0) + coalesce(t2val, 0) as v, +rank() over (partition by coalesce(t1grp, t2grp) +order by coalesce(t1val, 0) + coalesce(t2val, 0)) as rk +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u; +grp v rk +10 100 1 +10 200 2 +20 3300 1 +20 4400 2 +30 5000 1 +30 6000 2 +# DENSE_RANK() over FULL JOIN. +select coalesce(t1.grp, t2.grp) as grp, +dense_rank() over (order by coalesce(t1.grp, t2.grp)) as dr +from t1 full join t2 on t1.a = t2.a; +grp dr +10 1 +10 1 +20 2 +20 2 +30 3 +30 3 +select coalesce(t1grp, t2grp) as grp, +dense_rank() over (order by coalesce(t1grp, t2grp)) as dr +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u; +grp dr +10 1 +10 1 +20 2 +20 2 +30 3 +30 3 +# LEAD() and LAG() over FULL JOIN. +select coalesce(t1.a, t2.a) as a, +lag(coalesce(t1.a, t2.a)) +over (order by coalesce(t1.a, t2.a)) as prev_a, +lead(coalesce(t1.a, t2.a)) +over (order by coalesce(t1.a, t2.a)) as next_a +from t1 full join t2 on t1.a = t2.a +order by a; +a prev_a next_a +1 NULL 2 +2 1 3 +3 2 4 +4 3 5 +5 4 6 +6 5 NULL +select coalesce(t1a, t2a) as a, +lag(coalesce(t1a, t2a)) +over (order by coalesce(t1a, t2a)) as prev_a, +lead(coalesce(t1a, t2a)) +over (order by coalesce(t1a, t2a)) as next_a +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u +order by a; +a prev_a next_a +1 NULL 2 +2 1 3 +3 2 4 +4 3 5 +5 4 6 +6 5 NULL +# SUM() window with rows-BETWEEN frame over FULL JOIN. +select coalesce(t1.a, t2.a) as a, +coalesce(t1.val, 0) + coalesce(t2.val, 0) as v, +sum(coalesce(t1.val, 0) + coalesce(t2.val, 0)) +over (order by coalesce(t1.a, t2.a) +rows between 1 preceding and 1 following) as window_sum +from t1 full join t2 on t1.a = t2.a +order by a; +a v window_sum +1 100 300 +2 200 3600 +3 3300 7900 +4 4400 12700 +5 5000 15400 +6 6000 11000 +select coalesce(t1a, t2a) as a, +coalesce(t1val, 0) + coalesce(t2val, 0) as v, +sum(coalesce(t1val, 0) + coalesce(t2val, 0)) +over (order by coalesce(t1a, t2a) +rows between 1 preceding and 1 following) as window_sum +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u +order by a; +a v window_sum +1 100 300 +2 200 3600 +3 3300 7900 +4 4400 12700 +5 5000 15400 +6 6000 11000 +# COUNT() window partitioned by group, ordered within group. +select coalesce(t1.grp, t2.grp) as grp, +coalesce(t1.a, t2.a) as a, +count(*) over (partition by coalesce(t1.grp, t2.grp) +order by coalesce(t1.a, t2.a) +rows between unbounded preceding and current row) as cnt +from t1 full join t2 on t1.a = t2.a; +grp a cnt +10 1 1 +10 2 2 +20 3 1 +20 4 2 +30 5 1 +30 6 2 +select coalesce(t1grp, t2grp) as grp, +coalesce(t1a, t2a) as a, +count(*) over (partition by coalesce(t1grp, t2grp) +order by coalesce(t1a, t2a) +rows between unbounded preceding and current row) as cnt +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u; +grp a cnt +10 1 1 +10 2 2 +20 3 1 +20 4 2 +30 5 1 +30 6 2 +# Window function combined with GROUP BY on the FULL JOIN. +# Exercises the AGGR_OP::end_send path after the null-complement +# pass completes (this previously asserted in create_sort_index). +select coalesce(t1.grp, t2.grp) as grp, +sum(coalesce(t1.val, 0) + coalesce(t2.val, 0)) as s, +rank() over (order by sum(coalesce(t1.val, 0) + coalesce(t2.val, 0))) as rk +from t1 full join t2 on t1.a = t2.a +group by coalesce(t1.grp, t2.grp); +grp s rk +10 300 1 +20 7700 2 +30 11000 3 +select coalesce(t1grp, t2grp) as grp, +sum(coalesce(t1val, 0) + coalesce(t2val, 0)) as s, +rank() over (order by sum(coalesce(t1val, 0) + coalesce(t2val, 0))) as rk +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, +t2.a as t2a, t2.grp as t2grp, t2.val as t2val +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val +from t1 right join t2 on t1.a = t2.a) u +group by coalesce(t1grp, t2grp); +grp s rk +10 300 1 +20 7700 2 +30 11000 3 +drop table t1, t2; +# ======================================================== +# Section 10: CTEs +# ======================================================== +create table t1 (id int, val varchar(10)); +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int, val varchar(10)); +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +# Simple CTE wrapping a FULL JOIN. +with fj as ( +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 full join t2 on t1.id = t2.id +) +select * from fj; +id1 v1 id2 v2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select * from t1 left join t2 on t1.id = t2.id union select * from t1 right join t2 on t1.id = t2.id; +id val id val +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +# CTE on the left side of a FULL JOIN. +with vals as (select id, val from t1 where id <= 2) +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 full join t2 on v1.id = t2.id; +id1 val1 id2 val2 +1 a NULL NULL +2 b 2 x +NULL NULL 3 y +NULL NULL 4 z +with vals as (select id, val from t1 where id <= 2) +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 left join t2 on v1.id = t2.id +union +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 right join t2 on v1.id = t2.id; +id1 val1 id2 val2 +1 a NULL NULL +2 b 2 x +NULL NULL 3 y +NULL NULL 4 z +# Recursive CTE used in a FULL JOIN. +with recursive seq as ( +select 1 as n +union all +select n + 1 from seq where n < 4 +) +select s1.n as n1, s2.id as n2 +from seq s1 full join t2 s2 on s1.n = s2.id; +n1 n2 +1 NULL +2 2 +3 3 +4 4 +with recursive seq as ( +select 1 as n +union all +select n + 1 from seq where n < 4 +) +select s1.n as n1, s2.id as n2 +from seq s1 left join t2 s2 on s1.n = s2.id +union +select s1.n as n1, s2.id as n2 +from seq s1 right join t2 s2 on s1.n = s2.id; +n1 n2 +1 NULL +2 2 +3 3 +4 4 +# CTE on the left side of a FULL JOIN with filtering. +with left_cte as (select * from t1 where id in (1,2)) +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l full join t2 on l.id = t2.id; +lid lval rid rval +1 a NULL NULL +2 b 2 x +NULL NULL 3 y +NULL NULL 4 z +with left_cte as (select * from t1 where id in (1,2)) +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l left join t2 on l.id = t2.id +union +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l right join t2 on l.id = t2.id; +lid lval rid rval +1 a NULL NULL +2 b 2 x +NULL NULL 3 y +NULL NULL 4 z +drop table t1, t2; +# ======================================================== +# Section 11: Views over FULL JOIN +# ======================================================== +create table t1 (a int, b int); +insert into t1 values (1,10), (2,20), (3,30); +create table t2 (a int, b int); +insert into t2 values (2,200), (3,300), (4,400); +# Simple view over a FULL JOIN. +create view v_full as +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +select * from v_full; +a1 b1 a2 b2 +1 10 NULL NULL +2 20 2 200 +3 30 3 300 +NULL NULL 4 400 +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +a b a b +1 10 NULL NULL +2 20 2 200 +3 30 3 300 +NULL NULL 4 400 +# Query the view with additional filtering. +select * from v_full where a1 is not null and a2 is not null; +a1 b1 a2 b2 +2 20 2 200 +3 30 3 300 +select * from t1 inner join t2 on t1.a = t2.a; +a b a b +2 20 2 200 +3 30 3 300 +# View joined with another table via FULL JOIN. +create table t3 (a int, c varchar(10)); +insert into t3 values (1,'x'), (2,'y'), (4,'z'); +select v_full.a1, v_full.a2, t3.c +from v_full full join t3 on coalesce(v_full.a1, v_full.a2) = t3.a; +a1 a2 c +1 NULL x +2 2 y +3 3 NULL +NULL 4 z +select v_full.a1, v_full.a2, t3.c +from v_full left join t3 on coalesce(v_full.a1, v_full.a2) = t3.a +union +select v_full.a1, v_full.a2, t3.c +from v_full right join t3 on coalesce(v_full.a1, v_full.a2) = t3.a; +a1 a2 c +1 NULL x +2 2 y +3 3 NULL +NULL 4 z +# View that filters the FULL JOIN result. +create view v_full_filtered as +select t1.a as a1, t2.a as a2 +from t1 full join t2 on t1.a = t2.a +where t1.a is not null; +select * from v_full_filtered; +a1 a2 +1 NULL +2 2 +3 3 +select t1.a as a1, t2.a as a2 from t1 left join t2 on t1.a = t2.a; +a1 a2 +1 NULL +2 2 +3 3 +drop view v_full, v_full_filtered; +drop table t1, t2, t3; +# ======================================================== +# Section 12: Prepared statements +# ======================================================== +create table t1 (a int, b varchar(10)); +insert into t1 values (1,'one'), (2,'two'), (3,'three'); +create table t2 (a int, b varchar(10)); +insert into t2 values (2,'TWO'), (3,'THREE'), (4,'FOUR'); +# Basic prepared statement with FULL JOIN. +prepare stmt1 from +'select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 full join t2 on t1.a = t2.a'; +execute stmt1; +a1 b1 a2 b2 +1 one NULL NULL +2 two 2 TWO +3 three 3 THREE +NULL NULL 4 FOUR +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +a b a b +1 one NULL NULL +2 two 2 TWO +3 three 3 THREE +NULL NULL 4 FOUR +# Re-execute to verify PS re-execution stability. +execute stmt1; +a1 b1 a2 b2 +1 one NULL NULL +2 two 2 TWO +3 three 3 THREE +NULL NULL 4 FOUR +deallocate prepare stmt1; +# Parameter in the ON clause. +prepare stmt2 from +'select t1.a as a1, t2.a as a2 + from t1 full join t2 on t1.a = t2.a and t1.a > ?'; +set @threshold = 1; +execute stmt2 using @threshold; +a1 a2 +1 NULL +2 2 +3 3 +NULL 4 +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a and t1.a > 1 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a and t1.a > 1; +a1 a2 +1 NULL +2 2 +3 3 +NULL 4 +# Re-execute with a different parameter value. +set @threshold = 2; +execute stmt2 using @threshold; +a1 a2 +1 NULL +2 NULL +3 3 +NULL 2 +NULL 4 +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a and t1.a > 2 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a and t1.a > 2; +a1 a2 +1 NULL +2 NULL +3 3 +NULL 2 +NULL 4 +deallocate prepare stmt2; +# Parameter in the WHERE clause. +prepare stmt3 from +'select t1.a, t2.a + from t1 full join t2 on t1.a = t2.a + where t1.a is not null or t2.a > ?'; +set @minval = 3; +execute stmt3 using @minval; +a a +1 NULL +2 2 +3 3 +NULL 4 +execute stmt3 using @minval; +a a +1 NULL +2 2 +3 3 +NULL 4 +deallocate prepare stmt3; +drop table t1, t2; +# ======================================================== +# Section 13: Stored procedures +# ======================================================== +create table t1 (a int, b varchar(20)); +insert into t1 values (1,'alpha'), (2,'beta'), (3,'gamma'); +create table t2 (a int, b varchar(20)); +insert into t2 values (2,'BETA'), (4,'DELTA'), (5,'EPSILON'); +# SP that performs a FULL JOIN. +create procedure sp_full_join() +begin +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +end| +# SP with a FULL JOIN and a parameter. +create procedure sp_full_join_param(in min_a int) +begin +select t1.a as a1, t2.a as a2 +from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) >= min_a; +end| +# SP using FULL JOIN with INSERT ... SELECT. +create procedure sp_full_join_insert() +begin +create temporary table t3 (a1 int, b1 varchar(20), a2 int, b2 varchar(20)); +insert into t3 +select t1.a, t1.b, t2.a, t2.b +from t1 full join t2 on t1.a = t2.a; +select * from t3; +drop temporary table t3; +end| +call sp_full_join(); +a1 b1 a2 b2 +1 alpha NULL NULL +2 beta 2 BETA +3 gamma NULL NULL +NULL NULL 4 DELTA +NULL NULL 5 EPSILON +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +a b a b +1 alpha NULL NULL +2 beta 2 BETA +3 gamma NULL NULL +NULL NULL 4 DELTA +NULL NULL 5 EPSILON +call sp_full_join_param(3); +a1 a2 +3 NULL +NULL 4 +NULL 5 +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) >= 3 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) >= 3; +a1 a2 +3 NULL +NULL 4 +NULL 5 +# Call the SPs twice to test re-execution. +call sp_full_join(); +a1 b1 a2 b2 +1 alpha NULL NULL +2 beta 2 BETA +3 gamma NULL NULL +NULL NULL 4 DELTA +NULL NULL 5 EPSILON +call sp_full_join_param(1); +a1 a2 +1 NULL +2 2 +3 NULL +NULL 4 +NULL 5 +call sp_full_join_insert(); +a1 b1 a2 b2 +1 alpha NULL NULL +2 beta 2 BETA +3 gamma NULL NULL +NULL NULL 4 DELTA +NULL NULL 5 EPSILON +drop procedure sp_full_join; +drop procedure sp_full_join_param; +drop procedure sp_full_join_insert; +drop table t1, t2; +# ======================================================== +# Section 14: Subqueries and semijoins with FULL JOIN +# ======================================================== +create table t1 (a int, b int); +insert into t1 values (1,10), (2,20), (3,30); +create table t2 (a int, b int); +insert into t2 values (2,200), (3,300), (4,400); +create table t3 (a int); +insert into t3 values (1), (3), (5); +# IN subquery (semijoin) on the result of a FULL JOIN. +select * from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3); +a b a b +1 10 NULL NULL +3 30 3 300 +select * from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3) +union +select * from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3); +a b a b +1 10 NULL NULL +3 30 3 300 +# EXISTS subquery filtering a FULL JOIN. +select * from t1 full join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)); +a b a b +1 10 NULL NULL +3 30 3 300 +select * from t1 left join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)) +union +select * from t1 right join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)); +a b a b +1 10 NULL NULL +3 30 3 300 +# NOT IN (anti-semijoin) with a FULL JOIN. +select * from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3); +a b a b +2 20 2 200 +NULL NULL 4 400 +select * from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3) +union +select * from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3); +a b a b +2 20 2 200 +NULL NULL 4 400 +# FULL JOIN inside a subquery used as a semijoin predicate. +select * from t3 +where t3.a in ( +select coalesce(t1.a, t2.a) +from t1 full join t2 on t1.a = t2.a +); +a +1 +3 +select * from t3 +where t3.a in ( +select coalesce(t1.a, t2.a) +from t1 left join t2 on t1.a = t2.a +union +select coalesce(t1.a, t2.a) +from t1 right join t2 on t1.a = t2.a +); +a +1 +3 +# Correlated subquery with FULL JOIN. +select * from t3 +where exists ( +select 1 from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) = t3.a +); +a +1 +3 +select * from t3 +where exists ( +select 1 from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) = t3.a +union +select 1 from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) = t3.a +); +a +1 +3 +drop table t1, t2, t3; +# ======================================================== +# Section 15: Indexed access (PK, secondary, composite, unique) +# +# Exercises JT_EQ_REF / JT_REF access paths on the right +# side of a FULL JOIN, including NULLable unique keys. +# ======================================================== +# Primary key join (JT_EQ_REF). +create table t1 (id int primary key, val varchar(10)); +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int primary key, val varchar(10)); +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 full join t2 on t1.id = t2.id; +id1 v1 id2 v2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 right join t2 on t1.id = t2.id; +id1 v1 id2 v2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +# PK join with WHERE filter. +select t1.id as id1, t2.id as id2 +from t1 full join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2; +id1 id2 +3 3 +NULL 4 +select t1.id as id1, t2.id as id2 +from t1 left join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2 +union +select t1.id as id1, t2.id as id2 +from t1 right join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2; +id1 id2 +3 3 +NULL 4 +# PK join with aggregate. +select count(*) from t1 full join t2 on t1.id = t2.id; +count(*) +4 +select count(*) from (select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id, t1.val, t2.id, t2.val +from t1 right join t2 on t1.id = t2.id) dt; +count(*) +4 +drop table t1, t2; +# Secondary index join (JT_REF) with duplicates. +create table t1 (id int, grp int, val varchar(10), key(grp)); +insert into t1 values (1,10,'a'), (2,20,'b'), (3,20,'c'), (4,30,'d'); +create table t2 (id int, grp int, val varchar(10), key(grp)); +insert into t2 values (5,20,'x'), (6,30,'y'), (7,30,'z'), (8,40,'w'); +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 full join t2 on t1.grp = t2.grp; +id1 g1 id2 g2 +1 10 NULL NULL +2 20 5 20 +3 20 5 20 +4 30 6 30 +4 30 7 30 +NULL NULL 8 40 +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 right join t2 on t1.grp = t2.grp; +id1 g1 id2 g2 +1 10 NULL NULL +2 20 5 20 +3 20 5 20 +4 30 6 30 +4 30 7 30 +NULL NULL 8 40 +select count(*) from t1 full join t2 on t1.grp = t2.grp; +count(*) +6 +select count(*) from (select t1.id as id1, t1.grp as g1, t1.val as v1, +t2.id as id2, t2.grp as g2, t2.val as v2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.id, t1.grp, t1.val, t2.id, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt; +count(*) +6 +drop table t1, t2; +# Composite index join. +create table t1 (a int, b int, val varchar(10), primary key(a, b)); +insert into t1 values (1,1,'p'), (1,2,'q'), (2,1,'r'); +create table t2 (a int, b int, val varchar(10), primary key(a, b)); +insert into t2 values (1,2,'s'), (2,1,'t'), (2,2,'u'); +select t1.a as a1, t1.b as b1, t1.val as v1, +t2.a as a2, t2.b as b2, t2.val as v2 +from t1 full join t2 on t1.a = t2.a and t1.b = t2.b; +a1 b1 v1 a2 b2 v2 +1 1 p NULL NULL NULL +1 2 q 1 2 s +2 1 r 2 1 t +NULL NULL NULL 2 2 u +select t1.a as a1, t1.b as b1, t1.val as v1, +t2.a as a2, t2.b as b2, t2.val as v2 +from t1 left join t2 on t1.a = t2.a and t1.b = t2.b +union +select t1.a as a1, t1.b as b1, t1.val as v1, +t2.a as a2, t2.b as b2, t2.val as v2 +from t1 right join t2 on t1.a = t2.a and t1.b = t2.b; +a1 b1 v1 a2 b2 v2 +1 1 p NULL NULL NULL +1 2 q 1 2 s +2 1 r 2 1 t +NULL NULL NULL 2 2 u +# Composite index: join on partial key (first column only). +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 1 1 2 +1 2 1 2 +2 1 2 1 +2 1 2 2 +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 1 1 2 +1 2 1 2 +2 1 2 1 +2 1 2 2 +drop table t1, t2; +# UNIQUE index with NULLable column. +create table t1 (id int auto_increment primary key, val int, unique key(val)); +insert into t1 (val) values (1), (NULL), (3), (NULL); +create table t2 (id int auto_increment primary key, val int, unique key(val)); +insert into t2 (val) values (2), (3), (NULL), (NULL); +select t1.val as v1, t2.val as v2 +from t1 full join t2 on t1.val = t2.val; +v1 v2 +1 NULL +3 3 +NULL 2 +NULL NULL +NULL NULL +NULL NULL +NULL NULL +select t1.val as v1, t2.val as v2 +from t1 left join t2 on t1.val = t2.val +union +select t1.val as v1, t2.val as v2 +from t1 right join t2 on t1.val = t2.val; +v1 v2 +1 NULL +3 3 +NULL 2 +NULL NULL +drop table t1, t2; +# ======================================================== +# Section 16: Storage engines (MyISAM, Aria, mixed) +# +# The null-complement rescan must work regardless of +# underlying storage engine and across mixed-engine joins +# (different rowid formats in the weedout temp table). +# ======================================================== +# Both sides MyISAM. +create table t1 (a int, b varchar(10)) engine=MyISAM; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select count(*) from t1 full join t2 on t1.a = t2.a; +count(*) +4 +select count(*) from (select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.b, t2.a, t2.b +from t1 right join t2 on t1.a = t2.a) dt; +count(*) +4 +drop table t1, t2; +# Both sides Aria. +create table t1 (a int, b varchar(10)) engine=Aria; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=Aria; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select count(*) from t1 full join t2 on t1.a = t2.a; +count(*) +4 +select count(*) from (select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a, t1.b, t2.a, t2.b +from t1 right join t2 on t1.a = t2.a) dt; +count(*) +4 +drop table t1, t2; +# Mixed engines: InnoDB and MyISAM. +create table t1 (a int, b varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +# InnoDB on left, MyISAM on right. +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +# MyISAM on left, InnoDB on right. +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 full join t1 on t2.a = t1.a; +a1 b1 a2 b2 +2 x 2 b +3 y 3 c +4 z NULL NULL +NULL NULL 1 a +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 left join t1 on t2.a = t1.a +union +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 right join t1 on t2.a = t1.a; +a1 b1 a2 b2 +2 x 2 b +3 y 3 c +4 z NULL NULL +NULL NULL 1 a +drop table t1, t2; +# Mixed engines: InnoDB and Aria. +create table t1 (a int, b varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=Aria; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +# InnoDB on left, Aria on right. +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; +a1 b1 a2 b2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +# Aria on left, InnoDB on right. +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 full join t1 on t2.a = t1.a; +a1 b1 a2 b2 +2 x 2 b +3 y 3 c +4 z NULL NULL +NULL NULL 1 a +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 left join t1 on t2.a = t1.a +union +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 right join t1 on t2.a = t1.a; +a1 b1 a2 b2 +2 x 2 b +3 y 3 c +4 z NULL NULL +NULL NULL 1 a +drop table t1, t2; +# Three-way mixed engine FULL JOIN. +# No UNION companion: see the note above the chained-FULL-JOIN +# section — the LEFT/RIGHT permutation UNION over-approximates +# for chained FULL JOINs. +create table t1 (a int) engine=InnoDB; +insert into t1 values (1), (2), (3); +create table t2 (a int) engine=MyISAM; +insert into t2 values (2), (3), (4); +create table t3 (a int) engine=Aria; +insert into t3 values (3), (4), (5); +select t1.a as a1, t2.a as a2, t3.a as a3 +from t1 +full join t2 on t1.a = t2.a +full join t3 on t2.a = t3.a; +a1 a2 a3 +1 NULL NULL +2 2 NULL +3 3 3 +NULL 4 4 +NULL NULL 5 +drop table t1, t2, t3; +# Indexed mixed-engine FULL JOIN chain. +create table t1 (id int primary key, val varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int primary key, val varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +create table t3 (id int primary key, val varchar(10)) engine=Aria; +insert into t3 values (3,'p'), (4,'q'), (5,'r'); +# InnoDB PK full join MyISAM PK. +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 full join t2 on t1.id = t2.id; +id1 v1 id2 v2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 right join t2 on t1.id = t2.id; +id1 v1 id2 v2 +1 a NULL NULL +2 b 2 x +3 c 3 y +NULL NULL 4 z +# MyISAM PK full join Aria PK. +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 full join t3 on t2.id = t3.id; +id1 v1 id2 v2 +2 x NULL NULL +3 y 3 p +4 z 4 q +NULL NULL 5 r +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 left join t3 on t2.id = t3.id +union +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 right join t3 on t2.id = t3.id; +id1 v1 id2 v2 +2 x NULL NULL +3 y 3 p +4 z 4 q +NULL NULL 5 r +# Three-way: InnoDB PK, MyISAM PK, Aria PK. +# No UNION companion: see the note above the chained-FULL-JOIN +# section — the LEFT/RIGHT permutation UNION over-approximates +# for chained FULL JOINs. +select t1.id as id1, t2.id as id2, t3.id as id3 +from t1 +full join t2 on t1.id = t2.id +full join t3 on t2.id = t3.id; +id1 id2 id3 +1 NULL NULL +2 2 NULL +3 3 3 +NULL 4 4 +NULL NULL 5 +# Indexed mixed-engine with WHERE filter. +select t1.id as id1, t2.id as id2 +from t1 full join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3; +id1 id2 +2 2 +3 3 +select t1.id as id1, t2.id as id2 +from t1 left join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3 +union +select t1.id as id1, t2.id as id2 +from t1 right join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3; +id1 id2 +2 2 +3 3 +# Indexed mixed-engine with aggregate. +select count(*) from t1 full join t2 on t1.id = t2.id; +count(*) +4 +select count(*) from (select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id, t1.val, t2.id, t2.val +from t1 right join t2 on t1.id = t2.id) dt; +count(*) +4 +drop table t1, t2, t3; +# ======================================================== +# Section 17: Complex feature combinations +# +# Queries that stress FULL JOIN alongside other features at +# the same time: semi-joins with ranges, FULL-JOIN-in-FULL- +# JOIN inside a semi-join, CTEs combined with window +# functions, and HAVING over aggregates with FULL JOIN. +# ======================================================== +create table t3O (x int); +insert into t3O values (1), (2), (3), (4), (5), (6), (7); +create table t3L (k int, tag varchar(8)); +insert into t3L values (1,'L1'), (2,'L2'), (3,'L3'), (4,'L4'); +create table t3R (k int, tag varchar(8)); +insert into t3R values (3,'R3'), (4,'R4'), (5,'R5'), (6,'R6'); +create table t3M (k int, tag varchar(8)); +insert into t3M values (2,'M2'), (5,'M5'), (7,'M7'); +# 17.1 FULL JOIN inside a materialized semijoin. +# Exercises get_allowed_nj_tables() with emb_sjm_nest set: +# the FULL JOIN adjacency check must apply inside the SJM +# nest so that siblings cannot interleave between partners. +set @save_optimizer_switch= @@optimizer_switch; +set optimizer_switch='materialization=on,semijoin=on'; +select * from t3O +where t3O.x in ( +select coalesce(t3L.k, t3R.k) from t3L full join t3R on t3L.k = t3R.k +); +x +1 +2 +3 +4 +5 +6 +select * from t3O +where t3O.x in ( +select coalesce(t3L.k, t3R.k) from t3L left join t3R on t3L.k = t3R.k +union +select coalesce(t3L.k, t3R.k) from t3L right join t3R on t3L.k = t3R.k +); +x +1 +2 +3 +4 +5 +6 +# 17.2 FULL JOIN in semijoin with a range predicate on the +# FULL JOIN's coalesced key. +select * from t3O +where t3O.x in ( +select coalesce(t3L.k, t3R.k) c +from t3L full join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 2 and 5 +); +x +2 +3 +4 +5 +select * from t3O +where t3O.x in ( +select coalesce(t3L.k, t3R.k) c +from t3L left join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 2 and 5 +union +select coalesce(t3L.k, t3R.k) c +from t3L right join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 2 and 5 +); +x +2 +3 +4 +5 +# 17.3 Nested FULL JOINs (a FULL JOIN of a FULL JOIN) inside +# a semijoin. +select * from t3O +where t3O.x in ( +select coalesce(coalesce(t3L.k, t3R.k), t3M.k) +from (t3L full join t3R on t3L.k = t3R.k) +full join t3M on coalesce(t3L.k, t3R.k) = t3M.k +); +x +1 +2 +3 +4 +5 +6 +7 +select * from t3O +where t3O.x in ( +select coalesce(coalesce(t3L.k, t3R.k), t3M.k) +from (t3L left join t3R on t3L.k = t3R.k) +left join t3M on coalesce(t3L.k, t3R.k) = t3M.k +union +select coalesce(coalesce(t3L.k, t3R.k), t3M.k) +from (t3L right join t3R on t3L.k = t3R.k) +left join t3M on coalesce(t3L.k, t3R.k) = t3M.k +union +select coalesce(coalesce(t3L.k, t3R.k), t3M.k) +from (t3L left join t3R on t3L.k = t3R.k) +right join t3M on coalesce(t3L.k, t3R.k) = t3M.k +union +select coalesce(coalesce(t3L.k, t3R.k), t3M.k) +from (t3L right join t3R on t3L.k = t3R.k) +right join t3M on coalesce(t3L.k, t3R.k) = t3M.k +); +x +1 +2 +3 +4 +5 +6 +7 +set optimizer_switch= @save_optimizer_switch; +# 17.4 CTE that produces a FULL JOIN result, consumed by a +# window function in the outer query. +with fj as ( +select coalesce(t3L.k, t3R.k) as k, +t3L.tag as ltag, +t3R.tag as rtag +from t3L full join t3R on t3L.k = t3R.k +) +select k, ltag, rtag, +row_number() over (order by k) as rn, +count(*) over (order by k rows between unbounded preceding and current row) as running_cnt +from fj; +k ltag rtag rn running_cnt +1 L1 NULL 1 1 +2 L2 NULL 2 2 +3 L3 R3 3 3 +4 L4 R4 4 4 +5 NULL R5 5 5 +6 NULL R6 6 6 +with fj as ( +select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag +from t3L left join t3R on t3L.k = t3R.k +union +select coalesce(t3L.k, t3R.k), t3L.tag, t3R.tag +from t3L right join t3R on t3L.k = t3R.k +) +select k, ltag, rtag, +row_number() over (order by k) as rn, +count(*) over (order by k rows between unbounded preceding and current row) as running_cnt +from fj; +k ltag rtag rn running_cnt +1 L1 NULL 1 1 +2 L2 NULL 2 2 +3 L3 R3 3 3 +4 L4 R4 4 4 +5 NULL R5 5 5 +6 NULL R6 6 6 +# 17.5 FULL JOIN + HAVING + ORDER BY + aggregate window. +select coalesce(t3L.k, t3R.k) as k, +count(*) as cnt, +rank() over (order by count(*) desc) as rk +from t3L full join t3R on t3L.k = t3R.k +group by coalesce(t3L.k, t3R.k) +having count(*) >= 1 +order by rk, k; +k cnt rk +1 1 1 +2 1 1 +3 1 1 +4 1 1 +5 1 1 +6 1 1 +select coalesce(t3Lk, t3Rk) as k, +count(*) as cnt, +rank() over (order by count(*) desc) as rk +from (select t3L.k as t3Lk, t3L.tag as ltag, t3R.k as t3Rk, t3R.tag as rtag +from t3L left join t3R on t3L.k = t3R.k +union +select t3L.k, t3L.tag, t3R.k, t3R.tag +from t3L right join t3R on t3L.k = t3R.k) dt +group by coalesce(t3Lk, t3Rk) +having count(*) >= 1 +order by rk, k; +k cnt rk +1 1 1 +2 1 1 +3 1 1 +4 1 1 +5 1 1 +6 1 1 +# 17.6 FULL JOIN + EXISTS subquery that itself contains a +# FULL JOIN, all filtered by a range condition. +select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag +from t3L full join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 1 and 5 +and exists ( +select 1 from t3L l2 full join t3M on l2.k = t3M.k +where coalesce(l2.k, t3M.k) = coalesce(t3L.k, t3R.k) +); +k ltag rtag +1 L1 NULL +2 L2 NULL +3 L3 R3 +4 L4 R4 +5 NULL R5 +select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag +from t3L full join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 1 and 5 +and coalesce(t3L.k, t3R.k) in ( +select coalesce(l2.k, t3M.k) from t3L l2 left join t3M on l2.k = t3M.k +union +select coalesce(l2.k, t3M.k) from t3L l2 right join t3M on l2.k = t3M.k +); +k ltag rtag +1 L1 NULL +2 L2 NULL +3 L3 R3 +4 L4 R4 +5 NULL R5 +drop table t3O, t3L, t3R, t3M; +# ======================================================== +# Section 18: Regressions +# +# Specific bugs that were fixed; kept as targeted cases so +# they don't silently regress. +# ======================================================== +# FULL JOIN with GROUP BY: previously crashed on the +# create_sort_index assertion (filesort_result != 0) when +# AGGR_OP::end_send was called twice. +create table t1 (grp char(1), val int); +insert into t1 values ('a',10), ('a',20), ('b',30), ('c',40); +create table t2 (grp char(1), val int); +insert into t2 values ('b',100), ('c',200), ('c',300), ('d',400); +select coalesce(t1.grp, t2.grp) as grp, +count(*) as cnt, +sum(t1.val) as s1, +sum(t2.val) as s2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +grp cnt s1 s2 +a 2 30 NULL +b 1 30 100 +c 2 80 500 +d 1 NULL 400 +select coalesce(dt.grp1, dt.grp2) as grp, +count(*) as cnt, +sum(dt.val1) as s1, +sum(dt.val2) as s2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.grp, t1.val, t2.grp, t2.val +from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); +grp cnt s1 s2 +a 2 30 NULL +b 1 30 100 +c 2 80 500 +d 1 NULL 400 +drop table t1, t2; +# ==================================================================== +# Section 19: Right side of FULL JOIN must be a base table +# ==================================================================== +# +# Derived tables, views, and subqueries on the right side of a FULL +# JOIN are not supported and must produce an error. +create table t1 (a int); +create table t2 (a int); +# Derived table on the right side of a simple FULL JOIN +select * from t1 full join (select * from t2) dt on t1.a = dt.a; +ERROR HY000: FULL JOIN is only supported with base tables on the right side; 'dt' is not a base table +# View on the right side of a simple FULL JOIN +create view v1 as select * from t2; +select * from t1 full join v1 on t1.a = v1.a; +ERROR HY000: FULL JOIN is only supported with base tables on the right side; 'v1' is not a base table +drop view v1; +# Derived table on the right side of a nested FULL JOIN +create table t3 (a int); +select * from t1 full join t2 on t1.a = t2.a +full join (select * from t3) dt on t2.a = dt.a; +ERROR HY000: FULL JOIN is only supported with base tables on the right side; 'dt' is not a base table +drop table t1, t2, t3; +# End of 12.3 tests diff --git a/mysql-test/main/full_join.test b/mysql-test/main/full_join.test new file mode 100644 index 0000000000000..82d36dfbaea0c --- /dev/null +++ b/mysql-test/main/full_join.test @@ -0,0 +1,1707 @@ +--source include/have_innodb.inc +--source include/have_aria.inc + +--echo # +--echo # MDEV-37932 / MDEV-39014: FULL [OUTER] JOIN +--echo # +--echo # Tests are grouped by feature. Within each group, a FULL JOIN +--echo # query is generally paired with an equivalent LEFT JOIN UNION +--echo # RIGHT JOIN formulation to verify correctness. +--echo # + +--echo # ======================================================== +--echo # Section 1: Parser and syntax acceptance +--echo # +--echo # FULL JOIN, FULL OUTER JOIN, NATURAL FULL [OUTER] JOIN, and +--echo # their appearance inside views, derived tables, UNIONs, and +--echo # CTEs. +--echo # ======================================================== + +create table t1 (a int); +insert into t1 (a) values (1),(2); +create table t2 (a int); +insert into t2 (a) values (1),(3); +create table t3 (a int); +insert into t3 (a) values (1),(4); + +--echo # Basic FULL [OUTER] JOIN syntax. +select * from t1 full join t2 on t1.a = t2.a; +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; +explain extended select * from t1 full join t2 on t1.a = t2.a; + +select * from t1 full outer join t2 on t1.a = t2.a; +select * from t1 left outer join t2 on t1.a = t2.a union select * from t1 right outer join t2 on t1.a = t2.a; +explain extended select * from t1 full outer join t2 on t1.a = t2.a; + +--echo # NATURAL FULL [OUTER] JOIN. +select * from t1 natural full outer join t2; +select * from t1 natural left outer join t2 union select * from t1 natural right outer join t2; +explain extended select * from t1 natural full outer join t2; + +select * from t1 natural full join t2; +select * from t1 natural left join t2 union select * from t1 natural right join t2; +explain extended select * from t1 natural full join t2; + +--echo # FULL JOIN inside a view. +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; +select t1.a as t1a, t2.a as t2a from t1 left join t2 on t1.a = t2.a union select t1.a as t1a, t2.a as t2a from t1 right join t2 on t1.a = t2.a; +explain extended select * from v1; +drop view v1; + +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; +select t1.a as t1a, t2.a as t2a from t1 left outer join t2 on t1.a = t2.a union select t1.a as t1a, t2.a as t2a from t1 right outer join t2 on t1.a = t2.a; +explain extended select * from v1; +drop view v1; + +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full join t2; +select * from v1; +select t1.a as t1a, t2.a as t2a from t1 natural left join t2 union select t1.a as t1a, t2.a as t2a from t1 natural right join t2; +explain extended select * from v1; +drop view v1; + +create view v1 as select t1.a as t1a, t2.a as t2a from t1 natural full outer join t2; +select * from v1; +select t1.a as t1a, t2.a as t2a from t1 natural left outer join t2 union select t1.a as t1a, t2.a as t2a from t1 natural right outer join t2; +explain extended select * from v1; +drop view v1; + +--echo # FULL JOIN inside a derived table combined with UNION. +select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; +select * from (select t1.a from t1 left join t2 on t1.a = t2.a union select t1.a from t1 right join t2 on t1.a = t2.a union select * from t1) dt; + +select * from (select t1.a from t1 full outer join t2 on t1.a = t2.a union select * from t1) dt; +select * from (select t1.a from t1 left outer join t2 on t1.a = t2.a union select t1.a from t1 right outer join t2 on t1.a = t2.a union select * from t1) dt; + +select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; +select * from (select t1.a from t1 natural left join t2 union select t1.a from t1 natural right join t2 union select * from t1) dt; +explain extended select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; + +select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; +select * from (select t1.a from t1 natural left outer join t2 union select t1.a from t1 natural right outer join t2 union select * from t1) dt; +explain extended select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; + +--echo # FULL JOIN inside a CTE. +with cte as (select t1.a from t1 natural full join t2) select * from cte; +with cte as (select t1.a from t1 natural left join t2 union select t1.a from t1 natural right join t2) select * from cte; +explain extended with cte as (select t1.a from t1 natural full join t2) select * from cte; + +--echo # FULL JOIN referencing a missing table must error cleanly. +--error ER_NO_SUCH_TABLE +select * from t1, t2 full join t_not_exist on t2.c=t_not_exist.e and t_not_exist.f=t1.a; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 full outer join t_not_exist on t2.c=t_not_exist.e and t_not_exist.f=t1.a; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 natural full join t_not_exist; + +--error ER_NO_SUCH_TABLE +select * from t1, t2 natural full outer join t_not_exist; + +--echo # FULL JOIN where one operand is a derived table (on the left). +select * from (select * from t1) dt natural full join t2; +select * from (select * from t1) dt natural left join t2 union select * from (select * from t1) dt natural right join t2; + +select * from (select * from t2) du natural full join t1; +select * from (select * from t2) du natural left join t1 union select * from (select * from t2) du natural right join t1; + +--echo # FULL JOIN with a constant ON clause. +select * from t1 full join t2 on true; +select * from t1 left join t2 on true union select * from t1 right join t2 on true; + + +--echo # ======================================================== +--echo # Section 2: Basic FULL JOIN with nested joins on the left +--echo # +--echo # Each FULL JOIN query is followed by an equivalent +--echo # LEFT/RIGHT/UNION formulation; the two must match. +--echo # ======================================================== + +select * from t1 inner join t2 full join t3 on t1.a=t3.a; +select * from t1 inner join t2 left join t3 on t1.a=t3.a union select * from t1 inner join t2 right join t3 on t1.a=t3.a; + +select * from t1 inner join t2 on t1.a=t2.a full join t3 on t1.a=t3.a; +select * from t1 inner join t2 on t1.a=t2.a left join t3 on t1.a=t3.a union select * from t1 inner join t2 on t1.a=t2.a right join t3 on t1.a=t3.a; + +select * from t1 cross join t2 full join t3 on t1.a=t3.a; +select * from t1 cross join t2 left join t3 on t1.a=t3.a union select * from t1 cross join t2 right join t3 on t1.a=t3.a; + +select * from t1 cross join t2 on t1.a=t2.a full join t3 on t1.a=t3.a; +select * from t1 cross join t2 on t1.a=t2.a left join t3 on t1.a=t3.a union select * from t1 cross join t2 on t1.a=t2.a right join t3 on t1.a=t3.a; + +select * from (t1 left join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +select * from (t1 left join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 left join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; + +select * from (t1 right join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +select * from (t1 right join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; + +--echo # Nested NATURAL JOIN on the left of FULL JOIN. +select * from (t1 natural join t2) full join t3 on t1.a=t3.a; +select * from (t1 natural join t2) left join t3 on t1.a=t3.a union select * from (t1 natural join t2) right join t3 on t1.a=t3.a; + +--echo # Nested FULL JOIN on the left of FULL JOIN. +--echo # The inner FULL JOIN's unmatched right-side rows must appear +--echo # in the result even when the outer FULL JOIN condition does +--echo # not reference the inner right-side table. +--echo # Data: t1(1,2) t2(1,3) t3(1,4) +--sorted_result +select * from (t1 full join t2 on t1.a=t2.a) full join t3 on t1.a=t3.a; +--sorted_result +select * from (t1 left join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) left join t3 on t1.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t1.a=t3.a; + +--echo # Chained FULL JOINs with the second ON referencing the middle table. +--sorted_result +select * from t1 full join t2 on t1.a=t2.a full join t3 on t2.a=t3.a; +--sorted_result +select * from t1 left join t2 on t1.a=t2.a left join t3 on t2.a=t3.a union select * from t1 right join t2 on t1.a=t2.a left join t3 on t2.a=t3.a union select * from (t1 left join t2 on t1.a=t2.a) right join t3 on t2.a=t3.a union select * from (t1 right join t2 on t1.a=t2.a) right join t3 on t2.a=t3.a; + +--echo # Nested FULL JOIN with duplicate rows. +create table d1 (a int); +insert into d1 values (1),(1),(2); +create table d2 (a int); +insert into d2 values (1),(3),(3); +--sorted_result +select * from (d1 full join d2 on d1.a=d2.a) full join t3 on d1.a=t3.a; +--sorted_result +select * from (d1 left join d2 on d1.a=d2.a) left join t3 on d1.a=t3.a union all select * from (d1 right join d2 on d1.a=d2.a) left join t3 on d1.a=t3.a where d1.a is null union all select * from (d1 right join d2 on d1.a=d2.a) right join t3 on d1.a=t3.a where d1.a is null and d2.a is null; +drop table d1, d2; + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 3: FULL JOIN rewrites to LEFT, RIGHT, and INNER +--echo # +--echo # When a NULL-rejecting WHERE predicate selects one or both +--echo # sides, simplify_joins() rewrites the FULL JOIN accordingly. +--echo # The (re)written form must produce the same result as the +--echo # direct LEFT/RIGHT/INNER formulation. +--echo # ======================================================== + +create table t1 (pk int auto_increment, x int, y int, primary key (pk)); +create table t2 (pk int auto_increment, x int, y int, primary key (pk)); +insert into t1 (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 t2 (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 t1 full join t2 on t1.y = t2.y where t2.pk is not null; +select * from t1 right join t2 on t1.y = t2.y; + +--echo # FULL to RIGHT JOIN, these two queries should be equal: +select * from t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t2.pk is not null; +select * from t1 right join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; + +--echo # FULL to INNER JOIN, these two queries should be equal: +select * from t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t1.pk is not null and t2.pk is not null; +select * from t1 inner join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; + +--echo # FULL to LEFT JOIN, these two queries should be equal: +select * from t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 where t1.pk is not null; +select * from t1 left join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; + +--echo # FULL NATURAL to INNER JOIN, these two queries should be equal: +select * from t1 natural full join t2 where t1.pk is not null and t2.pk is not null; +select * from t1 inner join t2 on t1.x = t2.x and t1.y = t2.y; + +--echo # FULL NATURAL to LEFT JOIN, these two queries should be equal: +select * from t1 natural full join t2 where t1.pk is not null; +select * from t1 left join t2 on t2.pk = t1.pk and t2.x = t1.x and t2.y = t1.y; + +--echo # FULL NATURAL to RIGHT JOIN +select * from t1 natural full join t2 where t2.pk is not null; +select * from t1 right join t2 on t1.pk = t2.pk and t1.x = t2.x and t1.y = t2.y; + +--sorted_result +select * from t1 full join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; +--sorted_result +select * from t1 left join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1 union select * from t1 right join t2 on t1.x >= 0 and t1.x <= 1 and t2.x >= 0 and t2.x <= 1; + +--sorted_result +select * from t1 natural full join t2; +--sorted_result +select * from t1 natural left join t2 union select * from t1 natural right join t2; + +drop table t1, t2; + +--echo # Rewrites with nested joins. +create table t1 (v int); +insert into t1 (v) values (1); +create table t2 (v int); +insert into t2 (v) values (2); +create table t3 (v int); +insert into t3 (v) values (3); + +--echo # (FULL)FULL to (INNER)INNER JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t1.v is not null and t2.v is not null and t3.v is not null; +select * from t1 inner join t2 on t1.v = t2.v inner join t3 on t2.v = t3.v; + +--echo # (FULL)FULL to (RIGHT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t1.v = t3.v where t2.v is not null; +select * from t1 right join t2 on t1.v = t2.v left join t3 on t1.v = t3.v; + +--echo # (FULL)FULL to (LEFT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v full join t3 on t1.v = t3.v where t1.v is not null; +select * from t1 left join t2 on t2.v = t1.v left join t3 on t3.v = t1.v; + +--echo # (FULL)LEFT to (LEFT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v left join t3 on t2.v = t3.v where t1.v is not null; +select * from t1 left join t2 on t1.v = t2.v left join t3 on t2.v = t3.v; + +--echo # (FULL)LEFT to (RIGHT)LEFT JOIN +select * from t1 full join t2 on t1.v = t2.v left join t3 on t2.v = t3.v where t2.v is not null; +select * from t1 right join t2 on t1.v = t2.v left join t3 on t3.v = t2.v; + +--echo # (LEFT)FULL to (LEFT)RIGHT JOIN +select * from t1 left join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t3.v is not null; +select * from t1 left join t2 on t1.v = t2.v right join t3 on t2.v = t3.v; + +--echo # (LEFT)FULL to (LEFT)LEFT JOIN +insert into t1 (v) values (2),(3); +insert into t2 (v) values (1); +truncate t3; +insert into t3 (v) values (1); +select * from t1; +select * from t2; +select * from t3; +select * from t1 left join t2 on t1.v = t2.v full join t3 on t2.v = t3.v where t3.v = 1; +select * from t3 left join t1 on t1.v = 1 left join t2 on t2.v = 1; + +--echo # FULL to INNER, two variables. +select * from (select t1.v from t1 full join t2 on t1.v = t2.v where t1.v > 1 and t2.v > 1) as dt; +select t1.v from t2 inner join t1 where t2.v = t1.v and t1.v > 1 and t1.v > 1; + +--echo # FULL to INNER with a UNION. +select t1.v from t1 full join t2 on t1.v = t2.v where t1.v > 1 and t2.v > 1 union select * from t1; +select t1.v from t2 inner join t1 where t1.v = t2.v and t2.v > 1 and t2.v > 1 union select * from t1; + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 4: NATURAL FULL JOIN and COALESCE +--echo # +--echo # Common columns surface as COALESCE expressions rather than +--echo # plain fields. +--echo # ======================================================== + +create table t1 (a int, b int); +create table t2 (a int, b int); +create table t3 (a int, b int); +insert into t1 (a,b) values (1,1),(2,2); +insert into t2 (a,b) values (1,1),(3,3); +insert into t3 (a,b) values (3,3),(4,4); + +select * from t1 natural full join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null; + +select * from t1 natural left join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null +union +select * from t1 natural right join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null; + +explain extended +select * from t1 natural full join t2 where + t1.a is not null and t1.b is not null and + t2.a is not null and t2.b is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t2 join t1 where + t2.a = t1.a and t2.b = t1.b and + t1.a is not null and t1.b is not null; + + +select * from t1 natural full join t2 where t1.a is not null; + +select * from t1 natural left join t2 where t1.a is not null +union +select * from t1 natural right join t2 where t1.a is not null; + +explain extended select * from t1 natural full join t2 where t1.a is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; + + +select * from t1 natural full join t2 where t2.a is not null; + +select * from t1 natural left join t2 where t2.a is not null +union +select * from t1 natural right join t2 where t2.a is not null; + +explain extended select * from t1 natural full join t2 where t2.a is not null; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from + t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; + +select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; + +# Disabling VIEW protocol here because duplicate column names are +# replaced by generating unique names that have 'My_exp_' prefixes +# in VIEW protocol mode. That changes the result file output. +--disable_view_protocol +select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; +select * from (t1 natural left join t2) right join t2 t3 on t1.a=t3.a +union +select * from (t1 natural right join t2) right join t2 t3 on t1.a=t3.a; +--enable_view_protocol + +select t1.a from t1 natural full join (t2 natural join t3) where + t1.a is not null; + +select t1.a from t1 natural left join (t2 natural join t3) where t1.a is not null +union +select t1.a from t1 natural right join (t2 natural join t3) where t1.a is not null; + +explain extended select t1.a from + t1 natural full join (t2 natural join t3) where t1.a is not null; + +select t1.a AS a from t1 left join (t2 join t3) on + t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where + t1.a is not null; + + +explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.a; + + +select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; + +--echo # UNION equivalent of the aggregate above. +select dt.a, dt.b, avg(dt.t2a) as `avg(t2.a)` from ( + select t1.a as a, t1.b as b, t2.a as t2a from t1 natural left join t2 + union + select t1.a, t1.b, t2.a from t1 natural right join t2) dt +where dt.a is not null group by dt.a; + +explain extended select *, avg(t2.a) from t1 natural full join t2 where + t1.a is not null group by t1.a; + +select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, + avg(t2.a) AS avg_t2_a from + t1 left join t2 on t2.a = t1.a and t2.b = t1.b where + t1.a is not null group by t1.a; + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 5: NULL handling +--echo # +--echo # NULL = NULL is false, so rows whose join key is NULL never +--echo # match and must surface from their side unmatched. The +--echo # NULL-safe <=> operator matches NULL to NULL. +--echo # ======================================================== + +create table t1 (a int, b int); +insert into t1 values (NULL, NULL), (1, 10), (NULL, NULL); +create table t2 (a int, b int); +insert into t2 values (NULL, NULL), (2, 20), (NULL, NULL); + +--echo # Both sides have all-NULL rows; no match possible. +--sorted_result +select * from t1 full join t2 on t1.a = t2.a; +--sorted_result +--echo # The UNION formulation eliminates duplicate all-NULL rows; this +--echo # is expected to differ. PostgreSQL agrees with the FULL JOIN +--echo # result above. +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; + +--echo # IS NULL in the ON clause — all-NULL rows now match. +--sorted_result +select * from t1 full join t2 on t1.a is null and t2.a is null; +--sorted_result +select * from t1 left join t2 on t1.a is null and t2.a is null union select * from t1 right join t2 on t1.a is null and t2.a is null; + +--echo # NULL-safe equality operator (<=>). +--sorted_result +select * from t1 full join t2 on t1.a <=> t2.a; +--sorted_result +select * from t1 left join t2 on t1.a <=> t2.a union select * from t1 right join t2 on t1.a <=> t2.a; + +--echo # Table with only all-NULL rows on one side. +create table t3 (a int, b int); +insert into t3 values (NULL, NULL), (NULL, NULL); +--sorted_result +select * from t1 full join t3 on t1.a = t3.a; +--sorted_result +select * from t1 left join t3 on t1.a = t3.a union select * from t1 right join t3 on t1.a = t3.a; + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 6: Deeply nested FULL JOINs +--echo # ======================================================== + +create table t1 (a int); +insert into t1 values (1), (2); +create table t2 (a int); +insert into t2 values (2), (3); +create table t3 (a int); +insert into t3 values (3), (4); +create table t4 (a int); +insert into t4 values (1), (4); +create table t5 (a int); +insert into t5 values (2), (5); + +--echo # The LEFT/RIGHT-permutation UNION form is not a valid oracle +--echo # for chained FULL JOINs: it over-approximates by emitting a +--echo # right-side null-complement row for C rows that were already +--echo # matched against the inner FJ's own null-complement row. Per +--echo # SQL:2016 §7.10, (A FJ B) FJ C is left-associative and treats +--echo # R1 = (A FJ B) as a single relation; a C row matched against +--echo # any R1 row (including a null-complement row) is matched, and +--echo # must not appear again as unmatched. Therefore the chained +--echo # cases below have no UNION companion; the recorded result is +--echo # the oracle. + +--echo # Three-level nested FULL JOINs. +--sorted_result +select * from t1 + full join t2 on t1.a = t2.a + full join t3 on t2.a = t3.a; + +--echo # Four-level chained FULL JOINs. +--sorted_result +select * from t1 + full join t2 on t1.a = t2.a + full join t3 on t2.a = t3.a + full join t4 on t3.a = t4.a; + +--echo # Mixed FULL and INNER joins, deeply nested. +--sorted_result +select * from t1 + inner join t2 on t1.a = t2.a + full join t3 on t2.a = t3.a + full join t4 on t3.a = t4.a; + +drop table t1, t2, t3, t4, t5; + + +--echo # ======================================================== +--echo # Section 7: Mixed data types +--echo # ======================================================== + +create table t1 ( + id int, + str_val varchar(20), + dec_val decimal(10,2), + dt_val date +); +insert into t1 values + (1, 'hello', 10.50, '2024-01-01'), + (2, 'world', 20.75, '2024-06-15'), + (3, NULL, NULL, NULL); + +create table t2 ( + id int, + str_val varchar(20), + dec_val decimal(10,2), + dt_val date +); +insert into t2 values + (2, 'WORLD', 20.75, '2024-06-15'), + (4, 'test', 99.99, '2025-12-31'), + (NULL, NULL, NULL, NULL); + +--echo # FULL JOIN on integer column with mixed-type rows. +--sorted_result +select * from t1 full join t2 on t1.id = t2.id; +--sorted_result +select * from t1 left join t2 on t1.id = t2.id union select * from t1 right join t2 on t1.id = t2.id; + +--echo # FULL JOIN on varchar column (case-sensitive match depends on collation). +--sorted_result +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 full join t2 on t1.str_val = t2.str_val; +--sorted_result +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 left join t2 on t1.str_val = t2.str_val +union +select t1.id as id1, t1.str_val as sv1, t2.id as id2, t2.str_val as sv2 +from t1 right join t2 on t1.str_val = t2.str_val; + +--echo # FULL JOIN on decimal column. +--sorted_result +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 full join t2 on t1.dec_val = t2.dec_val; +--sorted_result +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 left join t2 on t1.dec_val = t2.dec_val +union +select t1.id as id1, t1.dec_val as d1, t2.id as id2, t2.dec_val as d2 +from t1 right join t2 on t1.dec_val = t2.dec_val; + +--echo # FULL JOIN on date column. +--sorted_result +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 full join t2 on t1.dt_val = t2.dt_val; +--sorted_result +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 left join t2 on t1.dt_val = t2.dt_val +union +select t1.id as id1, t1.dt_val as dt1, t2.id as id2, t2.dt_val as dt2 +from t1 right join t2 on t1.dt_val = t2.dt_val; + +--echo # FULL JOIN with cross-type comparison (int vs decimal). +create table t3 (a int); +insert into t3 values (1), (2), (3); +create table t4 (a decimal(5,1)); +insert into t4 values (1.0), (2.5), (3.0); +--sorted_result +select * from t3 full join t4 on t3.a = t4.a; +--sorted_result +select * from t3 left join t4 on t3.a = t4.a union select * from t3 right join t4 on t3.a = t4.a; + +--echo # FULL JOIN with cross-type comparison (int vs varchar). +create table t5 (a varchar(10)); +insert into t5 values ('1'), ('2'), ('four'); +--sorted_result +select * from t3 full join t5 on t3.a = t5.a; +--sorted_result +select * from t3 left join t5 on t3.a = t5.a union select * from t3 right join t5 on t3.a = t5.a; + +--echo # FULL JOIN on multiple mixed-type columns simultaneously. +--sorted_result +select * from t1 full join t2 + on t1.id = t2.id and t1.dec_val = t2.dec_val; +--sorted_result +select * from t1 left join t2 + on t1.id = t2.id and t1.dec_val = t2.dec_val +union +select * from t1 right join t2 + on t1.id = t2.id and t1.dec_val = t2.dec_val; + +drop table t1, t2, t3, t4, t5; + + +--echo # ======================================================== +--echo # Section 8: Aggregates with FULL JOIN +--echo # ======================================================== + +create table t1 (grp char(1), val int); +insert into t1 values ('a',10), ('a',20), ('b',30), ('c',40); +create table t2 (grp char(1), val int); +insert into t2 values ('b',100), ('c',200), ('c',300), ('d',400); + +--echo # COUNT and SUM over a FULL JOIN. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + count(*) as cnt, + sum(t1.val) as s1, + sum(t2.val) as s2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +--sorted_result +select coalesce(dt.grp1, dt.grp2) as grp, + count(*) as cnt, + sum(dt.val1) as s1, + sum(dt.val2) as s2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.grp, t1.val, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); + +--echo # AVG and MAX over a FULL JOIN. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + avg(t1.val) as avg1, + max(t2.val) as max2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +--sorted_result +select coalesce(dt.grp1, dt.grp2) as grp, + avg(dt.val1) as avg1, + max(dt.val2) as max2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.grp, t1.val, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); + +--echo # HAVING clause with FULL JOIN aggregate. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + count(*) as cnt +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp) +having count(*) > 1; +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + count(*) as cnt from t1 +left join t2 on t1.grp = t2.grp group by +coalesce(t1.grp, t2.grp) having count(*) > 1 +union +select coalesce(t1.grp, t2.grp) as grp, + count(*) as cnt from t1 +right join t2 on t1.grp = t2.grp group by +coalesce(t1.grp, t2.grp) having count(*) > 1; + +--echo # COUNT(*) with no GROUP BY — total row count of the FULL JOIN. +select count(*) from t1 full join t2 on t1.grp = t2.grp; +select count(*) from (select t1.grp as g1, t1.val as v1, t2.grp as g2, t2.val as v2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.grp, t1.val, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt; + +--echo # GROUP_CONCAT over a FULL JOIN. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + group_concat(t1.val order by t1.val) as vals1, + group_concat(t2.val order by t2.val) as vals2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +--sorted_result +select coalesce(dt.grp1, dt.grp2) as grp, + group_concat(dt.val1 order by dt.val1) as vals1, + group_concat(dt.val2 order by dt.val2) as vals2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.grp, t1.val, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); + +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 9: Window functions with FULL JOIN +--echo # +--echo # Adapted from main.win: ROW_NUMBER, RANK, DENSE_RANK, +--echo # LEAD/LAG, and aggregate windows (SUM/COUNT with frames) +--echo # applied to FULL JOIN result sets. +--echo # ======================================================== + +create table t1 (a int, grp int, val int); +insert into t1 values (1,10,100), (2,10,200), (3,20,300), (4,20,400); +create table t2 (a int, grp int, val int); +insert into t2 values (3,20,3000), (4,20,4000), (5,30,5000), (6,30,6000); + +--echo # ROW_NUMBER() over a FULL JOIN ordered by the coalesced key. +select coalesce(t1.a, t2.a) as a, + row_number() over (order by coalesce(t1.a, t2.a)) as rn +from t1 full join t2 on t1.a = t2.a +order by a; + +--echo # Equivalent: row-number over LEFT UNION RIGHT. +select a, row_number() over (order by a) as rn +from (select coalesce(t1.a, t2.a) as a + from t1 left join t2 on t1.a = t2.a + union + select coalesce(t1.a, t2.a) + from t1 right join t2 on t1.a = t2.a) u +order by a; + +--echo # RANK() with PARTITION BY over FULL JOIN. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + coalesce(t1.val, 0) + coalesce(t2.val, 0) as v, + rank() over (partition by coalesce(t1.grp, t2.grp) + order by coalesce(t1.val, 0) + coalesce(t2.val, 0)) as rk +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select coalesce(t1grp, t2grp) as grp, + coalesce(t1val, 0) + coalesce(t2val, 0) as v, + rank() over (partition by coalesce(t1grp, t2grp) + order by coalesce(t1val, 0) + coalesce(t2val, 0)) as rk +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u; + +--echo # DENSE_RANK() over FULL JOIN. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + dense_rank() over (order by coalesce(t1.grp, t2.grp)) as dr +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select coalesce(t1grp, t2grp) as grp, + dense_rank() over (order by coalesce(t1grp, t2grp)) as dr +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u; + +--echo # LEAD() and LAG() over FULL JOIN. +select coalesce(t1.a, t2.a) as a, + lag(coalesce(t1.a, t2.a)) + over (order by coalesce(t1.a, t2.a)) as prev_a, + lead(coalesce(t1.a, t2.a)) + over (order by coalesce(t1.a, t2.a)) as next_a +from t1 full join t2 on t1.a = t2.a +order by a; +select coalesce(t1a, t2a) as a, + lag(coalesce(t1a, t2a)) + over (order by coalesce(t1a, t2a)) as prev_a, + lead(coalesce(t1a, t2a)) + over (order by coalesce(t1a, t2a)) as next_a +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u +order by a; + +--echo # SUM() window with rows-BETWEEN frame over FULL JOIN. +select coalesce(t1.a, t2.a) as a, + coalesce(t1.val, 0) + coalesce(t2.val, 0) as v, + sum(coalesce(t1.val, 0) + coalesce(t2.val, 0)) + over (order by coalesce(t1.a, t2.a) + rows between 1 preceding and 1 following) as window_sum +from t1 full join t2 on t1.a = t2.a +order by a; +select coalesce(t1a, t2a) as a, + coalesce(t1val, 0) + coalesce(t2val, 0) as v, + sum(coalesce(t1val, 0) + coalesce(t2val, 0)) + over (order by coalesce(t1a, t2a) + rows between 1 preceding and 1 following) as window_sum +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u +order by a; + +--echo # COUNT() window partitioned by group, ordered within group. +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + coalesce(t1.a, t2.a) as a, + count(*) over (partition by coalesce(t1.grp, t2.grp) + order by coalesce(t1.a, t2.a) + rows between unbounded preceding and current row) as cnt +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select coalesce(t1grp, t2grp) as grp, + coalesce(t1a, t2a) as a, + count(*) over (partition by coalesce(t1grp, t2grp) + order by coalesce(t1a, t2a) + rows between unbounded preceding and current row) as cnt +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u; + +--echo # Window function combined with GROUP BY on the FULL JOIN. +--echo # Exercises the AGGR_OP::end_send path after the null-complement +--echo # pass completes (this previously asserted in create_sort_index). +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + sum(coalesce(t1.val, 0) + coalesce(t2.val, 0)) as s, + rank() over (order by sum(coalesce(t1.val, 0) + coalesce(t2.val, 0))) as rk +from t1 full join t2 on t1.a = t2.a +group by coalesce(t1.grp, t2.grp); +--sorted_result +select coalesce(t1grp, t2grp) as grp, + sum(coalesce(t1val, 0) + coalesce(t2val, 0)) as s, + rank() over (order by sum(coalesce(t1val, 0) + coalesce(t2val, 0))) as rk +from (select t1.a as t1a, t1.grp as t1grp, t1.val as t1val, + t2.a as t2a, t2.grp as t2grp, t2.val as t2val + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.grp, t1.val, t2.a, t2.grp, t2.val + from t1 right join t2 on t1.a = t2.a) u +group by coalesce(t1grp, t2grp); + +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 10: CTEs +--echo # ======================================================== + +create table t1 (id int, val varchar(10)); +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int, val varchar(10)); +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--echo # Simple CTE wrapping a FULL JOIN. +--sorted_result +with fj as ( + select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 + from t1 full join t2 on t1.id = t2.id +) +select * from fj; +--sorted_result +select * from t1 left join t2 on t1.id = t2.id union select * from t1 right join t2 on t1.id = t2.id; + +--echo # CTE on the left side of a FULL JOIN. +--sorted_result +with vals as (select id, val from t1 where id <= 2) +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 full join t2 on v1.id = t2.id; +--sorted_result +with vals as (select id, val from t1 where id <= 2) +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 left join t2 on v1.id = t2.id +union +select v1.id as id1, v1.val as val1, t2.id as id2, t2.val as val2 +from vals v1 right join t2 on v1.id = t2.id; + +--echo # Recursive CTE used in a FULL JOIN. +--sorted_result +with recursive seq as ( + select 1 as n + union all + select n + 1 from seq where n < 4 +) +select s1.n as n1, s2.id as n2 +from seq s1 full join t2 s2 on s1.n = s2.id; +--sorted_result +with recursive seq as ( + select 1 as n + union all + select n + 1 from seq where n < 4 +) +select s1.n as n1, s2.id as n2 +from seq s1 left join t2 s2 on s1.n = s2.id +union +select s1.n as n1, s2.id as n2 +from seq s1 right join t2 s2 on s1.n = s2.id; + +--echo # CTE on the left side of a FULL JOIN with filtering. +--sorted_result +with left_cte as (select * from t1 where id in (1,2)) +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l full join t2 on l.id = t2.id; +--sorted_result +with left_cte as (select * from t1 where id in (1,2)) +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l left join t2 on l.id = t2.id +union +select l.id as lid, l.val as lval, t2.id as rid, t2.val as rval +from left_cte l right join t2 on l.id = t2.id; + +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 11: Views over FULL JOIN +--echo # ======================================================== + +create table t1 (a int, b int); +insert into t1 values (1,10), (2,20), (3,30); +create table t2 (a int, b int); +insert into t2 values (2,200), (3,300), (4,400); + +--echo # Simple view over a FULL JOIN. +create view v_full as + select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 full join t2 on t1.a = t2.a; +--sorted_result +select * from v_full; +--sorted_result +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; + +--echo # Query the view with additional filtering. +--sorted_result +select * from v_full where a1 is not null and a2 is not null; +--sorted_result +select * from t1 inner join t2 on t1.a = t2.a; + +--echo # View joined with another table via FULL JOIN. +create table t3 (a int, c varchar(10)); +insert into t3 values (1,'x'), (2,'y'), (4,'z'); +--sorted_result +select v_full.a1, v_full.a2, t3.c +from v_full full join t3 on coalesce(v_full.a1, v_full.a2) = t3.a; +--sorted_result +select v_full.a1, v_full.a2, t3.c +from v_full left join t3 on coalesce(v_full.a1, v_full.a2) = t3.a +union +select v_full.a1, v_full.a2, t3.c +from v_full right join t3 on coalesce(v_full.a1, v_full.a2) = t3.a; + +--echo # View that filters the FULL JOIN result. +create view v_full_filtered as + select t1.a as a1, t2.a as a2 + from t1 full join t2 on t1.a = t2.a + where t1.a is not null; +--sorted_result +select * from v_full_filtered; +--sorted_result +select t1.a as a1, t2.a as a2 from t1 left join t2 on t1.a = t2.a; + +drop view v_full, v_full_filtered; +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 12: Prepared statements +--echo # ======================================================== + +create table t1 (a int, b varchar(10)); +insert into t1 values (1,'one'), (2,'two'), (3,'three'); +create table t2 (a int, b varchar(10)); +insert into t2 values (2,'TWO'), (3,'THREE'), (4,'FOUR'); + +--echo # Basic prepared statement with FULL JOIN. +prepare stmt1 from + 'select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 full join t2 on t1.a = t2.a'; +--sorted_result +execute stmt1; +--sorted_result +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; + +--echo # Re-execute to verify PS re-execution stability. +--sorted_result +execute stmt1; +deallocate prepare stmt1; + +--echo # Parameter in the ON clause. +prepare stmt2 from + 'select t1.a as a1, t2.a as a2 + from t1 full join t2 on t1.a = t2.a and t1.a > ?'; +set @threshold = 1; +--sorted_result +execute stmt2 using @threshold; +--sorted_result +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a and t1.a > 1 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a and t1.a > 1; + +--echo # Re-execute with a different parameter value. +set @threshold = 2; +--sorted_result +execute stmt2 using @threshold; +--sorted_result +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a and t1.a > 2 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a and t1.a > 2; +deallocate prepare stmt2; + +--echo # Parameter in the WHERE clause. +prepare stmt3 from + 'select t1.a, t2.a + from t1 full join t2 on t1.a = t2.a + where t1.a is not null or t2.a > ?'; +set @minval = 3; +--sorted_result +execute stmt3 using @minval; +--sorted_result +execute stmt3 using @minval; +deallocate prepare stmt3; + +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 13: Stored procedures +--echo # ======================================================== + +create table t1 (a int, b varchar(20)); +insert into t1 values (1,'alpha'), (2,'beta'), (3,'gamma'); +create table t2 (a int, b varchar(20)); +insert into t2 values (2,'BETA'), (4,'DELTA'), (5,'EPSILON'); + +delimiter |; + +--echo # SP that performs a FULL JOIN. +create procedure sp_full_join() +begin + select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 full join t2 on t1.a = t2.a; +end| + +--echo # SP with a FULL JOIN and a parameter. +create procedure sp_full_join_param(in min_a int) +begin + select t1.a as a1, t2.a as a2 + from t1 full join t2 on t1.a = t2.a + where coalesce(t1.a, t2.a) >= min_a; +end| + +--echo # SP using FULL JOIN with INSERT ... SELECT. +create procedure sp_full_join_insert() +begin + create temporary table t3 (a1 int, b1 varchar(20), a2 int, b2 varchar(20)); + insert into t3 + select t1.a, t1.b, t2.a, t2.b + from t1 full join t2 on t1.a = t2.a; + select * from t3; + drop temporary table t3; +end| + +delimiter ;| + +--sorted_result +call sp_full_join(); +--sorted_result +select * from t1 left join t2 on t1.a = t2.a union select * from t1 right join t2 on t1.a = t2.a; + +--sorted_result +call sp_full_join_param(3); +--sorted_result +select t1.a as a1, t2.a as a2 +from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) >= 3 +union +select t1.a as a1, t2.a as a2 +from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) >= 3; + +--echo # Call the SPs twice to test re-execution. +--sorted_result +call sp_full_join(); +--sorted_result +call sp_full_join_param(1); + +--sorted_result +call sp_full_join_insert(); + +drop procedure sp_full_join; +drop procedure sp_full_join_param; +drop procedure sp_full_join_insert; +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 14: Subqueries and semijoins with FULL JOIN +--echo # ======================================================== + +create table t1 (a int, b int); +insert into t1 values (1,10), (2,20), (3,30); +create table t2 (a int, b int); +insert into t2 values (2,200), (3,300), (4,400); +create table t3 (a int); +insert into t3 values (1), (3), (5); + +--echo # IN subquery (semijoin) on the result of a FULL JOIN. +--sorted_result +select * from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3); +--sorted_result +select * from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3) +union +select * from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) in (select a from t3); + +--echo # EXISTS subquery filtering a FULL JOIN. +--sorted_result +select * from t1 full join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)); +--sorted_result +select * from t1 left join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)) +union +select * from t1 right join t2 on t1.a = t2.a +where exists (select 1 from t3 where t3.a = coalesce(t1.a, t2.a)); + +--echo # NOT IN (anti-semijoin) with a FULL JOIN. +--sorted_result +select * from t1 full join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3); +--sorted_result +select * from t1 left join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3) +union +select * from t1 right join t2 on t1.a = t2.a +where coalesce(t1.a, t2.a) not in (select a from t3); + +--echo # FULL JOIN inside a subquery used as a semijoin predicate. +select * from t3 +where t3.a in ( + select coalesce(t1.a, t2.a) + from t1 full join t2 on t1.a = t2.a +); +select * from t3 +where t3.a in ( + select coalesce(t1.a, t2.a) + from t1 left join t2 on t1.a = t2.a + union + select coalesce(t1.a, t2.a) + from t1 right join t2 on t1.a = t2.a +); + +--echo # Correlated subquery with FULL JOIN. +--sorted_result +select * from t3 +where exists ( + select 1 from t1 full join t2 on t1.a = t2.a + where coalesce(t1.a, t2.a) = t3.a +); +--sorted_result +select * from t3 +where exists ( + select 1 from t1 left join t2 on t1.a = t2.a + where coalesce(t1.a, t2.a) = t3.a + union + select 1 from t1 right join t2 on t1.a = t2.a + where coalesce(t1.a, t2.a) = t3.a +); + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 15: Indexed access (PK, secondary, composite, unique) +--echo # +--echo # Exercises JT_EQ_REF / JT_REF access paths on the right +--echo # side of a FULL JOIN, including NULLable unique keys. +--echo # ======================================================== + +--echo # Primary key join (JT_EQ_REF). +create table t1 (id int primary key, val varchar(10)); +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int primary key, val varchar(10)); +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--sorted_result +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 full join t2 on t1.id = t2.id; +--sorted_result +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 right join t2 on t1.id = t2.id; + +--echo # PK join with WHERE filter. +--sorted_result +select t1.id as id1, t2.id as id2 +from t1 full join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2; +--sorted_result +select t1.id as id1, t2.id as id2 +from t1 left join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2 +union +select t1.id as id1, t2.id as id2 +from t1 right join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) > 2; + +--echo # PK join with aggregate. +select count(*) from t1 full join t2 on t1.id = t2.id; +select count(*) from (select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 + from t1 left join t2 on t1.id = t2.id + union + select t1.id, t1.val, t2.id, t2.val + from t1 right join t2 on t1.id = t2.id) dt; + +drop table t1, t2; + +--echo # Secondary index join (JT_REF) with duplicates. +create table t1 (id int, grp int, val varchar(10), key(grp)); +insert into t1 values (1,10,'a'), (2,20,'b'), (3,20,'c'), (4,30,'d'); +create table t2 (id int, grp int, val varchar(10), key(grp)); +insert into t2 values (5,20,'x'), (6,30,'y'), (7,30,'z'), (8,40,'w'); + +--sorted_result +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 full join t2 on t1.grp = t2.grp; +--sorted_result +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 left join t2 on t1.grp = t2.grp +union +select t1.id as id1, t1.grp as g1, t2.id as id2, t2.grp as g2 +from t1 right join t2 on t1.grp = t2.grp; + +select count(*) from t1 full join t2 on t1.grp = t2.grp; +select count(*) from (select t1.id as id1, t1.grp as g1, t1.val as v1, + t2.id as id2, t2.grp as g2, t2.val as v2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.id, t1.grp, t1.val, t2.id, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt; + +drop table t1, t2; + +--echo # Composite index join. +create table t1 (a int, b int, val varchar(10), primary key(a, b)); +insert into t1 values (1,1,'p'), (1,2,'q'), (2,1,'r'); +create table t2 (a int, b int, val varchar(10), primary key(a, b)); +insert into t2 values (1,2,'s'), (2,1,'t'), (2,2,'u'); + +--sorted_result +select t1.a as a1, t1.b as b1, t1.val as v1, + t2.a as a2, t2.b as b2, t2.val as v2 +from t1 full join t2 on t1.a = t2.a and t1.b = t2.b; +--sorted_result +select t1.a as a1, t1.b as b1, t1.val as v1, + t2.a as a2, t2.b as b2, t2.val as v2 +from t1 left join t2 on t1.a = t2.a and t1.b = t2.b +union +select t1.a as a1, t1.b as b1, t1.val as v1, + t2.a as a2, t2.b as b2, t2.val as v2 +from t1 right join t2 on t1.a = t2.a and t1.b = t2.b; + +--echo # Composite index: join on partial key (first column only). +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; + +drop table t1, t2; + +--echo # UNIQUE index with NULLable column. +create table t1 (id int auto_increment primary key, val int, unique key(val)); +insert into t1 (val) values (1), (NULL), (3), (NULL); +create table t2 (id int auto_increment primary key, val int, unique key(val)); +insert into t2 (val) values (2), (3), (NULL), (NULL); + +--sorted_result +select t1.val as v1, t2.val as v2 +from t1 full join t2 on t1.val = t2.val; +--sorted_result +select t1.val as v1, t2.val as v2 +from t1 left join t2 on t1.val = t2.val +union +select t1.val as v1, t2.val as v2 +from t1 right join t2 on t1.val = t2.val; + +drop table t1, t2; + + +--echo # ======================================================== +--echo # Section 16: Storage engines (MyISAM, Aria, mixed) +--echo # +--echo # The null-complement rescan must work regardless of +--echo # underlying storage engine and across mixed-engine joins +--echo # (different rowid formats in the weedout temp table). +--echo # ======================================================== + +--echo # Both sides MyISAM. +create table t1 (a int, b varchar(10)) engine=MyISAM; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; + +select count(*) from t1 full join t2 on t1.a = t2.a; +select count(*) from (select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.b, t2.a, t2.b + from t1 right join t2 on t1.a = t2.a) dt; + +drop table t1, t2; + +--echo # Both sides Aria. +create table t1 (a int, b varchar(10)) engine=Aria; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=Aria; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; + +select count(*) from t1 full join t2 on t1.a = t2.a; +select count(*) from (select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 + from t1 left join t2 on t1.a = t2.a + union + select t1.a, t1.b, t2.a, t2.b + from t1 right join t2 on t1.a = t2.a) dt; + +drop table t1, t2; + +--echo # Mixed engines: InnoDB and MyISAM. +create table t1 (a int, b varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--echo # InnoDB on left, MyISAM on right. +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; + +--echo # MyISAM on left, InnoDB on right. +--sorted_result +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 full join t1 on t2.a = t1.a; +--sorted_result +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 left join t1 on t2.a = t1.a +union +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 right join t1 on t2.a = t1.a; + +drop table t1, t2; + +--echo # Mixed engines: InnoDB and Aria. +create table t1 (a int, b varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (a int, b varchar(10)) engine=Aria; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); + +--echo # InnoDB on left, Aria on right. +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 full join t2 on t1.a = t2.a; +--sorted_result +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 left join t2 on t1.a = t2.a +union +select t1.a as a1, t1.b as b1, t2.a as a2, t2.b as b2 +from t1 right join t2 on t1.a = t2.a; + +--echo # Aria on left, InnoDB on right. +--sorted_result +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 full join t1 on t2.a = t1.a; +--sorted_result +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 left join t1 on t2.a = t1.a +union +select t2.a as a1, t2.b as b1, t1.a as a2, t1.b as b2 +from t2 right join t1 on t2.a = t1.a; + +drop table t1, t2; + +--echo # Three-way mixed engine FULL JOIN. +--echo # No UNION companion: see the note above the chained-FULL-JOIN +--echo # section — the LEFT/RIGHT permutation UNION over-approximates +--echo # for chained FULL JOINs. +create table t1 (a int) engine=InnoDB; +insert into t1 values (1), (2), (3); +create table t2 (a int) engine=MyISAM; +insert into t2 values (2), (3), (4); +create table t3 (a int) engine=Aria; +insert into t3 values (3), (4), (5); + +--sorted_result +select t1.a as a1, t2.a as a2, t3.a as a3 +from t1 + full join t2 on t1.a = t2.a + full join t3 on t2.a = t3.a; + +drop table t1, t2, t3; + +--echo # Indexed mixed-engine FULL JOIN chain. +create table t1 (id int primary key, val varchar(10)) engine=InnoDB; +insert into t1 values (1,'a'), (2,'b'), (3,'c'); +create table t2 (id int primary key, val varchar(10)) engine=MyISAM; +insert into t2 values (2,'x'), (3,'y'), (4,'z'); +create table t3 (id int primary key, val varchar(10)) engine=Aria; +insert into t3 values (3,'p'), (4,'q'), (5,'r'); + +--echo # InnoDB PK full join MyISAM PK. +--sorted_result +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 full join t2 on t1.id = t2.id; +--sorted_result +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 left join t2 on t1.id = t2.id +union +select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 +from t1 right join t2 on t1.id = t2.id; + +--echo # MyISAM PK full join Aria PK. +--sorted_result +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 full join t3 on t2.id = t3.id; +--sorted_result +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 left join t3 on t2.id = t3.id +union +select t2.id as id1, t2.val as v1, t3.id as id2, t3.val as v2 +from t2 right join t3 on t2.id = t3.id; + +--echo # Three-way: InnoDB PK, MyISAM PK, Aria PK. +--echo # No UNION companion: see the note above the chained-FULL-JOIN +--echo # section — the LEFT/RIGHT permutation UNION over-approximates +--echo # for chained FULL JOINs. +--sorted_result +select t1.id as id1, t2.id as id2, t3.id as id3 +from t1 + full join t2 on t1.id = t2.id + full join t3 on t2.id = t3.id; + +--echo # Indexed mixed-engine with WHERE filter. +--sorted_result +select t1.id as id1, t2.id as id2 +from t1 full join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3; +--sorted_result +select t1.id as id1, t2.id as id2 +from t1 left join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3 +union +select t1.id as id1, t2.id as id2 +from t1 right join t2 on t1.id = t2.id +where coalesce(t1.id, t2.id) between 2 and 3; + +--echo # Indexed mixed-engine with aggregate. +select count(*) from t1 full join t2 on t1.id = t2.id; +select count(*) from (select t1.id as id1, t1.val as v1, t2.id as id2, t2.val as v2 + from t1 left join t2 on t1.id = t2.id + union + select t1.id, t1.val, t2.id, t2.val + from t1 right join t2 on t1.id = t2.id) dt; + +drop table t1, t2, t3; + + +--echo # ======================================================== +--echo # Section 17: Complex feature combinations +--echo # +--echo # Queries that stress FULL JOIN alongside other features at +--echo # the same time: semi-joins with ranges, FULL-JOIN-in-FULL- +--echo # JOIN inside a semi-join, CTEs combined with window +--echo # functions, and HAVING over aggregates with FULL JOIN. +--echo # ======================================================== + +create table t3O (x int); +insert into t3O values (1), (2), (3), (4), (5), (6), (7); +create table t3L (k int, tag varchar(8)); +insert into t3L values (1,'L1'), (2,'L2'), (3,'L3'), (4,'L4'); +create table t3R (k int, tag varchar(8)); +insert into t3R values (3,'R3'), (4,'R4'), (5,'R5'), (6,'R6'); +create table t3M (k int, tag varchar(8)); +insert into t3M values (2,'M2'), (5,'M5'), (7,'M7'); + +--echo # 17.1 FULL JOIN inside a materialized semijoin. +--echo # Exercises get_allowed_nj_tables() with emb_sjm_nest set: +--echo # the FULL JOIN adjacency check must apply inside the SJM +--echo # nest so that siblings cannot interleave between partners. +set @save_optimizer_switch= @@optimizer_switch; +set optimizer_switch='materialization=on,semijoin=on'; + +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(t3L.k, t3R.k) from t3L full join t3R on t3L.k = t3R.k +); +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(t3L.k, t3R.k) from t3L left join t3R on t3L.k = t3R.k + union + select coalesce(t3L.k, t3R.k) from t3L right join t3R on t3L.k = t3R.k +); + +--echo # 17.2 FULL JOIN in semijoin with a range predicate on the +--echo # FULL JOIN's coalesced key. +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(t3L.k, t3R.k) c + from t3L full join t3R on t3L.k = t3R.k + where coalesce(t3L.k, t3R.k) between 2 and 5 +); +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(t3L.k, t3R.k) c + from t3L left join t3R on t3L.k = t3R.k + where coalesce(t3L.k, t3R.k) between 2 and 5 + union + select coalesce(t3L.k, t3R.k) c + from t3L right join t3R on t3L.k = t3R.k + where coalesce(t3L.k, t3R.k) between 2 and 5 +); + +--echo # 17.3 Nested FULL JOINs (a FULL JOIN of a FULL JOIN) inside +--echo # a semijoin. +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(coalesce(t3L.k, t3R.k), t3M.k) + from (t3L full join t3R on t3L.k = t3R.k) + full join t3M on coalesce(t3L.k, t3R.k) = t3M.k +); +--sorted_result +select * from t3O +where t3O.x in ( + select coalesce(coalesce(t3L.k, t3R.k), t3M.k) + from (t3L left join t3R on t3L.k = t3R.k) + left join t3M on coalesce(t3L.k, t3R.k) = t3M.k + union + select coalesce(coalesce(t3L.k, t3R.k), t3M.k) + from (t3L right join t3R on t3L.k = t3R.k) + left join t3M on coalesce(t3L.k, t3R.k) = t3M.k + union + select coalesce(coalesce(t3L.k, t3R.k), t3M.k) + from (t3L left join t3R on t3L.k = t3R.k) + right join t3M on coalesce(t3L.k, t3R.k) = t3M.k + union + select coalesce(coalesce(t3L.k, t3R.k), t3M.k) + from (t3L right join t3R on t3L.k = t3R.k) + right join t3M on coalesce(t3L.k, t3R.k) = t3M.k +); + +set optimizer_switch= @save_optimizer_switch; + +--echo # 17.4 CTE that produces a FULL JOIN result, consumed by a +--echo # window function in the outer query. +--sorted_result +with fj as ( + select coalesce(t3L.k, t3R.k) as k, + t3L.tag as ltag, + t3R.tag as rtag + from t3L full join t3R on t3L.k = t3R.k +) +select k, ltag, rtag, + row_number() over (order by k) as rn, + count(*) over (order by k rows between unbounded preceding and current row) as running_cnt +from fj; +--sorted_result +with fj as ( + select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag + from t3L left join t3R on t3L.k = t3R.k + union + select coalesce(t3L.k, t3R.k), t3L.tag, t3R.tag + from t3L right join t3R on t3L.k = t3R.k +) +select k, ltag, rtag, + row_number() over (order by k) as rn, + count(*) over (order by k rows between unbounded preceding and current row) as running_cnt +from fj; + +--echo # 17.5 FULL JOIN + HAVING + ORDER BY + aggregate window. +--sorted_result +select coalesce(t3L.k, t3R.k) as k, + count(*) as cnt, + rank() over (order by count(*) desc) as rk +from t3L full join t3R on t3L.k = t3R.k +group by coalesce(t3L.k, t3R.k) +having count(*) >= 1 +order by rk, k; +--sorted_result +select coalesce(t3Lk, t3Rk) as k, + count(*) as cnt, + rank() over (order by count(*) desc) as rk +from (select t3L.k as t3Lk, t3L.tag as ltag, t3R.k as t3Rk, t3R.tag as rtag + from t3L left join t3R on t3L.k = t3R.k + union + select t3L.k, t3L.tag, t3R.k, t3R.tag + from t3L right join t3R on t3L.k = t3R.k) dt +group by coalesce(t3Lk, t3Rk) +having count(*) >= 1 +order by rk, k; + +--echo # 17.6 FULL JOIN + EXISTS subquery that itself contains a +--echo # FULL JOIN, all filtered by a range condition. +--sorted_result +select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag +from t3L full join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 1 and 5 + and exists ( + select 1 from t3L l2 full join t3M on l2.k = t3M.k + where coalesce(l2.k, t3M.k) = coalesce(t3L.k, t3R.k) + ); +--sorted_result +select coalesce(t3L.k, t3R.k) as k, t3L.tag as ltag, t3R.tag as rtag +from t3L full join t3R on t3L.k = t3R.k +where coalesce(t3L.k, t3R.k) between 1 and 5 + and coalesce(t3L.k, t3R.k) in ( + select coalesce(l2.k, t3M.k) from t3L l2 left join t3M on l2.k = t3M.k + union + select coalesce(l2.k, t3M.k) from t3L l2 right join t3M on l2.k = t3M.k + ); + +drop table t3O, t3L, t3R, t3M; + + +--echo # ======================================================== +--echo # Section 18: Regressions +--echo # +--echo # Specific bugs that were fixed; kept as targeted cases so +--echo # they don't silently regress. +--echo # ======================================================== + +--echo # FULL JOIN with GROUP BY: previously crashed on the +--echo # create_sort_index assertion (filesort_result != 0) when +--echo # AGGR_OP::end_send was called twice. +create table t1 (grp char(1), val int); +insert into t1 values ('a',10), ('a',20), ('b',30), ('c',40); +create table t2 (grp char(1), val int); +insert into t2 values ('b',100), ('c',200), ('c',300), ('d',400); +--sorted_result +select coalesce(t1.grp, t2.grp) as grp, + count(*) as cnt, + sum(t1.val) as s1, + sum(t2.val) as s2 +from t1 full join t2 on t1.grp = t2.grp +group by coalesce(t1.grp, t2.grp); +--sorted_result +select coalesce(dt.grp1, dt.grp2) as grp, + count(*) as cnt, + sum(dt.val1) as s1, + sum(dt.val2) as s2 +from (select t1.grp as grp1, t1.val as val1, t2.grp as grp2, t2.val as val2 + from t1 left join t2 on t1.grp = t2.grp + union + select t1.grp, t1.val, t2.grp, t2.val + from t1 right join t2 on t1.grp = t2.grp) dt +group by coalesce(dt.grp1, dt.grp2); +drop table t1, t2; + +--echo # ==================================================================== +--echo # Section 19: Right side of FULL JOIN must be a base table +--echo # ==================================================================== +--echo # +--echo # Derived tables, views, and subqueries on the right side of a FULL +--echo # JOIN are not supported and must produce an error. + +create table t1 (a int); +create table t2 (a int); + +--echo # Derived table on the right side of a simple FULL JOIN +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from t1 full join (select * from t2) dt on t1.a = dt.a; + +--echo # View on the right side of a simple FULL JOIN +create view v1 as select * from t2; +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from t1 full join v1 on t1.a = v1.a; +drop view v1; + +--echo # Derived table on the right side of a nested FULL JOIN +create table t3 (a int); +--error ER_FULL_JOIN_BASE_TABLES_ONLY +select * from t1 full join t2 on t1.a = t2.a + full join (select * from t3) dt on t2.a = dt.a; +drop table t1, t2, t3; + +--echo # End of 12.3 tests diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index 6bee1bc7ea8ab..d9dbf80642b34 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3657,525 +3657,3 @@ DROP TABLE t1,t2,t3; # # End of 11.0 tests # -# -# MDEV-37932: FULL OUTER JOIN: Make the parser support FULL OUTER JOIN -# syntax -# -create table t1 (a int); -insert into t1 (a) values (1),(2),(3); -create table t2 (a int); -insert into t2 (a) values (1),(2),(3); -# This test only verifies syntax acceptance. -# TODO fix PS protocol before end of FULL OUTER JOIN development -select * from t1 full join t2 on t1.a = t2.a; -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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -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` = `test`.`t1`.`a`) where 1 -select * from t1 full outer join t2 on t1.a = t2.a; -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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -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` = `test`.`t1`.`a`) where 1 -select * from t1 natural full outer join t2; -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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -select * from t1 natural full join t2; -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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -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 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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -drop view v1; -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 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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -drop view v1; -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 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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -drop view v1; -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 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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 select `test`.`t1`.`a` AS `t1a`,`test`.`t2`.`a` AS `t2a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -drop view v1; -select * from (select t1.a from t1 full join t2 on t1.a = t2.a union select * from t1) dt; -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 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 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 ALL NULL NULL NULL NULL 12 100.00 -2 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 -2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 -3 UNION t1 ALL NULL NULL NULL NULL 3 100.00 -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` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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 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 ALL NULL NULL NULL NULL 12 100.00 -2 DERIVED t1 ALL NULL NULL NULL NULL 3 100.00 -2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 -3 UNION t1 ALL NULL NULL NULL NULL 3 100.00 -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` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 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 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 ALL NULL NULL NULL NULL 3 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 -Warnings: -Note 1003 with cte as (select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`))select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 -select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; -ERROR 42S02: Table 'test.t3' doesn't exist -select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; -ERROR 42S02: Table 'test.t3' doesn't exist -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 -6 0 0 -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 -1 -5 -5 -2 -4 -4 -3 -3 -3 -4 -2 -2 -5 -1 -1 -6 0 0 -7 1 1 -8 2 2 -9 3 3 -10 4 4 -11 5 5 -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 -1 -5 25 -2 -4 16 -3 -3 9 -4 -2 4 -5 -1 1 -6 0 0 -7 1 1 -8 2 4 -9 3 9 -10 4 16 -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 -# FULL to INNER, two variables; these two queries should have equal results. -select * from (select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1) as t3; -v -2 -select one.v from two inner join one where two.v = one.v and one.v > 1 and one.v > 1; -v -2 -# FULL to INNER with a UNION; these two queries should have equal results. -select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1 union select * from one; -v -2 -1 -3 -select one.v from two inner join one where one.v = two.v and two.v > 1 and two.v > 1 union select * from one; -v -2 -1 -3 -drop table one, two, three; -# NATURAL FULL JOIN COALESCE() for unique column sets. -create table t1 (a int, b int); -create table t2 (a int, b int); -create table t3 (a int, b int); -insert into t1 (a,b) values (1,1),(2,2); -insert into t2 (a,b) values (1,1),(3,3); -insert into t3 (a,b) values (3,3),(4,4); -select * from t1 natural full join t2 where -t1.a is not null and t1.b is not null and -t2.a is not null and t2.b is not null; -a b -1 1 -explain extended -select * from t1 natural full join t2 where -t1.a is not null and t1.b is not null and -t2.a is not null and t2.b is not null; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` join `test`.`t1` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null and `test`.`t1`.`a` is not null and `test`.`t1`.`b` is not null -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from -t2 join t1 where -t2.a = t1.a and t2.b = t1.b and -t1.a is not null and t1.b is not null; -a b -1 1 -select * from t1 natural full join t2 where t1.a is not null; -a b -1 1 -2 2 -explain extended select * from t1 natural full join t2 where t1.a is not null; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from -t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; -a b -1 1 -2 2 -select * from t1 natural full join t2 where t2.a is not null; -a b -1 1 -3 3 -explain extended select * from t1 natural full join t2 where t2.a is not null; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b` from `test`.`t2` left join `test`.`t1` on(`test`.`t1`.`a` = `test`.`t2`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`) where `test`.`t2`.`a` is not null -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from -t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; -a b -1 1 -3 3 -select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; -a b a b -1 1 1 1 -NULL NULL 3 3 -explain extended select * from -(t1 natural join t2) right join t2 t3 on t1.a=t3.a; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) -Warnings: -Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t2` `t3` left join (`test`.`t1` join `test`.`t2`) on(`test`.`t1`.`a` = `test`.`t3`.`a` and `test`.`t2`.`a` = `test`.`t3`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where 1 -select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; -a b a b -1 1 1 1 -NULL NULL 3 3 -explain extended select * from -(t1 natural full join t2) right join t2 t3 on t1.a=t3.a; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t2` `t3` left join (`test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t3`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`)) on(`test`.`t1`.`a` = `test`.`t3`.`a`) where 1 -select t1.a from t1 natural full join (t2 natural join t3) where -t1.a is not null; -a -1 -2 -explain extended select t1.a from -t1 natural full join (t2 natural join t3) where t1.a is not null; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -1 SIMPLE t3 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (incremental, BNL join) -Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` left join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b` and `test`.`t3`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null -select t1.a AS a from t1 left join (t2 join t3) on -t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where -t1.a is not null; -a -1 -2 -explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.a; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where 1 group by `test`.`t1`.`a` -select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; -a b avg(t2.a) -1 1 1.0000 -2 2 NULL -explain extended select *, avg(t2.a) from t1 natural full join t2 where -t1.a is not null group by t1.a; -id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where; Using temporary; Using filesort -1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) -Warnings: -Note 1003 select coalesce(`test`.`t1`.`a`,`test`.`t2`.`a`) AS `a`,coalesce(`test`.`t1`.`b`,`test`.`t2`.`b`) AS `b`,avg(`test`.`t2`.`a`) AS `avg(t2.a)` from `test`.`t1` left join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`) where `test`.`t1`.`a` is not null group by `test`.`t1`.`a` -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, -avg(t2.a) AS avg_t2_a from -t1 left join t2 on t2.a = t1.a and t2.b = t1.b where -t1.a is not null group by t1.a; -a b avg_t2_a -1 1 1.0000 -2 2 NULL -drop table t1, t2, t3; -# End of 12.3 tests diff --git a/mysql-test/main/join.test b/mysql-test/main/join.test index 094971af95e73..efdbf6724d963 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2046,270 +2046,3 @@ DROP TABLE t1,t2,t3; --echo # --echo # End of 11.0 tests --echo # - ---echo # ---echo # MDEV-37932: FULL OUTER JOIN: Make the parser support FULL OUTER JOIN ---echo # syntax ---echo # -create table t1 (a int); -insert into t1 (a) values (1),(2),(3); -create table t2 (a int); -insert into t2 (a) values (1),(2),(3); ---echo # This test only verifies syntax acceptance. ---echo # TODO fix PS protocol before end of FULL OUTER JOIN development ---disable_ps_protocol ---error ER_NOT_SUPPORTED_YET -select * from t1 full join t2 on t1.a = t2.a; -explain extended select * from t1 full join t2 on t1.a = t2.a; - ---error ER_NOT_SUPPORTED_YET -select * from t1 full outer join t2 on t1.a = t2.a; -explain extended select * from t1 full outer join t2 on t1.a = t2.a; - ---error ER_NOT_SUPPORTED_YET -select * from t1 natural full outer join t2; -explain extended select * from t1 natural full outer join t2; - ---error ER_NOT_SUPPORTED_YET -select * from t1 natural full join t2; -explain extended select * from t1 natural full join t2; - -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 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 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 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_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_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 -select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; -explain extended select * from (select t1.a from t1 natural full join t2 union select * from t1) dt; - ---error ER_NOT_SUPPORTED_YET -select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; -explain extended select * from (select t1.a from t1 natural full outer join t2 union select * from t1) dt; - ---error ER_NOT_SUPPORTED_YET -with cte as (select t1.a from t1 natural full join t2) select * from cte; -explain extended with cte as (select t1.a from t1 natural full join t2) select * from cte; - ---error ER_NO_SUCH_TABLE -select * from t1, t2 full join t3 on t2.c=t3.e and t3.f=t1.a; - ---error ER_NO_SUCH_TABLE -select * from t1, t2 full outer join t3 on t2.c=t3.e and t3.f=t1.a; - ---error ER_NO_SUCH_TABLE -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; - ---echo # FULL to INNER, two variables; these two queries should have equal results. -select * from (select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1) as t3; -select one.v from two inner join one where two.v = one.v and one.v > 1 and one.v > 1; - ---echo # FULL to INNER with a UNION; these two queries should have equal results. -select one.v from one full join two on one.v = two.v where one.v > 1 and two.v > 1 union select * from one; -select one.v from two inner join one where one.v = two.v and two.v > 1 and two.v > 1 union select * from one; - -drop table one, two, three; - ---echo # NATURAL FULL JOIN COALESCE() for unique column sets. -create table t1 (a int, b int); -create table t2 (a int, b int); -create table t3 (a int, b int); -insert into t1 (a,b) values (1,1),(2,2); -insert into t2 (a,b) values (1,1),(3,3); -insert into t3 (a,b) values (3,3),(4,4); - -select * from t1 natural full join t2 where - t1.a is not null and t1.b is not null and - t2.a is not null and t2.b is not null; - -explain extended -select * from t1 natural full join t2 where - t1.a is not null and t1.b is not null and - t2.a is not null and t2.b is not null; - -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from - t2 join t1 where - t2.a = t1.a and t2.b = t1.b and - t1.a is not null and t1.b is not null; - - -select * from t1 natural full join t2 where t1.a is not null; - -explain extended select * from t1 natural full join t2 where t1.a is not null; - -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from - t1 left join t2 on t2.a = t1.a and t2.b = t1.b where t1.a is not null; - - -select * from t1 natural full join t2 where t2.a is not null; - -explain extended select * from t1 natural full join t2 where t2.a is not null; - -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b from - t2 left join t1 on t1.a = t2.a and t1.b = t2.b where t2.a is not null; - - -select * from (t1 natural join t2) right join t2 t3 on t1.a=t3.a; - -explain extended select * from - (t1 natural join t2) right join t2 t3 on t1.a=t3.a; - -select * from (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; - -explain extended select * from - (t1 natural full join t2) right join t2 t3 on t1.a=t3.a; - - -select t1.a from t1 natural full join (t2 natural join t3) where - t1.a is not null; - -explain extended select t1.a from - t1 natural full join (t2 natural join t3) where t1.a is not null; - -select t1.a AS a from t1 left join (t2 join t3) on - t2.a = t1.a and t3.a = t1.a and t2.b = t1.b and t3.b = t1.b where - t1.a is not null; - - -explain extended select *, avg(t2.a) from t1 natural full join t2 group by t1.a; - - -select *, avg(t2.a) from t1 natural full join t2 where t1.a is not null group by t1.a; - -explain extended select *, avg(t2.a) from t1 natural full join t2 where - t1.a is not null group by t1.a; - -select coalesce(t1.a, t2.a) AS a, coalesce(t1.b, t2.b) AS b, - avg(t2.a) AS avg_t2_a from - t1 left join t2 on t2.a = t1.a and t2.b = t1.b where - t1.a is not null group by t1.a; - -drop table t1, t2, t3; - -# TODO fix PS protocol before end of FULL OUTER JOIN development ---enable_ps_protocol ---echo # End of 12.3 tests diff --git a/mysql-test/main/table_elim.result b/mysql-test/main/table_elim.result index e7ae65e63d9f3..c1bf6baa0686b 100644 --- a/mysql-test/main/table_elim.result +++ b/mysql-test/main/table_elim.result @@ -1125,22 +1125,22 @@ as select a, a as b from t1 where a in (1,3); explain extended select t1.a from t1 full join t2 on t2.a=t1.a; id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 -1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 Using where; Using index Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a`) where 1 +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join `test`.`t2` on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t1`.`a` is not null) where 1 explain extended select t1.a from t1 full join (t2 full join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 -1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 -1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 Using where +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 Using where Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b`) where 1 +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t3` join `test`.`t2`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b` and `test`.`t1`.`a` is not null and `test`.`t2`.`a` is not null) where 1 explain extended select t1.a from t1 full join (t2 left join t3 on t2.b=t3.b) on t2.a=t1.a and t3.a=t1.a; id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 -1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 -1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.a 1 100.00 Using where +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t2.a 1 100.00 Using where Warnings: -Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b`) where 1 +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` full join (`test`.`t2` join `test`.`t3`) on(`test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t2`.`b` and `test`.`t1`.`a` is not null and `test`.`t2`.`a` is not null) where 1 drop table t1, t2, t3; # End of 12.3 tests diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 37a7718fae42e..03cd52df9d362 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12393,4 +12393,4 @@ ER_CANNOT_CAST_ON_IDENT1_ASSIGNMENT_FOR_OPERATION ER_CANNOT_CAST_ON_IDENT2_ASSIGNMENT_FOR_OPERATION eng "Cannot cast '%s' as '%s' in assignment of %sQ.%sQ for operation %s" ER_FULL_JOIN_BASE_TABLES_ONLY - eng "FULL JOIN supported for base tables only, %s is not a base table" + eng "FULL JOIN is only supported with base tables on the right side; '%s' is not a base table" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 3606e46ed1733..68bff2f6d9d2f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7927,7 +7927,8 @@ store_top_level_join_columns(THD *thd, TABLE_LIST *table_ref, swapped in the first loop. */ if (same_level_left_neighbor && - cur_table_ref->outer_join & JOIN_TYPE_RIGHT) + (cur_table_ref->outer_join & JOIN_TYPE_RIGHT) && + !(cur_table_ref->outer_join & JOIN_TYPE_FULL)) { /* This can happen only for JOIN ... ON. */ DBUG_ASSERT(table_ref->nested_join->join_list.elements == 2); @@ -8865,23 +8866,38 @@ insert_fields(THD *thd, Name_resolution_context *context, { DBUG_ASSERT((tables->field_translation == NULL && table) || tables->is_natural_join); - DBUG_ASSERT(item->type() == Item::FIELD_ITEM); - Item_field *fld= (Item_field*) item; - const char *field_db_name= field_iterator.get_db_name().str; - const char *field_table_name= field_iterator.get_table_name().str; - - if (!tables->schema_table && - !(fld->have_privileges= - (get_column_grant(thd, field_iterator.grant(), - field_db_name, - field_table_name, fld->field_name) & - VIEW_ANY_ACL))) + if (item->type() == Item::FIELD_ITEM) { - my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "ANY", - thd->security_ctx->priv_user, - thd->security_ctx->host_or_ip, - field_db_name, field_table_name); - DBUG_RETURN(TRUE); + Item_field *fld= (Item_field*) item; + const char *field_db_name= field_iterator.get_db_name().str; + const char *field_table_name= field_iterator.get_table_name().str; + + if (!tables->schema_table && + !(fld->have_privileges= + (get_column_grant(thd, field_iterator.grant(), + field_db_name, + field_table_name, fld->field_name) & + VIEW_ANY_ACL))) + { + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "ANY", + thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, + field_db_name, field_table_name); + DBUG_RETURN(TRUE); + } + } + else + { + /* + For NATURAL FULL JOIN, common columns are represented + as COALESCE expressions rather than plain Item_field. The + per-column privilege check is skipped because the underlying + fields already had their privileges verified during name + resolution. Assert that this is indeed a FULL JOIN context. + */ + DBUG_ASSERT(tables->is_natural_join && tables->nested_join && + (tables->nested_join->join_list.head()->outer_join & + JOIN_TYPE_FULL)); } } #endif diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e5c1a46277a78..1ab3c7926a695 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4904,11 +4904,26 @@ static void fix_prepare_info_in_table_list(THD *thd, TABLE_LIST *tbl) { for (; tbl; tbl= tbl->next_local) { - if (tbl->on_expr && !tbl->prep_on_expr) + /* + Walk up the nested JOINs so that upper-level ON expressions also + get saved into their respective prep_on_expr's. + + We must do this to support PS: + prepare st from + 'select ... from t1 full join t2 on t1.a = t2.a'; + */ + TABLE_LIST *embedding= tbl; + do { - thd->check_and_register_item_tree(&tbl->prep_on_expr, &tbl->on_expr); - tbl->on_expr= tbl->on_expr->copy_andor_structure(thd); + if (embedding->on_expr && !embedding->prep_on_expr) + { + thd->check_and_register_item_tree(&embedding->prep_on_expr, + &embedding->on_expr); + embedding->on_expr= embedding->on_expr->copy_andor_structure(thd); + } } + while ((embedding= embedding->embedding)); + if (tbl->is_view_or_derived() && tbl->is_merged_derived()) { SELECT_LEX *sel= tbl->get_single_select(); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9a413675ec712..664dc95227ec3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8631,7 +8631,8 @@ bool st_select_lex::add_cross_joined_table(TABLE_LIST *left_op, left_op->first_leaf_for_name_resolution(); } - if (!(tbl->outer_join & JOIN_TYPE_RIGHT)) + if (!(tbl->outer_join & JOIN_TYPE_RIGHT) || + (tbl->outer_join & JOIN_TYPE_FULL)) { pair_tbl= tbl; tbl= li++; @@ -8648,6 +8649,15 @@ bool st_select_lex::add_cross_joined_table(TABLE_LIST *left_op, cj_nest->on_expr= tbl->on_expr; cj_nest->embedding= tbl->embedding; cj_nest->join_list= jl; + /* + Transfer FULL JOIN state to the new nest: tbl is being replaced + by cj_nest in the join tree, so the partner pointer must follow + (and the partner's back-pointer must be retargeted). + */ + cj_nest->foj_partner= tbl->foj_partner; + cj_nest->on_context= tbl->on_context; + if (cj_nest->foj_partner) + cj_nest->foj_partner->foj_partner= cj_nest; cj_nest->alias.str= "(nest_last_join)"; cj_nest->alias.length= sizeof("(nest_last_join)")-1; li.replace(cj_nest); @@ -8673,6 +8683,8 @@ bool st_select_lex::add_cross_joined_table(TABLE_LIST *left_op, tbl->on_expr= 0; tbl->straight= straight_fl; tbl->natural_join= 0; + tbl->foj_partner= 0; + tbl->on_context= 0; tbl->embedding= cj_nest; tbl->join_list= cjl; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 92dcae6df99f2..920f6381d9d0b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -202,6 +202,7 @@ static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, bool do_substitution); static COND *simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj); +static bool check_full_join_base_tables(List *join_list); static bool check_interleaving_with_nj(JOIN_TAB *next); static void restore_prev_nj_state(JOIN_TAB *last); static uint reset_nj_counters(JOIN *join, List *join_list); @@ -452,6 +453,121 @@ bool dbug_user_var_equals_str(THD *thd, const char *name, const char* value) } #endif /* DBUG_OFF */ + +/* + Duplicate-row filter for FULL JOIN execution. + + During the first (LEFT JOIN) pass of a FULL JOIN, the filter records + the rowids of right-side rows that were matched. During the second + (null-complement) pass, the filter is consulted to skip rows that + were already emitted, so that only unmatched right-side rows produce + NULL-complemented output. + + Internally this reuses the semi-join weedout infrastructure + (SJ_TMP_TABLE). +*/ +class full_join_duplicate_filter : public Sql_alloc +{ + // Weedout temp table that stores seen rowids. + SJ_TMP_TABLE tbl; + +public: + /* + Allocate and populate the weedout temp table for the right side of + a FULL JOIN. Builds an SJ_TMP_TABLE whose record is the right + table's rowid. Returns true on error. + */ + bool init(THD *thd, JOIN_TAB *right_tab) + { + DBUG_ASSERT(thd); + DBUG_ASSERT(right_tab); + + tbl.tmp_table= NULL; + tbl.is_degenerate= false; + tbl.have_degenerate_row= false; + tbl.next_flush_table= nullptr; + + if (!(tbl.tabs= thd->alloc(1))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATAL)); + return true; + } + + uint jt_rowid_offset= 0; + uint jt_null_bits= 0; + + tbl.tabs[0].join_tab= right_tab; + tbl.tabs[0].rowid_offset= jt_rowid_offset; + jt_rowid_offset+= right_tab->table->file->ref_length; + if (right_tab->table->maybe_null) + { + tbl.tabs[0].null_byte= jt_null_bits / 8; + tbl.tabs[0].null_bit= jt_null_bits++; + } + + tbl.tabs_end= tbl.tabs + 1; + tbl.rowid_len= jt_rowid_offset; + tbl.null_bits= jt_null_bits; + tbl.null_bytes= (jt_null_bits + 7) / 8; + + right_tab->table->prepare_for_position(); + right_tab->keep_current_rowid= TRUE; + + if (tbl.create_sj_weedout_tmp_table(thd)) + return true; + return false; + } + + /* + Record the current right-side rowid during the first (LEFT JOIN) + pass. Duplicate-key errors are silently ignored because, during + the first pass, we only need to remember that the rowid was seen at + least once. Returns 0 on success, 1 on error. + */ + int remember_rowids(THD *thd) + { + DBUG_ASSERT(thd); + int res= tbl.sj_weedout_check_row(thd); + if (res == -1) + return 1; + return 0; + } + + /* + Check whether the current right-side rowid was already emitted. + Called during the second (null-complement) pass: if the rowid is + already in the temp table, sets *is_duplicate so the caller can + skip emitting a NULL-complemented row for a right-side row that + was already matched. Returns 0 on success, 1 on error. + */ + int check_rowids(THD *thd, bool *is_duplicate) + { + DBUG_ASSERT(thd); + DBUG_ASSERT(is_duplicate); + int res= tbl.sj_weedout_check_row(thd); + if (res == -1) + return 1; + *is_duplicate= (res == 1); + return 0; + } + + /* + Delete all recorded rows and free the weedout temp table. Must + be called after FULL JOIN execution is complete. + */ + void cleanup(THD *thd) + { + tbl.sj_weedout_delete_rows(); + if (tbl.tmp_table) + { + tbl.tmp_table->file->ha_index_or_rnd_end(); + free_tmp_table(thd, tbl.tmp_table); + tbl.tmp_table= NULL; + } + } +}; + + /* Intialize POSITION structure. */ @@ -2340,6 +2456,9 @@ JOIN::optimize_inner() } #endif + if (check_full_join_base_tables(join_list)) + DBUG_RETURN(1); + SELECT_LEX *sel= select_lex; if (sel->first_cond_optimization) { @@ -3066,19 +3185,6 @@ int JOIN::optimize_stage2() if (setup_semijoin_loosescan(this)) DBUG_RETURN(1); - /* - 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) - { - if (!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(0); - } - if (make_join_select(this, select, conds)) { if (thd->is_error()) @@ -5923,18 +6029,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; @@ -14291,6 +14385,17 @@ make_outerjoin_info(JOIN *join) if (tbl->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) { + /* + Skip the LEFT side of a FULL JOIN. Its null-complementing is + handled by the fj_dups mechanism, not by the standard nested + loop outer join machinery. Setting up an outer join scope + here would cause the ON condition to be pushed into + this table's select_cond, filtering out rows before they reach + the right side and preventing null-complement generation. + */ + if ((tbl->outer_join & JOIN_TYPE_FULL) && + (tbl->outer_join & JOIN_TYPE_LEFT)) + goto skip_outer_join_setup; /* Table tab is the only one inner table for outer join. (Like table t4 for the table reference t3 LEFT JOIN t4 ON t3.a=t4.a @@ -14304,7 +14409,8 @@ make_outerjoin_info(JOIN *join) } else if (!embedding) tab->table->reginfo.not_exists_optimize= 0; - + +skip_outer_join_setup: for ( ; embedding ; embedding= embedding->embedding) { if (embedding->is_active_sjm()) @@ -14321,6 +14427,13 @@ make_outerjoin_info(JOIN *join) tab->table->reginfo.not_exists_optimize= 0; continue; } + /* + Again, skip the LEFT side of a FULL JOIN (see above comment + for details). + */ + if ((embedding->outer_join & JOIN_TYPE_FULL) && + (embedding->outer_join & JOIN_TYPE_LEFT)) + continue; NESTED_JOIN *nested_join= embedding->nested_join; if (!nested_join->counter) { @@ -14414,6 +14527,121 @@ bool build_tmp_join_prefix_cond(JOIN *join, JOIN_TAB *last_tab, Item **ret) } +/* + Push parts of an outer-join ON expression to tables that appear + after the inner tables in the execution order. + + The caller (make_join_select) has a loop that walks the inner tables + of each outer join, extracting conjuncts from the ON expression + and pushing them to each inner table's select_cond. But that loop + only visits tables from [start_from...last_tab], building used_tables2 + as it goes. If the ON expression references a table that the + optimizer placed after last_tab, no conjunct is ever extracted for + it, and the condition is silently lost. + + This happens with FULL JOIN. For example, if: + + (A FULL JOIN B ON A.x = B.x) RIGHT JOIN C ON B.x = C.x + + is rewritten to: + + C LEFT JOIN (A, B) ON B.x = C.x + + then the optimizer may order these tables as A, B, C. The inner + scope of the LEFT JOIN is {A, B} with last_tab = B, but the ON + expression "B.x = C.x" references C, which comes after B. The main + loop never reaches C, so "B.x = C.x" is never pushed. + + This function picks up where the main loop left off: it scans + tables after last_tab and, for each one referenced by on_expr, + extracts the relevant conjuncts, wraps them in the same trigcond + guards used for outer-join conditions (found + not_null_compl), + and attaches them to that table's select_cond. + + @param used_tables Cumulative bitmap of tables visited so far + (updated in place so the caller sees the new + tables we covered). + @return true on error. +*/ +static bool +push_on_expr_to_later_outer_tables(THD *thd, JOIN *join, JOIN_TAB *last_tab, + JOIN_TAB *first_inner_tab, Item *on_expr, + table_map *used_tables) +{ + /* + Every table referenced by on_expr is already in used_tables; + nothing left to push. + */ + if (!(on_expr->used_tables() & ~(*used_tables))) + return false; + + JOIN_TAB *end_tab= join->join_tab + join->top_join_tab_count; + for (JOIN_TAB *outer_tab= last_tab + 1; outer_tab < end_tab; outer_tab++) + { + if (!outer_tab->table) + continue; + + /* + Always add the table to used_tables even if on_expr doesn't + reference it. make_cond_for_table needs used_tables to include + all tables up to and including the one being extracted for. + */ + table_map current_map= outer_tab->table->map; + *used_tables|= current_map; + if (!(on_expr->used_tables() & current_map)) + continue; + + /* + Extract the conjuncts from on_expr that can be evaluated once + this table's row is available. + */ + COND *tmp_cond= make_cond_for_table(thd, on_expr, *used_tables, + current_map, -1, + FALSE, FALSE); + if (thd->is_error()) + return true; + if (!tmp_cond) + continue; + + /* + Wrap in the two "standard" outer-join trigcond guards: + + trigcond(found, ) -- disabled until a match is found + trigcond(not_null_compl, ...) -- disabled during null-complement + + This makes the pushed condition behave identically to conditions + pushed to inner tables by the main loop. + */ + DBUG_ASSERT(tmp_cond->fixed()); + if (!(tmp_cond= add_found_match_trig_cond(thd, first_inner_tab, + tmp_cond, 0))) + return true; + + tmp_cond= new (thd->mem_root) + Item_func_trig_cond(thd, tmp_cond, &first_inner_tab->not_null_compl); + if (!tmp_cond) + return true; + tmp_cond->quick_fix_field(); + + /* AND the wrapped condition into this table's select_cond. */ + DBUG_ASSERT(!outer_tab->select_cond || outer_tab->select_cond->fixed()); + outer_tab->select_cond= + !outer_tab->select_cond ? tmp_cond : + new (thd->mem_root) + Item_cond_and(thd, outer_tab->select_cond, tmp_cond); + if (!outer_tab->select_cond) + return true; + + outer_tab->select_cond->quick_fix_field(); + outer_tab->select_cond->update_used_tables(); + if (outer_tab->select) + outer_tab->select->cond= outer_tab->select_cond; + } + + return false; +} + + static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { @@ -15024,33 +15252,33 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) join_tab != end_with; join_tab++) { - if (*join_tab->on_expr_ref) + if (!*join_tab->on_expr_ref || !join_tab->first_inner) + continue; + + JOIN_TAB *cond_tab= join_tab->first_inner; + COND *tmp_cond= make_cond_for_table(thd, *join_tab->on_expr_ref, + join->const_table_map, + (table_map) 0, -1, FALSE, FALSE); + if (!tmp_cond) { - JOIN_TAB *cond_tab= join_tab->first_inner; - COND *tmp_cond= make_cond_for_table(thd, *join_tab->on_expr_ref, - join->const_table_map, - (table_map) 0, -1, FALSE, FALSE); - if (!tmp_cond) - { - if (!thd->is_error()) - continue; - DBUG_RETURN(1); - } - tmp_cond= new (thd->mem_root) Item_func_trig_cond(thd, tmp_cond, - &cond_tab->not_null_compl); - if (!tmp_cond) - DBUG_RETURN(1); - tmp_cond->quick_fix_field(); - cond_tab->select_cond= !cond_tab->select_cond ? tmp_cond : - new (thd->mem_root) Item_cond_and(thd, cond_tab->select_cond, - tmp_cond); - if (!cond_tab->select_cond) - DBUG_RETURN(1); - cond_tab->select_cond->quick_fix_field(); - cond_tab->select_cond->update_used_tables(); - if (cond_tab->select) - cond_tab->select->cond= cond_tab->select_cond; - } + if (!thd->is_error()) + continue; + DBUG_RETURN(1); + } + tmp_cond= new (thd->mem_root) Item_func_trig_cond(thd, tmp_cond, + &cond_tab->not_null_compl); + if (!tmp_cond) + DBUG_RETURN(1); + tmp_cond->quick_fix_field(); + cond_tab->select_cond= !cond_tab->select_cond ? tmp_cond : + new (thd->mem_root) Item_cond_and(thd, cond_tab->select_cond, + tmp_cond); + if (!cond_tab->select_cond) + DBUG_RETURN(1); + cond_tab->select_cond->quick_fix_field(); + cond_tab->select_cond->update_used_tables(); + if (cond_tab->select) + cond_tab->select->cond= cond_tab->select_cond; } @@ -15185,7 +15413,12 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) cond_tab->select->cond= cond_tab->select_cond; } } - first_inner_tab= first_inner_tab->first_upper; + + if (push_on_expr_to_later_outer_tables(thd, join, last_tab, + first_inner_tab, on_expr, + &used_tables2)) + DBUG_RETURN(1); + first_inner_tab= first_inner_tab->first_upper; } if (!tab->bush_children) i++; @@ -15927,6 +16160,29 @@ end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) #endif */ + +/* + Return true if the given TABLE_LIST is a FULL JOIN operand or is + embedded (at any depth) within a FULL JOIN nest. Join caching is + disabled for such tables because the null-complement rescan requires a + plain sequential scan of the right side (rather than one that reads + from a buffered join cache). +*/ +static inline bool is_in_full_join_scope(TABLE_LIST *tl) +{ + if (tl->outer_join & JOIN_TYPE_FULL) + return true; + for (TABLE_LIST *embedding= tl->embedding; + embedding; + embedding= embedding->embedding) + { + if (embedding->outer_join & JOIN_TYPE_FULL) + return true; + } + return false; +} + + static uint check_join_cache_usage(JOIN_TAB *tab, ulonglong options, @@ -15955,6 +16211,9 @@ uint check_join_cache_usage(JOIN_TAB *tab, BKA_HINT_ENUM, false); join->return_tab= 0; + if (is_in_full_join_scope(tab->tab_list)) + goto no_join_cache; + if (tab->no_forced_join_cache || (hint_disables_bnl && no_bka_cache)) goto no_join_cache; @@ -19913,6 +20172,17 @@ static void rewrite_full_to_left(TABLE_LIST *left_table, */ DBUG_ASSERT(right_table->on_expr); + /* + Clear the left table's ON expression and prep_on_expr. The + parser set on_expr on both sides of the FULL JOIN via + add_join_on(), but after the rewrite to LEFT JOIN only the + right table should carry the ON clause. Clear prep_on_expr + too so reinit_before_use() won't restore a stale expression + for prepared statement re-execution. + */ + left_table->on_expr= nullptr; + left_table->prep_on_expr= nullptr; + // Only the right table in a LEFT JOIN has the naming context in the grammar left_table->on_context= nullptr; } @@ -19962,10 +20232,18 @@ static void rewrite_full_to_right(TABLE_LIST *left_table, 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; + /* + Update prep_on_expr to match the post-rewrite state so that + reinit_before_use() restores the correct ON expressions for + prepared statement re-execution. The ON expression moved from + the right table to the left, so prep_on_expr must follow. + */ + left_table->prep_on_expr= right_table->prep_on_expr; + right_table->prep_on_expr= nullptr; + /* Prepare the right table to become the left table by clearing its context. The left table retains the context @@ -20025,19 +20303,7 @@ static COND *rewrite_full_outer_joins(JOIN *join, that). */ if ((*right_table)->outer_join & JOIN_TYPE_LEFT) - { - if (join->thd->lex->describe) - DBUG_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(ME_FATAL), - "FULL JOINs that cannot be converted to LEFT, RIGHT, or " - "INNER JOINs"); - DBUG_RETURN(nullptr); - } + DBUG_RETURN(conds); /* Must always see the right table before the left. Down below, we deal @@ -20102,12 +20368,12 @@ static COND *rewrite_full_outer_joins(JOIN *join, If the left table, be it a nested join or not, rejects nulls for the WHERE condition, then rewrite. */ - *not_null_tables= left_not_null_tables; if (left_used_tables & *not_null_tables) { rewrite_full_to_left(left_table, *right_table); --join->thd->lex->full_join_count; } + *not_null_tables= left_not_null_tables; // else the FULL JOIN cannot be rewritten, pass it along. } @@ -20115,6 +20381,40 @@ static COND *rewrite_full_outer_joins(JOIN *join, } +/** + Check that the right side of every FULL JOIN is a base table. + + Returns TRUE and raises ER_FULL_JOIN_BASE_TABLES_ONLY if a derived table + or view is found on the right side of any FULL JOIN. +*/ + +static bool +check_full_join_base_tables(List *join_list) +{ + TABLE_LIST *table; + List_iterator li(*join_list); + + while ((table= li++)) + { + if ((table->outer_join & JOIN_TYPE_FULL) && + (table->outer_join & JOIN_TYPE_RIGHT)) + { + if (table->derived || table->is_view()) + { + my_error(ER_FULL_JOIN_BASE_TABLES_ONLY, MYF(0), table->alias.str); + return true; + } + } + + if (table->nested_join && + check_full_join_base_tables(&table->nested_join->join_list)) + return true; // already set thd error state + } + + return false; +} + + /** Simplify joins replacing outer joins by inner joins whenever it's possible. @@ -20255,19 +20555,33 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN); DBUG_ENTER("simplify_joins"); - /* + /* Try to simplify join operations from join_list. - The most outer join operation is checked for conversion first. + The most outer join operation is checked for conversion first. */ while ((table= li++)) { - // We only support FULL JOIN on base tables. - if (table->outer_join & JOIN_TYPE_FULL && - !table->is_non_derived()) + /* + The join list is in reverse, so we see the right side of a FULL + JOIN before the left and attempted the rewrite on that earlier + iteration. If the FULL JOIN was rewritten, JOIN_TYPE_FULL is + cleared on both sides. If it is still set, then no rewrite + occurred and this is the LEFT side of a FULL JOIN. + + So we still need to propagate this side's bits into the embedding's + used_tables bitmap so downstream machinery knows that this table + participates in the nest. + */ + if ((table->outer_join & JOIN_TYPE_FULL) && + (table->outer_join & JOIN_TYPE_LEFT)) { - my_error(ER_FULL_JOIN_BASE_TABLES_ONLY, MYF(0), - table->alias.str); - DBUG_RETURN(nullptr); + if (table->embedding) + { + table->embedding->nested_join->used_tables |= + table->nested_join ? table->nested_join->used_tables + : table->get_map(); + } + continue; } table_map used_tables; @@ -20282,14 +20596,14 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) if (table->on_expr) { Item *expr= table->on_expr; - /* - If an on expression E is attached to the table, + /* + If an on expression E is attached to the table, check all null rejected predicates in this expression. If such a predicate over an attribute belonging to an inner table of an embedded outer join is found, the outer join is converted to an inner join and - the corresponding on expression is added to E. - */ + the corresponding on expression is added to E. + */ expr= simplify_joins(join, &nested_join->join_list, expr, in_sj || table->sj_on_expr); if (!expr && join->thd->is_error()) @@ -20299,8 +20613,17 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool in_sj) { DBUG_ASSERT(expr); + /* + Preserve our ON expression, and if we're a FULL JOIN, + then preserve our partner's ON expression too. + */ table->on_expr= expr; table->prep_on_expr= expr->copy_andor_structure(join->thd); + if (table->outer_join & JOIN_TYPE_FULL) + { + table->foj_partner->on_expr= expr; + table->foj_partner->prep_on_expr= table->prep_on_expr; + } } } conds= simplify_nested_join(join, table, conds, in_sj, @@ -20881,6 +21204,65 @@ static void restore_prev_nj_state(JOIN_TAB *last) } +/* + Compute full_join_nest_tables, the union of direct_children_map of + every join nest that transitively contains a FULL JOIN table. + + The optimizer must place all tables of such a nest before placing + tables outside it, because the FULL JOIN null-complement algorithm + requires its tables to be adjacent in the join order. +*/ + +static void +compute_full_join_nest_tables(JOIN *join, SELECT_LEX *lex) +{ + join->full_join_nest_tables= 0; + if (!join->thd->lex->full_join_count) + return; + + TABLE_LIST *tl; + List_iterator ti(lex->leaf_tables); + while ((tl= ti++)) + { + if (!(tl->outer_join & JOIN_TYPE_FULL)) + continue; + + for (TABLE_LIST *embedding= tl->embedding; + embedding; + embedding= embedding->embedding) + { + if (embedding->nested_join) + join->full_join_nest_tables|= + embedding->nested_join->direct_children_map; + } + } +} + + +/* + If any FULL JOIN nest tables in the candidate pool are still + unplaced, return just those tables; otherwise return 0 to indicate + no restriction. + + The null-complement algorithm requires FULL JOIN tables to be + adjacent in the execution order. Allowing an outside table to be + interleaved between FULL JOIN partners would break the algorithm. +*/ + +static table_map +restrict_to_unplaced_fj_tables(JOIN *join, uint idx, table_map pool) +{ + if (!join->full_join_nest_tables) + return 0; + + table_map remaining= 0; + for (uint i= idx; i < join->table_count; i++) + remaining|= join->best_ref[i]->table->map; + + return join->full_join_nest_tables & remaining & pool; +} + + /* Compute allowed_top_level_tables - a bitmap of tables one can put into the join order if the last table in the join prefix is not inside any outer @@ -20929,33 +21311,33 @@ void JOIN::calc_allowed_top_level_tables(SELECT_LEX *lex) embedding= embedding->embedding; } - // Ok we are in the parent nested outer join nest. - if (!embedding) - { - allowed_top_level_tables |= map; - continue; - } - embedding->nested_join->direct_children_map |= map; - - // Walk to grand-parent join nest. - embedding= embedding->embedding; - - // Walk out of any semi-join nests - while (embedding && !embedding->on_expr) + /* + Walk through all upper join nests, adding this table to each + nest's direct_children_map. This must traverse the entire chain + so that deeply nested FULL JOINs are handled correctly. + The original code only walked two levels (parent and + grandparent), which caused an assertion failure when four or + more tables were chained with FULL JOINs. + */ + while (true) { - DBUG_ASSERT(embedding->sj_on_expr); + if (!embedding) + { + allowed_top_level_tables |= map; + break; + } embedding->nested_join->direct_children_map |= map; embedding= embedding->embedding; + // Walk out of any semi-join nests + while (embedding && !embedding->on_expr) + { + embedding->nested_join->direct_children_map |= map; + embedding= embedding->embedding; + } } - - if (embedding) - { - DBUG_ASSERT(embedding->on_expr); // Impossible, see above - embedding->nested_join->direct_children_map |= map; - } - else - allowed_top_level_tables |= map; } + + compute_full_join_nest_tables(this, lex); DBUG_VOID_RETURN; } @@ -20985,10 +21367,21 @@ table_map JOIN::get_allowed_nj_tables(uint idx) } } } - // Return bitmap of tables not in any join nest - if (emb_sjm_nest) - return emb_sjm_nest->nested_join->direct_children_map; - return allowed_top_level_tables; + + /* + Select the set of tables the optimizer may pick from next. When + placing tables inside a materialized semijoin, that set is the + SJM nest's direct children. Otherwise it is the set of top-level + tables. + */ + const table_map pool= emb_sjm_nest + ? emb_sjm_nest->nested_join->direct_children_map + : allowed_top_level_tables; + + if (table_map fj_only= restrict_to_unplaced_fj_tables(this, idx, pool)) + return fj_only; + + return pool; } @@ -24456,6 +24849,67 @@ Next_select_func setup_end_select_func(JOIN *join) -1 if error should be sent */ +/* + Allocate a full_join_duplicate_filter for each right-side FULL JOIN + table in the top-level join-tab range [start_tab, start_tab+count). + + The filter records right-side rowids matched during the LEFT JOIN + pass so the null-complement rescan can skip them. Only base tables + are supported on the right side of a FULL JOIN, but a query may + contain multiple (possibly nested) FULL JOINs, so each right-side + tab gets its own filter. + + Returns true on allocation failure (error already reported). +*/ + +static bool alloc_full_join_duplicate_filters(JOIN *join, JOIN_TAB *start_tab, + uint count) +{ + if (!join->thd->lex->full_join_count) + return false; + + for (uint i= 0; i < count; ++i) + { + start_tab[i].fj_dups= nullptr; + start_tab[i].fj_null_complement_done= false; + if (!(start_tab[i].tab_list->outer_join & JOIN_TYPE_FULL) || + !(start_tab[i].tab_list->outer_join & JOIN_TYPE_RIGHT)) + continue; + + DBUG_ASSERT(count >= 2); + auto fj_dups= join->thd->alloc(1); + if (!fj_dups) + return true; + if (fj_dups->init(join->thd, &start_tab[i])) + return true; + start_tab[i].fj_dups= fj_dups; + } + return false; +} + + +/* + Release the temp tables backing each FULL JOIN duplicate filter + allocated by alloc_full_join_duplicate_filters. +*/ + +static void free_full_join_duplicate_filters(JOIN *join, JOIN_TAB *start_tab, + uint count) +{ + if (!join->thd->lex->full_join_count) + return; + + for (uint i= 0; i < count; ++i) + { + if (!(start_tab[i].tab_list->outer_join & JOIN_TYPE_FULL) || + start_tab[i].fj_dups == nullptr) + continue; + start_tab[i].fj_dups->cleanup(join->thd); + start_tab[i].fj_dups= nullptr; + } +} + + static int do_select(JOIN *join, Procedure *procedure) { @@ -24582,14 +25036,20 @@ do_select(JOIN *join, Procedure *procedure) join->join_tab[top_level_tables-1].cached_pfs_batch_update= join->join_tab[top_level_tables-1].pfs_batch_update(); - JOIN_TAB *join_tab= join->join_tab + + JOIN_TAB *start_tab= join->join_tab + (join->tables_list ? join->const_tables : 0); + + if (alloc_full_join_duplicate_filters(join, start_tab, top_level_tables)) + DBUG_RETURN(-1); + if (join->outer_ref_cond && !join->outer_ref_cond->val_bool()) error= NESTED_LOOP_NO_MORE_ROWS; else - error= join->first_select(join,join_tab,0); + error= join->first_select(join,start_tab,0); if (error >= NESTED_LOOP_OK && likely(join->thd->killed != ABORT_QUERY)) - error= join->first_select(join,join_tab,1); + error= join->first_select(join,start_tab,1); + + free_full_join_duplicate_filters(join, start_tab, top_level_tables); } join->thd->limit_found_rows= join->send_records - join->duplicate_rows; @@ -24756,7 +25216,7 @@ sub_select_postjoin_aggr(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) { rc= aggr->end_send(); if (rc >= NESTED_LOOP_OK) - rc= sub_select(join, join_tab, end_of_records); + rc= sub_select(join, join_tab, true); DBUG_RETURN(rc); } @@ -24979,6 +25439,67 @@ sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS. */ +/* + Rescan the right table of a FULL JOIN to emit null-complemented + rows for the right-side rows that were not matched during the first + (LEFT JOIN) pass. + + The rescan is forced to a plain sequential scan (not the original + JT_REF / JT_EQ_REF access method, which would look up keys derived + from the now-nullified left side and return zero rows). The pushed + SQL_SELECT and on_precond are cleared for the rescan and restored + afterwards so that subsequent executions (prepared statement + re-execution, correlated subquery iterations) see the original + access method. + + Runs at end_of_records, so it is safe to restore without restarting + the handler scan. Caller guarantees that join_tab is the RIGHT side + of a FULL JOIN and writing_null_complements is currently false. +*/ + +static enum_nested_loop_state +run_fj_null_complement_pass(JOIN *join, JOIN_TAB *join_tab) +{ + join_tab->writing_null_complements= true; + Item *saved_on_precond= join_tab->on_precond; + join_tab->on_precond= nullptr; + + // Restart reading from the right table as a full scan. + join_tab->table->file->ha_end_keyread(); + if (join_tab->type == JT_FT) + join_tab->table->file->ha_ft_end(); + else if (join_tab->table->hlindex && + join_tab->table->hlindex->context) + join_tab->table->hlindex_read_end(); + else + join_tab->table->file->ha_index_or_rnd_end(); + + // Save-off important state before restarting the full scan. + READ_RECORD saved_read_record= join_tab->read_record; + READ_RECORD::Setup_func saved_read_first= join_tab->read_first_record; + SQL_SELECT *saved_select= join_tab->select; + join_tab->read_first_record= join_init_read_record; + join_tab->select= nullptr; + + // full scan of right table and null-complement generation + enum_nested_loop_state nls= sub_select(join, join_tab, 0); + if (nls >= NESTED_LOOP_OK) + nls= sub_select(join, join_tab, 1); + + // restore the saved-off state. + join_tab->read_first_record= saved_read_first; + join_tab->read_record= saved_read_record; + join_tab->select= saved_select; + join_tab->writing_null_complements= false; + join_tab->fj_null_complement_done= true; + join_tab->on_precond= saved_on_precond; + + if (nls == NESTED_LOOP_NO_MORE_ROWS) + nls= NESTED_LOOP_OK; + return nls; +} + + enum_nested_loop_state sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) { @@ -25008,8 +25529,40 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) if (end_of_records) { - enum_nested_loop_state nls= - (*join_tab->next_select)(join,join_tab+1,end_of_records); + enum_nested_loop_state nls= NESTED_LOOP_OK; + + // eor means 'end of records' + bool eor_forwarded_by_rescan= false; + + /* + For chained FULL JOINs (e.g. A FJ B FJ C), the inner FJ's + null-complement rescan can produce rows that match the outer + FJ's right side. Those matches must be recorded in the outer + fj_dups filter before the outer's own rescan runs, otherwise + the outer re-emits an already-matched right-side row as + "unmatched". Therefore, run *this tab's rescan first, and only + then propagate end_of_records deeper (which triggers the next + tab's rescan, now with updated fj_dups state). The + fj_null_complement_done flag prevents a subsequent EOR from + re-triggering this same rescan. + + run_fj_null_complement_pass internally calls + sub_select(self, true) which already forwards EOR down the + chain, so we must not forward it a second time below. + */ + if (join_tab->fj_dups && // is the right side of a FULL JOIN + !join_tab->writing_null_complements && + !join_tab->fj_null_complement_done && + (join_tab->tab_list->outer_join & JOIN_TYPE_FULL) && + (join_tab->tab_list->outer_join & JOIN_TYPE_RIGHT)) + { + nls= run_fj_null_complement_pass(join, join_tab); + eor_forwarded_by_rescan= true; + } + + if (!eor_forwarded_by_rescan && nls >= NESTED_LOOP_OK) + nls= (*join_tab->next_select)(join,join_tab+1,end_of_records); + DBUG_RETURN(nls); } join_tab->tracker->r_scans++; @@ -25118,7 +25671,15 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) if (rc == NESTED_LOOP_NO_MORE_ROWS) { - if (join_tab->last_inner && !join_tab->found) + /* + Skip the standard outer-join null complement when we are doing a + FULL JOIN null-complement rescan of the right table. During + that rescan the evaluate_join_record() early-exit path handles + unmatched rows directly, and the normal "no match found" path + must not fire because join_tab->found is not being maintained. + */ + if (join_tab->last_inner && !join_tab->found && + !join_tab->writing_null_complements) { rc= evaluate_null_complemented_join_record(join, join_tab); if (rc == NESTED_LOOP_NO_MORE_ROWS) @@ -25134,6 +25695,101 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) DBUG_RETURN(rc); } + +/* + 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). +*/ +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) + { + List_iterator li(tl->nested_join->join_list); + TABLE_LIST *child; + while ((child= li++)) + mark_table_list_as_null_row(child); + } +} + + +/* Reverse of mark_table_list_as_null_row: restore real row data. */ +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) + { + List_iterator li(tl->nested_join->join_list); + TABLE_LIST *child; + while ((child= li++)) + unmark_table_list_as_null_row(child); + } +} + + +/* + Handle a single row read during a FULL JOIN null-complement rescan. + + Called from evaluate_join_record when writing_null_complements is + set and the current table has an fj_dups filter (i.e. it is the + right side of a FULL JOIN). The steps are: + + 1. Skip rows whose rowid was already recorded during the first + (LEFT JOIN) pass. + 2. Null-complement the FULL JOIN partner side. + 3. Apply WHERE (only) to the null-complemented row. select_cond + has the structure + trigcond(found, WHERE) AND trigcond(not_null_compl, ON) + so setting found=1 activates WHERE while not_null_compl=0 + disables ON (which returns TRUE when its trigcond is off). + Restore both flags after evaluation. + 4. Forward the row through the remaining join tabs. + 5. Unmark the partner side before returning. +*/ + +static enum_nested_loop_state +evaluate_fj_null_complement_row(JOIN *join, JOIN_TAB *join_tab, + COND *select_cond) +{ + bool is_dup= false; + if (join_tab->fj_dups->check_rowids(join->thd, &is_dup)) + return NESTED_LOOP_ERROR; + if (is_dup) + return NESTED_LOOP_OK; + + mark_table_list_as_null_row(join_tab->tab_list->foj_partner); + + if (select_cond) + { + bool saved_found= join_tab->found; + bool saved_nnc= join_tab->not_null_compl; + join_tab->found= 1; + join_tab->not_null_compl= 0; + bool where_ok= select_cond->val_bool(); + join_tab->found= saved_found; + join_tab->not_null_compl= saved_nnc; + if (!where_ok) + { + unmark_table_list_as_null_row(join_tab->tab_list->foj_partner); + return NESTED_LOOP_OK; + } + } + + enum_nested_loop_state rc= + (*join_tab->next_select)(join, join_tab+1, false); + join->thd->get_stmt_da()->inc_current_row_for_warning(); + + unmark_table_list_as_null_row(join_tab->tab_list->foj_partner); + + if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS) + return rc; + return NESTED_LOOP_OK; +} + + /** @brief Process one row of the nested loop join. @@ -25173,6 +25829,10 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } + // FULL JOIN null-complement generation. + if (join_tab->writing_null_complements && join_tab->fj_dups) + DBUG_RETURN(evaluate_fj_null_complement_row(join, join_tab, select_cond)); + join_tab->tracker->r_rows++; if (select_cond) @@ -25305,6 +25965,18 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, DBUG_PRINT("counts", ("examined_rows: %llu found: %d", (ulonglong) join->thd->m_examined_row_count, (int) found)); + /* + For FULL JOIN: reaching this point means the ON condition matched + (because when 'found' is still 0, the WHERE trigcond is disabled). + Remember the right-side rowid so the null-complement pass skips + it, even if the WHERE later rejects the row and clears found. + */ + if (join_tab->fj_dups && !join_tab->writing_null_complements) + { + if (join_tab->fj_dups->remember_rowids(join->thd)) + DBUG_RETURN(NESTED_LOOP_ERROR); + } + if (found) { enum enum_nested_loop_state rc; @@ -26388,8 +27060,7 @@ end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) to get fields from previous tab. */ DBUG_ASSERT(join_tab == NULL || join_tab != join->join_tab); - //TODO pass fields via argument - List *fields= join_tab ? (join_tab-1)->fields : join->fields; + List *fields= join->fields; if (end_of_records) { @@ -27169,7 +27840,7 @@ make_cond_for_table_from_pred(THD *thd, Item *root_cond, Item *cond, { table_map rand_table_bit= (table_map) RAND_TABLE_BIT; - if (used_table && !(cond->used_tables() & used_table)) + if (!cond || (used_table && !(cond->used_tables() & used_table))) return (COND*) 0; // Already checked if (cond->type() == Item::COND_ITEM) diff --git a/sql/sql_select.h b/sql/sql_select.h index 2507a871a6005..66413c540ecd2 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -249,10 +249,34 @@ class AGGR_OP; class Filesort; struct SplM_plan_info; class SplM_opt_info; +class full_join_duplicate_filter; typedef struct st_join_table { - TABLE *table; - TABLE_LIST *tab_list; + /* + Non-NULL only on the right side of a FULL JOIN. Tracks right-side + rowids matched during the LEFT JOIN pass so the null-complement + rescan can skip them. + */ + full_join_duplicate_filter *fj_dups; + + /* + True when sending null-complemented rows to the result sink when + evaluating the right side of a FULL JOIN. + */ + bool writing_null_complements{false}; + + /* + Set to true once this tab's FULL JOIN null-complement rescan has + finished. Used to (a) suppress a second rescan triggered by + repeated end_of_records propagation in chained FULL JOIN plans, + and (b) make fj_null_complement_pending() report "done" rather + than "pending" for tabs whose rescan already ran. + */ + bool fj_null_complement_done{false}; + + TABLE *table; /**< pointer to table cursor */ + TABLE_LIST *tab_list; /**< pointer to query table, e.g. `t1` */ + KEYUSE *keyuse; /**< pointer to first used key */ KEY *hj_key; /**< descriptor of the used best hash join key not supported by any index */ @@ -1193,6 +1217,14 @@ class AGGR_OP :public Sql_alloc {}; enum_nested_loop_state put_record() { return put_record(false); }; + + /* + Flush the last pending group to the temp table without reading or + sending accumulated rows. Used when a FULL JOIN null-complement + pass is still pending and we need to keep accumulating rows. + */ + enum_nested_loop_state flush_record() { return put_record(true); }; + /* Send the result of operation further (to a next operation/client) This function is called after all records were put into tmp table. @@ -1414,6 +1446,12 @@ class JOIN :public Sql_alloc table_map eq_ref_tables; table_map allowed_top_level_tables; + + /* + Tables in nests that contain FULL JOINs, along with their nest siblings. + */ + table_map full_join_nest_tables; + ha_rows send_records,found_records, accepted_rows; /* diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 00f84827fec17..992799f5873c8 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12597,6 +12597,7 @@ join_table: expr { add_join_on(thd, $5, $8); + add_join_on(thd, $1, $8); $1->on_context= Lex->pop_context(); $5->on_context= $1->on_context; Select->parsing_place= NO_MATTER; diff --git a/sql/table.cc b/sql/table.cc index 6155d2aa28f2b..c86b05f1a8aa2 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -10241,6 +10241,8 @@ bool TABLE_LIST::init_derived(THD *thd, bool init_view) hint_table_state(thd, this, MERGE_HINT_ENUM, optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_MERGE)); + DBUG_ASSERT(!(outer_join & JOIN_TYPE_FULL) || foj_partner); + if (!is_materialized_derived() && unit->can_be_merged() && /* Following is special case of @@ -10268,7 +10270,13 @@ bool TABLE_LIST::init_derived(THD *thd, bool init_view) (thd->lex->sql_command == SQLCOM_DELETE && (((Sql_cmd_delete *) thd->lex->m_sql_cmd)->is_multitable() || thd->lex->query_tables->is_multitable())))) && - !is_recursive_with_table()) + !is_recursive_with_table() && + /* + Derived tables that participate in a FULL JOIN must not be + merged because the FULL JOIN null-complement logic only + works when the physical table is available. + */ + !foj_partner) set_merged_derived(); else set_materialized_derived();