mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	 dae5a99c73
			
		
	
	
	dae5a99c73
	
	
	
		
			
			Parser changes made by Alexander Barkov <bar@mariadb.com>. Part of the tests made by Iqbal Hassan <iqbal@hasprime.com>. Initially marking with ORA_JOIN flag made also by Iqbal Hassan <iqbal@hasprime.com>. Main idea is that parser mark fields with (+) with a flag (ORA_JOIN). During Prepare the flag bring to the new created items if needed. Later after preparing (fix_firlds()) WHERE confition the relations betweel the tables analyzed and tables reordered so to make JOIN/LEFT JOIN operators in chain equivalent to the query with oracle outer join operator (+). Then the flags of (+) removed.
		
			
				
	
	
		
			1147 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1147 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2000, 2016, Oracle and/or its affiliates.
 | |
|    Copyright (c) 2010, 2024, MariaDB
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */
 | |
| 
 | |
| #include "lex_ident_sys.h"
 | |
| #include "mariadb.h"
 | |
| #include "sql_base.h"
 | |
| #include "sql_parse.h"      // check_stack_overrun
 | |
| 
 | |
| /*
 | |
|   Support for Oracle's outer join syntax.
 | |
| 
 | |
|   == Contents ==
 | |
|   1. Basic syntax
 | |
|   1.1. Outer join operator
 | |
|   1.2. Outer-joining tables
 | |
|   1.3 Example 1: peer outer joins
 | |
|   1.4 Example 2: chained outer joins
 | |
|   1.5 Outer join graph
 | |
|   2. Implementation
 | |
|   2.1 Parser
 | |
|   2.2 Conversion to LEFT JOIN tree
 | |
|   2.2.1 Building the graph
 | |
|   2.2.2 Ordering the graph
 | |
|   2.2.3 Building the TABLE_LIST structure
 | |
|   3. Debugging
 | |
| 
 | |
| 
 | |
|   == 1. Basic syntax ==
 | |
| 
 | |
|   Oracle's outer join syntax is like this:
 | |
| 
 | |
|     set sql_mode='oracle';
 | |
|     select * from t1, t2 where t1.col=t2.col(+)
 | |
| 
 | |
|   The (+) is the "outer join operator". It specifies that table t2 is outer-
 | |
|   joined (i.e. is INNER) and the predicate containing the (+) is the outer
 | |
|   join's ON expression. This makes the above query to be equivalent to:
 | |
| 
 | |
|     select * from t1.col left join t2 on t1.col=t2.col
 | |
| 
 | |
|   == 1.1. Outer join operator ==
 | |
|   Outer join operators may occur only in the WHERE clause. The WHERE clause
 | |
|   may be one predicate or multiple predicates connected with AND. Each of the
 | |
|   predicates
 | |
|    - may reference only one outer-joined (aka the "INNER") table (all
 | |
|      references to its columns have "outer join operator")
 | |
|    - may reference zero, one or more "OUTER" tables (without outer join
 | |
|      operator).
 | |
| 
 | |
|   A predicate that refers to an INNER table and OUTER table(s) prescribes that
 | |
|   the INNER table is joined with outer join operation.
 | |
| 
 | |
|   A predicate that only refers to an INNER table (like "t1.col(+)=124") will be
 | |
|   added to the table's ON expression, provided there is another predicate that
 | |
|   prescribes that the inner table is joined with outer join operation
 | |
|   (otherwise, the predicate remains in the WHERE and a warning is issued).
 | |
| 
 | |
|   == 1.2. Outer-joining tables ==
 | |
| 
 | |
|   If a query uses outer join operators, the FROM clause must be a simple
 | |
|   comma-separated list of tables (denoting inner join operations):
 | |
| 
 | |
|     FROM t1, t2, ..., tN
 | |
| 
 | |
|   If outer join operators prescribe that some table t_j is joined with outer
 | |
|   join, the FROM clause becomes:
 | |
| 
 | |
|     FROM (t1, ..., tbl) LEFT JOIN t_j ON outer_join_predicates, ... tN
 | |
| 
 | |
|   Here, all tables used by outer_join_predicates are moved to the left (ok to
 | |
|   do as inner join is commutative).
 | |
| 
 | |
|   == 1.3 Example 1: peer outer joins ==
 | |
|   Consider a query:
 | |
| 
 | |
|     select *
 | |
|     from t1,t2,t3
 | |
|     where t1.col=t2.col(+) and t1.col=t3.col(+)
 | |
| 
 | |
|   This has two outer join predicates
 | |
|     OUTER=t1, INNER=t2
 | |
|     OUTER=t1, INNER=t3
 | |
| 
 | |
|   The query can be transformed to
 | |
| 
 | |
|     select *
 | |
|     from (t1 left join t2 on t2.col=t1.col) left join t3 on t1.col=t3.col
 | |
| 
 | |
|   or an equvalent query:
 | |
| 
 | |
|     select *
 | |
|     from (t1 left join t3 on t3.col=t1.col) left join t2 on t1.col=t2.col
 | |
| 
 | |
|   MariaDB optimizer will try to preserve the original order the tables were
 | |
|   listed in the WHERE clause.
 | |
| 
 | |
|   == 1.4 Example 2: chained outer joins ==
 | |
|   The query
 | |
| 
 | |
|     select ...
 | |
|     from t1,t2,t3
 | |
|     where cond1(t1.col, t2.col(+)) and cond2(t2.col, t3.col(+))
 | |
| 
 | |
|   has two outer join predicates
 | |
|     OUTER=t1, INNER=t2
 | |
|     OUTER=t2, INNER=t3
 | |
| 
 | |
|   The query is transformed into
 | |
| 
 | |
|     select ...
 | |
|     from
 | |
|       t1
 | |
|       left join t2 on cond1(t1.col, t2.col)
 | |
|       left join t3 on cond2(t2.col, t3.col)
 | |
| 
 | |
|   Note that the result of transformation is
 | |
|      (t1 left join t2) left join t3
 | |
|   and not
 | |
|      t1 left join (t2 left join t3)
 | |
| 
 | |
|   (these two expressions are in general not equivalent) There's always just one
 | |
|   table on the inner side of the outer join.
 | |
| 
 | |
|   == 1.5 Outer join graph ==
 | |
|   If we take tables as vertexes and OUTER->INNER relationships as edges, then
 | |
|   we get a directed graph.
 | |
| 
 | |
|   The graph must not have cycles. A query that produces a graph with cycles
 | |
|   is aborted with error.
 | |
|   The graph may have alternative paths, like t1->t2->t3 and t1->t4->t3.
 | |
| 
 | |
|   In order to produce the query expression with LEFT JOIN syntax, one needs to
 | |
|   perform topological sorting of the graph.
 | |
|   Then, write out the graph vertices (tables) in an order such that all edges
 | |
|   would come from left to the right.
 | |
| 
 | |
|   == 2. Implementation ==
 | |
| 
 | |
|   == 2.1 Parser ==
 | |
|   The parser recognizes the "(+)" operator. After parsing, Item objects that
 | |
|   have a (+) operator somewhere inside have (item->with_flags() & ORA_JOIN)
 | |
|   flag set.
 | |
| 
 | |
|   == 2.2 Conversion to LEFT JOIN tree ==
 | |
| 
 | |
|   At Name Resolution phase, we convert (+) operators into LEFT JOIN data
 | |
|   structures (that is, a tree of TABLE_LIST objects with ON expressions).
 | |
|   This is done in setup_oracle_join().
 | |
| 
 | |
|   === 2.2.1 Building the graph ===
 | |
|   First, we create an array of table_pos structures. These are the vertices
 | |
|   of the graph.
 | |
|   Then, we analyze the WHERE clause and construct graph's edges.
 | |
| 
 | |
|   == 2.2.2 Ordering the graph ==
 | |
| 
 | |
|   Then, we walk the graph and construct a linked list of table_pos structures
 | |
|   (connected via table_pos::{next,prev}) so that they come in what we call
 | |
|   "LEFT JOIN syntax order". In decreasing order of importance, the criteria
 | |
|   are:
 | |
|   1. Outer tables must come before their inner tables.
 | |
|   2. Tables that are connected to the tables already in the order must come
 | |
|      before those who are not
 | |
|   3. Tables that were listed earlier in the original FROM clause come before.
 | |
| 
 | |
|   == 2.2.3 Building the TABLE_LIST structure ==
 | |
|   Then, we walk through the table_pos objects via {table_pos::next} edges and
 | |
|   create a parsed LEFT JOIN data sructure.
 | |
| 
 | |
|   For a chain of t1-t2-t3-t4-t5 we would create:
 | |
| 
 | |
|      (
 | |
|          (
 | |
|              (
 | |
|                  t1
 | |
|                  [left] join t2 on cond2
 | |
|              )
 | |
|              [left] join t3 on cond3
 | |
|          )
 | |
|          [left] join t4 on cond4
 | |
|      )
 | |
|      [left] join t5 on cond5
 | |
| 
 | |
|   Each pair of () brackets is a TABLE_LIST object representing a join nest. It
 | |
|   has a NESTED_JOIN object which includes its two children.
 | |
| 
 | |
|   Some of the brackets are redundant, this is not a problem because
 | |
|   simplify_joins() will remove them.
 | |
| 
 | |
|   == 3. Debugging ==
 | |
|   One can examine the conversion result by doing this:
 | |
| 
 | |
|     create view v1 as select ...
 | |
|     show create view v1;
 | |
| 
 | |
|   Unlike regular EXPLAIN, this bypasses simplify_joins() call.
 | |
| */
 | |
