From 1fa730f8bb860ece49cf09a40921f0a9bed2ce31 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 29 Oct 2025 13:47:05 -0400 Subject: [PATCH] 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 | 24 +++++++++++++ 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 | 67 ++++++++++++++++++++++++++++++++++--- sql/table.h | 3 +- 7 files changed, 125 insertions(+), 5 deletions(-) diff --git a/mysql-test/main/join.result b/mysql-test/main/join.result index d9dbf80642b34..03b358c5bf63a 100644 --- a/mysql-test/main/join.result +++ b/mysql-test/main/join.result @@ -3657,3 +3657,27 @@ 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); +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..36286c95e452f 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); +# 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 290e3da83a919..64cfc5e255d90 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1343,6 +1343,7 @@ void LEX::start(THD *thd_arg) memset(&trg_chistics, 0, sizeof(trg_chistics)); selects_for_hint_resolution.empty(); + has_full_outer_join= false; DBUG_VOID_RETURN; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d59f7158805b4..42c4449a0107e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -3593,6 +3593,9 @@ struct LEX: public Query_tables_list vers_select_conds_t vers_conditions; vers_select_conds_t period_conditions; + /* False by default, this will be true if the query has a full join. */ + bool has_full_outer_join; + inline void free_set_stmt_mem_root() { DBUG_ASSERT(!is_arena_for_set_stmt()); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 517fcdaba0299..377dfc4006d3a 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -10305,6 +10305,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->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_yacc.yy b/sql/sql_yacc.yy index 6fc288c055160..cdcd3bf452304 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1183,8 +1183,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 @@ -12365,8 +12365,66 @@ 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, $1, $8); + $1->on_context= Lex->pop_context(); + Select->parsing_place= NO_MATTER; + $$= $1; + Lex->has_full_outer_join= true; + } + | 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->has_full_outer_join= true; + } + | 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); + + Select->add_joined_table($6); + $6->outer_join|= (JOIN_TYPE_RIGHT | + JOIN_TYPE_FULL); + + add_join_natural($6,$1,NULL,Select); + Lex->has_full_outer_join= true; + } + ; inner_join: /* $$ set if using STRAIGHT_JOIN, false otherwise */ JOIN_SYM { $$ = 0; } @@ -16691,7 +16749,6 @@ keyword_func_sp_var_and_label: | FILE_SYM | FIRST_SYM | FOUND_SYM - | FULL | GENERAL | GENERATED_SYM | GRANTS @@ -17021,6 +17078,7 @@ reserved_keyword_udt_not_param_type: | FIRST_VALUE_SYM | FOREIGN | FROM + | FULL | FULLTEXT_SYM | GOTO_ORACLE_SYM | GRANT @@ -17748,6 +17806,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 fc3e625b2438a..855c26d3dc12d 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2214,7 +2214,8 @@ class IS_table_read_plan; #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_FULL 4U +#define JOIN_TYPE_OUTER 8U /* Marker that this is an outer join */ /* view WITH CHECK OPTION parameter options */ #define VIEW_CHECK_NONE 0