From c56d2303aa6236d23b61f8c8c483f531e9e2dfd5 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Thu, 30 Oct 2025 14:43:53 -0400 Subject: [PATCH] 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. --- mysql-test/main/join.result | 100 ++++++++++++++++++++++++++++++++++++ mysql-test/main/join.test | 62 +++++++++++++++++++++- sql/sql_parse.cc | 5 -- sql/sql_select.cc | 39 ++++++++++++-- sql/sql_view.cc | 1 + sql/sql_yacc.yy | 9 ++-- sql/table.h | 9 ++-- 7 files changed, 206 insertions(+), 19 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index 03b358c5bf63a..ec51f15f168c1 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3667,17 +3667,117 @@ create table t2 (a int); insert into t2 (a) values (1),(2),(3); select * from t1 full join t2 on t1.a = t2.a; ERROR 42000: This version of MariaDB doesn't yet support 'full join' +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 36286c95e452f..755653208ec87 100644 --- a/mysql-test/main/join.test +++ b/mysql-test/main/join.test @@ -2055,21 +2055,79 @@ 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 +--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 377dfc4006d3a..517fcdaba0299 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -10305,11 +10305,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->has_full_outer_join; - if (thd->lex->has_full_outer_join) - 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 dc1f5aeac0168..051c04e3f65f0 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1894,6 +1894,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->has_full_outer_join && // FULL JOIN not yet supported... + !thd->lex->is_view_context_analysis() && // ...but allow VIEW creation... + !thd->lex->describe) // ...and limited EXPLAIN support during development + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "full join"); + DBUG_RETURN(0); // All OK err: @@ -2767,7 +2779,13 @@ 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 + /* + 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. + */ + else if (!thd->lex->has_full_outer_join) { if (optimize_stage2()) DBUG_RETURN(1); @@ -31710,8 +31728,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 ")); @@ -31722,9 +31745,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 4ae53c2be50e0..745e158102b3c 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1909,6 +1909,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->has_full_outer_join|= view_query_lex->has_full_outer_join; lex_end(view_query_lex); end: diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index cdcd3bf452304..24fd9b5510e5e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -12387,8 +12387,9 @@ join_table: } expr { - add_join_on(thd, $1, $8); + add_join_on(thd, $5, $8); $1->on_context= Lex->pop_context(); + $5->on_context= $1->on_context; Select->parsing_place= NO_MATTER; $$= $1; Lex->has_full_outer_join= true; @@ -12415,11 +12416,13 @@ join_table: Select->add_joined_table($1); $1->outer_join|= (JOIN_TYPE_LEFT | - JOIN_TYPE_FULL); + JOIN_TYPE_FULL | + JOIN_TYPE_NATURAL); Select->add_joined_table($6); $6->outer_join|= (JOIN_TYPE_RIGHT | - JOIN_TYPE_FULL); + JOIN_TYPE_FULL | + JOIN_TYPE_NATURAL); add_join_natural($6,$1,NULL,Select); Lex->has_full_outer_join= true; diff --git a/sql/table.h b/sql/table.h index 855c26d3dc12d..58974ed2d2806 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2212,10 +2212,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_FULL 4U -#define JOIN_TYPE_OUTER 8U /* 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