| 
 | |
| 
 | |
| /**
 | |
|   An outer join graph vertex.
 | |
| */
 | |
| 
 | |
| struct table_pos: public Sql_alloc
 | |
| {
 | |
|   /*
 | |
|     Links the tables in the "LEFT JOIN syntax order"
 | |
|   */
 | |
|   table_pos *next;
 | |
|   table_pos *prev;
 | |
| 
 | |
|   /* Tables we have outgoing edges to. Duplicates are possible. */
 | |
|   List<table_pos> inner_side;
 | |
| 
 | |
|   /* Incoming edges */
 | |
|   List<table_pos> outer_side;
 | |
| 
 | |
|   /* ON condition expressions (to be AND-ed together) */
 | |
|   List<Item> on_conds;
 | |
|   TABLE_LIST *table;
 | |
| 
 | |
|   /* Ordinal number of the table in the original FROM clause */
 | |
|   int order;
 | |
| 
 | |
|   /* TRUE <=> this table is already linked (in the prev<=>next chain) */
 | |
|   bool processed;
 | |
| 
 | |
|   /* TRUE <=> All tables in outer_side are already linked in prev/next */
 | |
|   bool outer_processed;
 | |
| 
 | |
|   bool is_outer_of(table_pos *tab)
 | |
|   {
 | |
|     List_iterator_fast<table_pos> it(outer_side);
 | |
|     table_pos *t;
 | |
|     while((t= it++))
 | |
|     {
 | |
|       if (t == tab)
 | |
|         return TRUE;
 | |
|     }
 | |
|     return FALSE;
 | |
|    }
 | |
| };
 | |
| 
 | |
| static bool add_conditions_to_where(THD *thd, Item **conds,
 | |
|                                     List<Item> &&return_to_where);
 | |
| 
 | |
| /*
 | |
|   Order the tables (which are inner_side peers of some table)
 | |
|    - INNER table comes before its OUTER
 | |
|    - then, tables that were later in the FROM clause come first
 | |
| */
 | |
| 
 | |
| static int table_pos_sort(table_pos *a, table_pos *b, void *arg)
 | |
| {
 | |
|   if (a->is_outer_of(b))
 | |
|     return -1;
 | |
|   if (b->is_outer_of(a))
 | |
|     return 1;
 | |
|   return b->order - a->order;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|     Collect info about table relationships from a part of WHERE condition
 | |
| 
 | |
|   @detail
 | |
|     This processes an individual AND-part of the WHERE clause.
 | |
|     We catch these patterns:
 | |
| 
 | |
|     1. Condition refers to one or more OUTER tables and one INNER table:
 | |
|        cond(outer_table1.col, outer_table2.col, ..., inner_table.col(+))
 | |
| 
 | |
|     2. Condition refers to just one inner table:
 | |
|        cond(inner_table.col(+), constants)
 | |
| 
 | |
|     We note one single inner table and zero or more outer tables and record
 | |
|     these dependencies in the table graph.
 | |
| 
 | |
|     Also, the predicates that will form the ON expression are collected in
 | |
|     table_list::on_conds.
 | |
| */
 | |
| 
 | |
| static bool
 | |
| ora_join_process_expression(THD *thd, Item *cond,
 | |
|                             table_pos *tab, uint n_tables)
 | |
| {
 | |
|   DBUG_ENTER("ora_join_process_expression");
 | |
| 
 | |
|   struct ora_join_processor_param param;
 | |
|   param.inner= NULL;
 | |
|   param.outer.empty();
 | |
|   param.or_present= FALSE;
 | |
| 
 | |
|   if (cond->walk(&Item::ora_join_processor, (void *)(¶m), WALK_NO_REF))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   /*
 | |
|     There should be at least one table for inner part (outer can be absent in
 | |
|     case of constants)
 | |
|   */
 | |
|   DBUG_ASSERT(param.inner != NULL);
 | |
|   table_pos *inner_tab= tab + param.inner->ora_join_table_no;
 | |
| 
 | |
|   if (param.outer.elements > 0)
 | |
|   {
 | |
|     if (param.or_present)
 | |
|     {
 | |
|       my_error(ER_INVALID_USE_OF_ORA_JOIN_WRONG_FUNC, MYF(0));
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     {
 | |
|       // Permanent list can be used in AND
 | |
|       Query_arena_stmt on_stmt_arena(thd);
 | |
|       inner_tab->on_conds.push_back(cond);
 | |
|     }
 | |
|     List_iterator_fast<TABLE_LIST> it(param.outer);
 | |
|     TABLE_LIST *t;
 | |
|     while ((t= it++))
 | |
|     {
 | |
|       table_pos *outer_tab= tab + t->ora_join_table_no;
 | |
|       outer_tab->inner_side.push_back(inner_tab);
 | |
|       inner_tab->outer_side.push_back(outer_tab);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     // Permanent list can be used in AND
 | |
|     Query_arena_stmt on_stmt_arena(thd);
 | |
|     inner_tab->on_conds.push_back(cond);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|     Put the table @t into "LEFT JOIN syntax order" list after table @end.
 | |
| 
 | |
|   @detail
 | |
|     @t must not be already in that list.
 | |
| */
 | |
| 
 | |
| static void insert_element_after(table_pos *end, table_pos *t,
 | |
|                                  uint * const processed)
 | |
| {
 | |
|   DBUG_ASSERT(t->next == NULL);
 | |
|   DBUG_ASSERT(t->prev == NULL);
 | |
|   if (end)
 | |
|   {
 | |
|     if ((t->next= end->next))
 | |
|       end->next->prev= t;
 | |
|     end->next= t;
 | |
|     t->prev= end;
 | |
|   }
 | |
|   t->processed= TRUE;
 | |
|   (*processed)++;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool process_outer_relations(THD* thd,
 | |
|                                     table_pos *tab,
 | |
|                                     table_pos *first,
 | |
|                                     uint * const processed,
 | |
|                                     uint n_tables);
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|   Check presence of directional cycles (starting from "tab" with beginning
 | |
|   of check in "beginning") in case we have non-directional cycle
 | |
| 
 | |
|   @bdetail
 | |
|     Recusively check if table "beginning" is reachable from table "tab" through
 | |
|     the table_pos::inner_side pointers.
 | |
| */
 | |
| 
 | |
| static bool check_directed_cycle(THD* thd, table_pos *tab,
 | |
|                                  table_pos* beginning, uint lvl, uint max)
 | |
| {
 | |
|   List_iterator_fast<table_pos> it(tab->inner_side);
 | |
|   table_pos *t;
 | |
|   uchar buff[STACK_BUFF_ALLOC]; // Max argument in function
 | |
|   if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
 | |
|     return(TRUE);// Fatal error flag is set!
 | |
| 
 | |
|   if ((++lvl) >= max)
 | |
|   {
 | |
|     /*
 | |
|       We checked tables more times than tables we have => we have other cycle
 | |
|       reachable from "beginning"
 | |
| 
 | |
|       TODO: try to make such test
 | |
|       The loop of interest would like this:
 | |
| 
 | |
|        t1->t2->t3->t4->-+
 | |
|                ^        |
 | |
|                |        |
 | |
|                +--------+
 | |
|       However for such graph we would first call
 | |
|       check_directed_cycle(tab=t3,beginning=t3) and detect the loop.
 | |
|       We won't get to the point where we we would call
 | |
|       check_directed_cycle(tab=t3,beginning=t1)
 | |
|     */
 | |
|     return FALSE;
 | |
|   }
 | |
|   while ((t= it++))
 | |
|   {
 | |
|     if (t == beginning)
 | |
|       return true;
 | |
|     if (check_directed_cycle(thd, t, beginning, lvl, max))
 | |
|       return TRUE;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|     Table @tab has been added to the "LEFT JOIN syntax ordering". Add its
 | |
|     inner-side neighbors first, then add all their connections.
 | |
| 
 | |
|   @param  tab        Table for which to process
 | |
|   param   processed  Counter of processed tables.
 | |
|   @param  n_tables   Total number of tables in the join
 | |
| 
 | |
|   @return
 | |
|     FALSE  OK
 | |
|     TRUE   Error, found a circular loop in outer join relationships.
 | |
| */
 | |
| 
 | |
| static bool process_inner_relations(THD* thd,
 | |
|                                     table_pos *tab,
 | |
|                                     uint *const processed,
 | |
|                                     uint n_tables)
 | |
| {
 | |
|   if (tab->inner_side.elements > 0)
 | |
|   {
 | |
|     List_iterator_fast<table_pos> it(tab->inner_side);
 | |
|     table_pos *t;
 | |
| 
 | |
|     /* First, add all inner_side neighbors */
 | |
|     while ((t= it++))
 | |
|     {
 | |
|       if (t->processed)
 | |
|       {
 | |
|         /*
 | |
|           it is case of "non-cyclic" loop (or just processed already
 | |
|           branch)
 | |
| 
 | |
|              tab->t
 | |
|                   ^
 | |
|                   |
 | |
|               t1--+
 | |
|           (it would be t1 then t then tab and again probe t (from tab))
 | |
| 
 | |
|           Check if it is also directional loop:
 | |
|         */
 | |
|         if (check_directed_cycle(thd, t, t, 0, n_tables))
 | |
|         {
 | |
|           /*
 | |
|              Found a circular dependency:
 | |
| 
 | |
|               t1->tab -> t -+
 | |
|                    ^        |
 | |
|                    |        |
 | |
|                    +--....--+
 | |
|           */
 | |
|           my_error(ER_INVALID_USE_OF_ORA_JOIN_CYCLE, MYF(0));
 | |
|           return TRUE;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|         insert_element_after(tab, t, processed);
 | |
|     }
 | |
| 
 | |
|     /* Second, process the connections of each neighbor */
 | |
|     it.rewind();
 | |
|     while ((t= it++))
 | |
|     {
 | |
|       if (!t->outer_processed)
 | |
|       {
 | |
|         if (process_outer_relations(thd, t, tab, processed, n_tables))
 | |
|           return TRUE;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief
 | |
|     Insert @tab into the "LEFT JOIN syntax ordering" list between @first and
 | |
|     @last.
 | |
| */
 | |
| 
 | |
| static
 | |
| void insert_element_between(THD *thd, table_pos *tab,
 | |
|                             table_pos *first, table_pos *last,
 | |
|                             uint *const processed)
 | |
| {
 | |
|   table_pos *curr= last;
 | |
| 
 | |
|   DBUG_ASSERT(first != last);
 | |
|   // find place to insert
 | |
|   while (curr->prev != first && tab->order > curr->prev->order &&
 | |
|          !curr->is_outer_of(curr->prev))
 | |
|     curr= curr->prev;
 | |
| 
 | |
|   insert_element_after(curr->prev, tab, processed);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief
 | |
|     Table @tab has been added to the "LEFT JOIN syntax ordering". Add its
 | |
|     outer-side neighbors and all their connections.
 | |
| 
 | |
|   @param tab    Examine tab->outer_side and their connection.
 | |
|   @param first  All outer-side connections must be added after "first" and
 | |
|                 before the "tab".
 | |
| 
 | |
|   @detail
 | |
|     Outer-side neighbors need to be added *before* the table tab.
 | |
| */
 | |
| 
 | |
| static bool process_outer_relations(THD* thd,
 | |
|                                     table_pos *tab,
 | |
|                                     table_pos *first,
 | |
|                                     uint * const processed,
 | |
|                                     uint n_tables)
 | |
| {
 | |
|   uchar buff[STACK_BUFF_ALLOC]; // Max argument in function
 | |
|   if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
 | |
|     return(TRUE);// Fatal error flag is set!
 | |
| 
 | |
|   tab->outer_processed= TRUE;
 | |
|   if (tab->outer_side.elements)
 | |
|   {
 | |
|     List_iterator_fast<table_pos> it(tab->outer_side);
 | |
|     table_pos *t;
 | |
|     while((t= it++))
 | |
|     {
 | |
|       if (!t->processed)
 | |
|       {
 | |
|         /*
 | |
|           This (t3 in the example) table serves as inner table for several
 | |
|           others.
 | |
| 
 | |
|           For example we have such dependencies (outer to the right and inner
 | |
|           to the left):
 | |
|             SELECT *
 | |
|             FROM t1,t2,t3,t4
 | |
|             WHERE t1.a=t2.a(+) AND t2.a=t3.a(+) AND
 | |
|                   t4.a=t3.a(+);
 | |
| 
 | |
|           t1->t2-+
 | |
|                  |
 | |
|                  +==>t3
 | |
|                  |
 | |
|           t4-----+
 | |
| 
 | |
|           So we have built following list of left joins already (we
 | |
|           started from the first independent table we have found - t1
 | |
|           and went by inner_side relation till table t3):
 | |
| 
 | |
|           first
 | |
|           |
 | |
|           *->t1 => t2 => t3
 | |
|                          ^
 | |
|                          |
 | |
|             t->t4       tab
 | |
| 
 | |
|           Now we go by list of unprocessed outer relation of t3 and put
 | |
|           them  before t3.
 | |
|           So we have to put t4 between t1 and t2 or between t2 and t3
 | |
|           (depends on its original position because we
 | |
|           are trying to keep original order where it is possible).
 | |
| 
 | |
|              t1 => t2 => t4 => t3
 | |
| 
 | |
|             SELECT *
 | |
|             FROM t1 left join t2 on (t1.a=t2.a),
 | |
|                  t4
 | |
|                  left join t3 on (t2.a=t3.a and t4.a=t3.a)
 | |
| 
 | |
|           We can also put it before t1, but as far as we have found t1 first
 | |
|           it have definitely early position in the original list of tables
 | |
|           than t4.
 | |
|         */
 | |
|         insert_element_between(thd, t, first, tab, processed);
 | |
|         if (process_inner_relations(thd, t, processed, n_tables))
 | |
|           return TRUE;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return process_inner_relations(thd, tab, processed, n_tables);
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
| static void dbug_trace_table_pos(table_pos *t)
 | |
| {
 | |
|   DBUG_ENTER("dbug_trace_table_pos");
 | |
|   DBUG_PRINT("table_pos", ("Table: %s", t->table->alias.str));
 | |
|   List_iterator_fast iti(t->on_conds);
 | |
|   Item *item;
 | |
|   while ((item= iti++))
 | |
|   {
 | |
|     StringBuffer<STRING_BUFFER_USUAL_SIZE> expr;
 | |
|     item->print(&expr, QT_ORDINARY);
 | |
|     DBUG_PRINT("INFO", ("  On_conds: %s", expr.c_ptr()));
 | |
|   }
 | |
|   List_iterator_fast itot(t->outer_side);
 | |
|   table_pos *tbl;
 | |
|   while ((tbl= itot++))
 | |
|     DBUG_PRINT("INFO", ("  Outer side: %s", tbl->table->alias.str));
 | |
| 
 | |
|   List_iterator_fast itit(t->inner_side);
 | |
|   while ((tbl= itit++))
 | |
|     DBUG_PRINT("INFO", ("  Inner side: %s", tbl->table->alias.str));
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void dbug_trace_table_entry(const char *prefix,
 | |
|                                    const char *legend, TABLE_LIST *t)
 | |
| {
 | |
|   if (t)
 | |
|   {
 | |
|     StringBuffer<STRING_BUFFER_USUAL_SIZE> expr;
 | |
|     if (t->on_expr)
 | |
|       t->on_expr->print(&expr, QT_ORDINARY);
 | |
| 
 | |
|     DBUG_PRINT(prefix, ("%s Table: '%s' %p  outer: %s  on_expr: %s", legend,
 | |
|                         (t->alias.str ? t->alias.str : ""), t,
 | |
|                         (t->outer_join ? "YES" : "no"),
 | |
|                         (t->on_expr ? expr.c_ptr() : "NULL")));
 | |
|   }
 | |
|   else
 | |
|     DBUG_PRINT(prefix, ("%s Table: NULL", legend));
 | |
| }
 | |
| 
 | |
| 
 | |
| static void dbug_trace_table_list(TABLE_LIST *t)
 | |
| {
 | |
|   DBUG_ENTER("dbug_trace_table_list");
 | |
|   dbug_trace_table_entry("TABLE_LIST", "---", t);
 | |
|   dbug_trace_table_entry("INFO", "Embedding", t->embedding);
 | |
|   if (t->join_list)
 | |
|   {
 | |
|     DBUG_PRINT("INFO", ("Join list: %p elements %d",
 | |
|                         t->join_list, t->join_list->elements));
 | |
|     List_iterator_fast<TABLE_LIST> it(*t->join_list);
 | |
|     TABLE_LIST *tbl;
 | |
|     while ((tbl= it++))
 | |
|     {
 | |
|       dbug_trace_table_entry("INFO", "join_list", tbl);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|     DBUG_PRINT("INFO", ("Join list: NULL"));
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|     Init array of graph vertexes
 | |
| 
 | |
|   @return
 | |
|     TRUE   Error
 | |
|     FALSE  Ok, conversion is either done or not needed.
 | |
| */
 | |
| 
 | |
| static bool init_tables_array(TABLE_LIST *tables,
 | |
|                               uint n_tables,
 | |
|                               table_pos *tab)
 | |
| {
 | |
|   table_pos *t= tab;
 | |
|   TABLE_LIST *table= tables;
 | |
|   uint i= 0;
 | |
|   DBUG_ENTER("init_tables_array");
 | |
| 
 | |
|   /*
 | |
|     Create a graph vertex for each table.
 | |
|     Also note the original order of tables in the FROM clause.
 | |
|   */
 | |
|   for (;table; i++, t++, table= table->next_local)
 | |
|   {
 | |
|     DBUG_ASSERT(i < n_tables);
 | |
|     if (table->outer_join || table->nested_join || table->natural_join ||
 | |
|         table->embedding || table->straight)
 | |
|     {
 | |
|       // mixed with other JOIN operations
 | |
|       my_error(ER_INVALID_USE_OF_ORA_JOIN_MIX, MYF(0));
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     t->next= t->prev=  NULL;
 | |
|     t->inner_side.empty();
 | |
|     t->outer_side.empty();
 | |
|     t->on_conds.empty();
 | |
|     t->table= table;
 | |
|     // psergey-todo: can't we keep ora_join_table_no in table_pos?
 | |
|     table->ora_join_table_no= t->order= i;
 | |
|     t->processed= t->outer_processed= FALSE;
 | |
|   }
 | |
|   DBUG_ASSERT(i == n_tables);
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief
 | |
|     Convert Oracle's outer join (+) operators into regular outer join
 | |
|     structures
 | |
| 
 | |
|   @param conds             INOUT  The WHERE condition
 | |
|   @param select_join_list  INOUT  Top-level join list
 | |
| 
 | |
|   @return
 | |
|     TRUE   Error
 | |
|     FALSE  Ok, conversion is either done or not needed.
 | |
| */
 | |
| 
 | |
| bool setup_oracle_join(THD *thd, Item **conds,
 | |
|                        TABLE_LIST *tables,
 | |
|                        SQL_I_List<TABLE_LIST> &select_table_list,
 | |
|                        List<TABLE_LIST> *select_join_list,
 | |
|                        List<Item> *all_fields)
 | |
| {
 | |
|   DBUG_ENTER("setup_oracle_join");
 | |
|   uint n_tables= select_table_list.elements;
 | |
|   uint i= 0;
 | |
| 
 | |
|   if (!(*conds)->with_ora_join() || n_tables == 0)
 | |
|     DBUG_RETURN(FALSE); // no oracle joins
 | |
| 
 | |
|   table_pos *tab= (table_pos *) new(thd->mem_root) table_pos[n_tables];
 | |
|   if (init_tables_array(tables, n_tables, tab))
 | |
|     DBUG_RETURN(TRUE); // mixed with other joins
 | |
| 
 | |
| 
 | |
|   /*
 | |
|     Process the WHERE clause:
 | |
|      - Find Outer Join conditions
 | |
|      - Create graph edges from them
 | |
|      - Remove them from the WHERE clause
 | |
|   */
 | |
|   if (is_cond_and(*conds))
 | |
|   {
 | |
|     Item_cond_and *and_item= (Item_cond_and *)(*conds);
 | |
|     Item *item;
 | |
|     List_iterator<Item> it(*and_item->argument_list());
 | |
|     while ((item= it++))
 | |
|     {
 | |
|       if (item->with_ora_join())
 | |
|       {
 | |
|         if (ora_join_process_expression(thd, item, tab, n_tables))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         item->walk(&Item::remove_ora_join_processor, 0, 0);
 | |
|         it.remove(); // will be moved to ON
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if ((*conds)->with_ora_join())
 | |
|     {
 | |
|      if (ora_join_process_expression(thd, (*conds), tab, n_tables))
 | |
|         DBUG_RETURN(TRUE);
 | |
|       (*conds)->walk(&Item::remove_ora_join_processor, 0, 0);
 | |
|       *conds= NULL; // will be moved to ON
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Check for inner tables that don't have outer tables;
 | |
|     Prepare for producing the ordering.
 | |
|   */
 | |
|   List<Item> return_to_where;
 | |
|   return_to_where.empty();
 | |
|   for (i= 0; i < n_tables; i++)
 | |
|   {
 | |
|     if (tab[i].on_conds.elements > 0 &&
 | |
|         tab[i].outer_side.elements == 0)
 | |
|     {
 | |
|       /*
 | |
|         This table is marked as INNER but it has no matching OUTER tables. This
 | |
|         can happen for queries like:
 | |
| 
 | |
|           select * from t1,t2 where t2.a(+)=123;
 | |
| 
 | |
|         Issue a warning and move ON condition predicates back to the WHERE.
 | |
|       */
 | |
|       List_iterator_fast it(tab[i].on_conds);
 | |
|       StringBuffer<STRING_BUFFER_USUAL_SIZE> expr;
 | |
|       Item *item;
 | |
|       while ((item= it++))
 | |
|       {
 | |
|         expr.set("", 0, system_charset_info);
 | |
|         item->print(&expr, QT_ORDINARY);
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                             WARN_ORA_JOIN_IGNORED,
 | |
|                             ER_THD(thd, WARN_ORA_JOIN_IGNORED),
 | |
|                             expr.c_ptr());
 | |
| 
 | |
|         // The item will be moved into the WHERE clause
 | |
|         item->walk(&Item::remove_ora_join_processor, 0, 0);
 | |
|       }
 | |
|       return_to_where.append(&tab[i].on_conds);
 | |
|       tab[i].on_conds.empty();
 | |
|     }
 | |
|     /*
 | |
|       Sort the outgoing edges in reverse order (those that should come
 | |
|       first are the last). This is because we will do this:
 | |
| 
 | |
|         for each T in tab[i].inner_side
 | |
|           insert_element_after(tab[i], T);
 | |
| 
 | |
|       after which the elements will be in the right order.
 | |
|     */
 | |
|     if (tab[i].inner_side.elements > 1)
 | |
|        bubble_sort<table_pos>(&tab[i].inner_side, table_pos_sort, NULL);
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
|     dbug_trace_table_pos(tab + i);
 | |
|     dbug_trace_table_list(tab[i].table);
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Order the tables according to the "LEFT JOIN syntax order".
 | |
|   */
 | |
|   table_pos *list= NULL;
 | |
|   table_pos *end= NULL;
 | |
|   uint processed= 0;
 | |
|   i= 0;
 | |
|   do
 | |
|   {
 | |
|     // Find the first independent table
 | |
|     for(;
 | |
|         i < n_tables && (tab[i].processed || tab[i].outer_side.elements != 0);
 | |
|         i++);
 | |
|     if (i >= n_tables)
 | |
|       break;
 | |
| 
 | |
|     // Set (list, end) to point to the list we've collected so far
 | |
|     if (list == NULL)
 | |
|       list= tab + i;
 | |
|     else
 | |
|     {
 | |
|       if (end == NULL)
 | |
|         end= list;
 | |
|       while(end->next) end= end->next; // go to the new end
 | |
|     }
 | |
| 
 | |
|     // Process "sub-graph" with this independent is top of one of branches
 | |
|     insert_element_after(end, tab + i, &processed);
 | |
|     process_inner_relations(thd, tab + i, &processed, n_tables);
 | |
|   } while (i < n_tables);
 | |
| 
 | |
|   if (processed < n_tables)
 | |
|   {
 | |
|     /*
 | |
|       Some tables are not processed but they all have incoming edges.
 | |
|       This can only happen if there are circular dependencies:
 | |
| 
 | |
|        t1 -> t2 -> t3 -+
 | |
|         ^              |
 | |
|         |              |
 | |
|         +--------------+
 | |
|     */
 | |
|     my_error(ER_INVALID_USE_OF_ORA_JOIN_CYCLE, MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
|   for (table_pos *t= list; t != NULL; t= t->next)
 | |
|     dbug_trace_table_pos(t);
 | |
| #endif
 | |
| 
 | |
| 
 | |
|   /*
 | |
|     Now we build new permanent list of table according to our new order
 | |
| 
 | |
|     table1 [left inner] join table2 ... [left inner] join tableN
 | |
| 
 | |
|     which parses in:
 | |
| 
 | |
|      top_join_list of SELECT_LEX
 | |
|        |
 | |
|        nest_tableN-1  --nested_join-> NESTED_JOIN_N-1
 | |
|        join_list of NESTED_JOIN_N-1
 | |
|         /\
 | |
|   tableN  nest_tableN-2
 | |
|          ...
 | |
|            join_list of of NESTED_JOIN_2
 | |
|             /\
 | |
|       table3  nest_table1  --nested_join-> NESTED_JOIN_1
 | |
|               join_list of NESTED_JOIN1
 | |
|                /\
 | |
|          table2  table1
 | |
| 
 | |
|   */
 | |
|   TABLE_LIST *new_from= list->table;
 | |
|   if (n_tables > 1) // nothing to do with only one table
 | |
|   {
 | |
|     // changes are permanent
 | |
|     Query_arena_stmt on_stmt_arena(thd);
 | |
|     TABLE_LIST *prev_table= list->table;
 | |
|     TABLE_LIST *nest_table_lists;
 | |
|     NESTED_JOIN *nested_joins;
 | |
|     if (!(nest_table_lists=
 | |
|           (TABLE_LIST*)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) *
 | |
|                                      (n_tables - 1) +
 | |
|                                    ALIGN_SIZE(sizeof(NESTED_JOIN)) *
 | |
|                                      (n_tables - 1))))
 | |
|     {
 | |
|       DBUG_RETURN(TRUE); // EOM
 | |
|     }
 | |
|     nested_joins= (NESTED_JOIN *)(((char*)nest_table_lists) +
 | |
|                                   ALIGN_SIZE(sizeof(TABLE_LIST)) *
 | |
|                                   (n_tables - 1));
 | |
|     for (uint i= 0; i < n_tables - 1; i++)
 | |
|     {
 | |
|       nest_table_lists[i].nested_join= nested_joins + i;
 | |
|     }
 | |
|     nested_joins[0].join_list.empty();
 | |
|     nested_joins[0].join_list.push_front(list->table);
 | |
|     DBUG_ASSERT(list->table->embedding == NULL);
 | |
|     list->table->embedding= nest_table_lists;
 | |
|     list->table->join_list= &nested_joins->join_list;
 | |
|     DBUG_ASSERT(list->table->outer_join == 0);
 | |
|     uint i;
 | |
|     table_pos *curr;
 | |
|     for (i=0, curr= list->next; curr; i++, curr= curr->next)
 | |
|     {
 | |
|       DBUG_ASSERT(i <= n_tables - 2);
 | |
|       TABLE_LIST *next_embedding= ((i < n_tables - 2) ?
 | |
|                                     nest_table_lists + (i+1) :
 | |
|                                     NULL);
 | |
|       // join type
 | |
|       DBUG_ASSERT(curr->table->outer_join == 0);
 | |
|       DBUG_ASSERT(curr->table->on_expr == 0);
 | |
|       if (curr->outer_side.elements)
 | |
|       {
 | |
|         DBUG_ASSERT(curr->on_conds.elements > 0);
 | |
|         curr->table->outer_join|=JOIN_TYPE_LEFT;
 | |
|         // update maybe_null which was previously set in setup_table_map()
 | |
|         if (curr->table->table)
 | |
|           curr->table->table->maybe_null= JOIN_TYPE_LEFT;
 | |
|         if (curr->on_conds.elements == 1)
 | |
|         {
 | |
|           curr->table->on_expr= curr->on_conds.head();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           Item *item= new(thd->mem_root) Item_cond_and(thd, curr->on_conds);
 | |
|           if (!item)
 | |
|             DBUG_RETURN(TRUE);
 | |
|           item->top_level_item();
 | |
|           curr->table->on_expr= item;
 | |
|           /* setup_on_expr() will call fix_fields() for on_expr */
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         DBUG_ASSERT(curr->on_conds.elements == 0);
 | |
|       }
 | |
| 
 | |
|       // add real table
 | |
|       prev_table->next_local= curr->table;
 | |
|       nested_joins[i].join_list.push_front(curr->table);
 | |
|       DBUG_ASSERT(curr->table->embedding == NULL);
 | |
|       curr->table->embedding= nest_table_lists + i;
 | |
|       curr->table->join_list= &nested_joins[i].join_list;
 | |
|       // prepare fake table
 | |
|       nest_table_lists[i].alias= {STRING_WITH_LEN("(nest_last_join)")};
 | |
|       nest_table_lists[i].embedding= next_embedding;
 | |
| 
 | |
|       if (next_embedding)
 | |
|       {
 | |
|         nested_joins[i+1].join_list.empty();
 | |
|         nested_joins[i+1].join_list.push_front(nest_table_lists + i);
 | |
|         nest_table_lists[i].join_list= &nested_joins[i + 1].join_list;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         DBUG_ASSERT(i == n_tables - 2);
 | |
|         // all tables should be there because query was without JOIN
 | |
|         // operators except oracle ones
 | |
|         DBUG_ASSERT(select_join_list->elements == n_tables);
 | |
|         select_join_list->empty();
 | |
|         select_join_list->push_front(nest_table_lists + i);
 | |
|         nest_table_lists[i].join_list= select_join_list;
 | |
|       }
 | |
| 
 | |
|       prev_table= curr->table;
 | |
|     }
 | |
|     prev_table->next_local= NULL;
 | |
|     select_table_list.first= new_from;
 | |
|     select_table_list.next= &prev_table->next_local;
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("INFO", ("new FROM clause: %p", new_from));
 | |
| #ifndef DBUG_OFF
 | |
|   for (TABLE_LIST *t= new_from; t; t= t->next_local)
 | |
|   {
 | |
|     dbug_trace_table_list(t);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (add_conditions_to_where(thd, conds, std::move(return_to_where)))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   // Refresh nullability of already fixed parts:
 | |
| 
 | |
|   // WHERE
 | |
|   if (conds[0])
 | |
|   {
 | |
|     conds[0]->update_used_tables();
 | |
|     conds[0]->walk(&Item::add_maybe_null_after_ora_join_processor, 0, 0);
 | |
|   }
 | |
|   // SELECT list and hidden fields
 | |
|   if (all_fields)
 | |
|   {
 | |
|     List_iterator<Item> it(*all_fields);
 | |
|     Item *item;
 | |
|     while((item= it++))
 | |
|     {
 | |
|       item->update_used_tables();
 | |
|       item->walk(&Item::add_maybe_null_after_ora_join_processor, 0, 0);
 | |
|     }
 | |
|   }
 | |
|   // parts of WHERE moved to ON (original ONs will be fixed later)
 | |
|   for (i= 0; i < n_tables; i++)
 | |
|   {
 | |
|     // we have to count becaust this lists are included in other lists
 | |
|     List_iterator<Item> it(*all_fields);
 | |
|     Item *item;
 | |
|     for (uint j= 0; j < tab[i].on_conds.elements && (item= it++); j++)
 | |
|     {
 | |
|       item->update_used_tables();
 | |
|       item->walk(&Item::add_maybe_null_after_ora_join_processor, 0, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   @brief
 | |
|     Add conditions from return_to_where into *conds.
 | |
|     Then, normalize *conds: it can be an Item_cond_and with one or zero
 | |
|     children.
 | |
| 
 | |
|   @return
 | |
|     false  OK
 | |
|     true   Fatal error
 | |
| */
 | |
| 
 | |
| static bool add_conditions_to_where(THD *thd, Item **conds,
 | |
|                                     List<Item> &&return_to_where)
 | |
| {
 | |
|   // Make changes on statement's mem_root
 | |
|   Query_arena_stmt on_stmt_arena(thd);
 | |
|   uint number_of_cond_parts= return_to_where.elements;
 | |
|   if ((*conds) != NULL)
 | |
|   {
 | |
|     if (is_cond_and(*conds))
 | |
|     {
 | |
|       uint elements= ((Item_cond_and *)(*conds))->argument_list()->elements;
 | |
|       switch (elements)
 | |
|       {
 | |
|         case 0:
 | |
|           (*conds)= NULL;
 | |
|           break;
 | |
|         case 1:
 | |
|           (*conds)= ((Item_cond_and *)(*conds))->argument_list()->head();
 | |
|           number_of_cond_parts++;
 | |
|           break;
 | |
|         default:
 | |
|           number_of_cond_parts+= elements;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       number_of_cond_parts++;
 | |
|   }
 | |
| 
 | |
|   if (number_of_cond_parts == 0)
 | |
|   {
 | |
|     /* Nothing is left in the WHERE */
 | |
|     DBUG_ASSERT((*conds) == NULL);
 | |
|   }
 | |
|   else if (number_of_cond_parts == 1)
 | |
|   {
 | |
|     if ((*conds) == NULL)
 | |
|     {
 | |
|       DBUG_ASSERT(return_to_where.elements == 1);
 | |
|       (*conds)= return_to_where.head();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       // There is one remaining condition, it's in *conds.
 | |
|       DBUG_ASSERT(return_to_where.elements == 0);
 | |
|       DBUG_ASSERT((*conds) != NULL);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if ((*conds) == NULL || !is_cond_and(*conds))
 | |
|     {
 | |
|       if (*conds)
 | |
|         return_to_where.push_back(*conds);
 | |
|     }
 | |
|     else
 | |
|       return_to_where.append(((Item_cond_and *)(*conds))->argument_list());
 | |
| 
 | |
|     if (!((*conds)= new(thd->mem_root) Item_cond_and(thd, return_to_where)))
 | |
|       return true;
 | |
|     (*conds)->top_level_item();
 | |
|     if ((*conds)->fix_fields(thd, conds))
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 |