mirror of
https://github.com/MariaDB/server.git
synced 2025-07-27 18:02:13 +03:00
Merge mysql.com:/home/psergey/mysql-5.0-bug14026-r2
into mysql.com:/home/psergey/mysql-5.0-oct03-push
This commit is contained in:
@ -98,6 +98,12 @@ static COND* substitute_for_best_equal_field(COND *cond,
|
||||
void *table_join_idx);
|
||||
static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list,
|
||||
COND *conds, bool top);
|
||||
static bool check_interleaving_with_nj(JOIN_TAB *last, JOIN_TAB *next);
|
||||
static void restore_prev_nj_state(JOIN_TAB *last);
|
||||
static void reset_nj_counters(List<TABLE_LIST> *join_list);
|
||||
static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
|
||||
uint first_unused);
|
||||
|
||||
static COND *optimize_cond(JOIN *join, COND *conds,
|
||||
List<TABLE_LIST> *join_list,
|
||||
Item::cond_result *cond_value);
|
||||
@ -520,12 +526,14 @@ bool JOIN::test_in_subselect(Item **where)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
global select optimisation.
|
||||
return 0 - success
|
||||
1 - error
|
||||
error code saved in field 'error'
|
||||
*/
|
||||
|
||||
int
|
||||
JOIN::optimize()
|
||||
{
|
||||
@ -588,6 +596,7 @@ JOIN::optimize()
|
||||
|
||||
/* Convert all outer joins to inner joins if possible */
|
||||
conds= simplify_joins(this, join_list, conds, TRUE);
|
||||
build_bitmap_for_nested_joins(join_list, 0);
|
||||
|
||||
sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
|
||||
|
||||
@ -700,7 +709,8 @@ JOIN::optimize()
|
||||
DBUG_PRINT("error",("Error: make_select() failed"));
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
|
||||
reset_nj_counters(join_list);
|
||||
make_outerjoin_info(this);
|
||||
|
||||
/*
|
||||
@ -1968,14 +1978,19 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds,
|
||||
continue;
|
||||
}
|
||||
outer_join|= table->map;
|
||||
s->embedding_map= 0;
|
||||
for (;embedding; embedding= embedding->embedding)
|
||||
s->embedding_map|= embedding->nested_join->nj_map;
|
||||
continue;
|
||||
}
|
||||
if (embedding)
|
||||
{
|
||||
/* s belongs to a nested join, maybe to several embedded joins */
|
||||
s->embedding_map= 0;
|
||||
do
|
||||
{
|
||||
NESTED_JOIN *nested_join= embedding->nested_join;
|
||||
s->embedding_map|=nested_join->nj_map;
|
||||
s->dependent|= embedding->dep_tables;
|
||||
embedding= embedding->embedding;
|
||||
outer_join|= nested_join->used_tables;
|
||||
@ -3549,6 +3564,8 @@ choose_plan(JOIN *join, table_map join_tables)
|
||||
bool straight_join= join->select_options & SELECT_STRAIGHT_JOIN;
|
||||
DBUG_ENTER("choose_plan");
|
||||
|
||||
join->cur_embedding_map= 0;
|
||||
reset_nj_counters(join->join_list);
|
||||
/*
|
||||
if (SELECT_STRAIGHT_JOIN option is set)
|
||||
reorder tables so dependent tables come after tables they depend
|
||||
@ -4029,7 +4046,9 @@ best_extension_by_limited_search(JOIN *join,
|
||||
for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
|
||||
{
|
||||
table_map real_table_bit= s->table->map;
|
||||
if ((remaining_tables & real_table_bit) && !(remaining_tables & s->dependent))
|
||||
if ((remaining_tables & real_table_bit) &&
|
||||
!(remaining_tables & s->dependent) &&
|
||||
(!idx || !check_interleaving_with_nj(join->positions[idx-1].table, s)))
|
||||
{
|
||||
double current_record_count, current_read_time;
|
||||
|
||||
@ -4045,6 +4064,7 @@ best_extension_by_limited_search(JOIN *join,
|
||||
{
|
||||
DBUG_EXECUTE("opt", print_plan(join, read_time, record_count, idx,
|
||||
"prune_by_cost"););
|
||||
restore_prev_nj_state(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -4073,6 +4093,7 @@ best_extension_by_limited_search(JOIN *join,
|
||||
{
|
||||
DBUG_EXECUTE("opt", print_plan(join, read_time, record_count, idx,
|
||||
"pruned_by_heuristic"););
|
||||
restore_prev_nj_state(s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -4107,9 +4128,11 @@ best_extension_by_limited_search(JOIN *join,
|
||||
sizeof(POSITION) * (idx + 1));
|
||||
join->best_read= current_read_time - 0.001;
|
||||
}
|
||||
DBUG_EXECUTE("opt",
|
||||
print_plan(join, current_read_time, current_record_count, idx, "full_plan"););
|
||||
DBUG_EXECUTE("opt", print_plan(join, current_read_time,
|
||||
current_record_count, idx,
|
||||
"full_plan"););
|
||||
}
|
||||
restore_prev_nj_state(s);
|
||||
}
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
@ -4154,7 +4177,8 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
|
||||
for (JOIN_TAB **pos=join->best_ref+idx ; (s=*pos) ; pos++)
|
||||
{
|
||||
table_map real_table_bit=s->table->map;
|
||||
if ((rest_tables & real_table_bit) && !(rest_tables & s->dependent))
|
||||
if ((rest_tables & real_table_bit) && !(rest_tables & s->dependent) &&
|
||||
(!idx|| !check_interleaving_with_nj(join->positions[idx-1].table, s)))
|
||||
{
|
||||
double best,best_time,records;
|
||||
best=best_time=records=DBL_MAX;
|
||||
@ -4492,10 +4516,10 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
|
||||
join->unit->select_limit_cnt >= records)
|
||||
join->sort_by_table= (TABLE*) 1; // Must use temporary table
|
||||
|
||||
/*
|
||||
/*
|
||||
Go to the next level only if there hasn't been a better key on
|
||||
this level! This will cut down the search for a lot simple cases!
|
||||
*/
|
||||
*/
|
||||
double current_record_count=record_count*records;
|
||||
double current_read_time=read_time+best;
|
||||
if (best_record_count > current_record_count ||
|
||||
@ -4516,6 +4540,7 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
|
||||
return;
|
||||
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
|
||||
}
|
||||
restore_prev_nj_state(s);
|
||||
if (join->select_options & SELECT_STRAIGHT_JOIN)
|
||||
break; // Don't test all combinations
|
||||
}
|
||||
@ -5101,7 +5126,7 @@ add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab)
|
||||
This function can be called only after the execution plan
|
||||
has been chosen.
|
||||
*/
|
||||
|
||||
|
||||
static void
|
||||
make_outerjoin_info(JOIN *join)
|
||||
{
|
||||
@ -7262,11 +7287,11 @@ propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list,
|
||||
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 requite extra recursive calls.
|
||||
elimination of outer joins does not require extra recursive calls.
|
||||
|
||||
EXAMPLES
|
||||
Here is an example of a join query with invalid cross references:
|
||||
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN ON t3.b=t1.b
|
||||
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b
|
||||
|
||||
RETURN VALUE
|
||||
The new condition, if success
|
||||
@ -7423,7 +7448,257 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
|
||||
}
|
||||
DBUG_RETURN(conds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Assign each nested join structure a bit in nested_join_map
|
||||
|
||||
SYNOPSIS
|
||||
build_bitmap_for_nested_joins()
|
||||
join Join being processed
|
||||
join_list List of tables
|
||||
first_unused Number of first unused bit in nested_join_map before the
|
||||
call
|
||||
|
||||
DESCRIPTION
|
||||
Assign each nested join structure (except "confluent" ones - those that
|
||||
embed only one element) a bit in nested_join_map.
|
||||
|
||||
NOTE
|
||||
This function is called after simplify_joins(), when there are no
|
||||
redundant nested joins, #non_confluent_nested_joins <= #tables_in_join so
|
||||
we will not run out of bits in nested_join_map.
|
||||
|
||||
RETURN
|
||||
First unused bit in nested_join_map after the call.
|
||||
*/
|
||||
|
||||
static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
|
||||
uint first_unused)
|
||||
{
|
||||
List_iterator<TABLE_LIST> li(*join_list);
|
||||
TABLE_LIST *table;
|
||||
DBUG_ENTER("build_bitmap_for_nested_joins");
|
||||
while ((table= li++))
|
||||
{
|
||||
NESTED_JOIN *nested_join;
|
||||
if ((nested_join= table->nested_join))
|
||||
{
|
||||
/*
|
||||
It is guaranteed by simplify_joins() function that a nested join
|
||||
that has only one child represents a single table VIEW (and the child
|
||||
is an underlying table). We don't assign bits to such nested join
|
||||
structures because
|
||||
1. it is redundant (a "sequence" of one table cannot be interleaved
|
||||
with anything)
|
||||
2. we could run out bits in nested_join_map otherwise.
|
||||
*/
|
||||
if (nested_join->join_list.elements != 1)
|
||||
{
|
||||
nested_join->nj_map= 1 << first_unused++;
|
||||
first_unused= build_bitmap_for_nested_joins(&nested_join->join_list,
|
||||
first_unused);
|
||||
}
|
||||
}
|
||||
}
|
||||
DBUG_RETURN(first_unused);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Set NESTED_JOIN::counter=0 in all nested joins in passed list
|
||||
|
||||
SYNOPSIS
|
||||
reset_nj_counters()
|
||||
join_list List of nested joins to process. It may also contain base
|
||||
tables which will be ignored.
|
||||
|
||||
DESCRIPTION
|
||||
Recursively set NESTED_JOIN::counter=0 for all nested joins contained in
|
||||
the passed join_list.
|
||||
*/
|
||||
|
||||
static void reset_nj_counters(List<TABLE_LIST> *join_list)
|
||||
{
|
||||
List_iterator<TABLE_LIST> li(*join_list);
|
||||
TABLE_LIST *table;
|
||||
DBUG_ENTER("reset_nj_counters");
|
||||
while ((table= li++))
|
||||
{
|
||||
NESTED_JOIN *nested_join;
|
||||
if ((nested_join= table->nested_join))
|
||||
{
|
||||
nested_join->counter= 0;
|
||||
reset_nj_counters(&nested_join->join_list);
|
||||
}
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Check interleaving with an inner tables of an outer join for extension table
|
||||
|
||||
SYNOPSIS
|
||||
check_interleaving_with_nj()
|
||||
join Join being processed
|
||||
last_tab Last table in current partial join order (this function is
|
||||
not called for empty partial join orders)
|
||||
next_tab Table we're going to extend the current partial join with
|
||||
|
||||
DESCRIPTION
|
||||
Check if table next_tab can be added to current partial join order, and
|
||||
if yes, record that it has been added.
|
||||
|
||||
The function assumes that both current partial join order and its
|
||||
extension with next_tab are valid wrt table dependencies.
|
||||
|
||||
IMPLEMENTATION
|
||||
LIMITATIONS ON JOIN ORDER
|
||||
The nested [outer] joins executioner algorithm imposes these limitations
|
||||
on join order:
|
||||
1. "Outer tables first" - any "outer" table must be before any
|
||||
corresponding "inner" table.
|
||||
2. "No interleaving" - tables inside a nested join must form a continuous
|
||||
sequence in join order (i.e. the sequence must not be interrupted by
|
||||
tables that are outside of this nested join).
|
||||
|
||||
#1 is checked elsewhere, this function checks #2 provided that #1 has
|
||||
been already checked.
|
||||
|
||||
WHY NEED NON-INTERLEAVING
|
||||
Consider an example:
|
||||
|
||||
select * from t0 join t1 left join (t2 join t3) on cond1
|
||||
|
||||
The join order "t1 t2 t0 t3" is invalid:
|
||||
|
||||
table t0 is outside of the nested join, so WHERE condition for t0 is
|
||||
attached directly to t0 (without triggers, and it may be used to access
|
||||
t0). Applying WHERE(t0) to (t2,t0,t3) record is invalid as we may miss
|
||||
combinations of (t1, t2, t3) that satisfy condition cond1, and produce a
|
||||
null-complemented (t1, t2.NULLs, t3.NULLs) row, which should not have
|
||||
been produced.
|
||||
|
||||
If table t0 is not between t2 and t3, the problem doesn't exist:
|
||||
* If t0 is located after (t2,t3), WHERE(t0) is applied after nested join
|
||||
processing has finished.
|
||||
* If t0 is located before (t2,t3), predicates like WHERE_cond(t0, t2) are
|
||||
wrapped into condition triggers, which takes care of correct nested
|
||||
join processing.
|
||||
|
||||
HOW IT IS IMPLEMENTED
|
||||
The limitations on join order can be rephrased as follows: for valid
|
||||
join order one must be able to:
|
||||
1. write down the used tables in the join order on one line.
|
||||
2. for each nested join, put one '(' and one ')' on the said line
|
||||
3. write "LEFT JOIN" and "ON (...)" where appropriate
|
||||
4. get a query equivalent to the query we're trying to execute.
|
||||
|
||||
Calls to check_interleaving_with_nj() are equivalent to writing the
|
||||
above described line from left to right.
|
||||
A single check_interleaving_with_nj(A,B) call is equivalent to writing
|
||||
table B and appropriate brackets on condition that table A and
|
||||
appropriate brackets is the last what was written. Graphically the
|
||||
transition is as follows:
|
||||
|
||||
+---- current position
|
||||
|
|
||||
... last_tab ))) | ( next_tab ) )..) | ...
|
||||
X Y Z |
|
||||
+- need to move to this
|
||||
position.
|
||||
|
||||
Notes about the position:
|
||||
The caller guarantees that there is no more then one X-bracket by
|
||||
checking "!(remaining_tables & s->dependent)" before calling this
|
||||
function. X-bracket may have a pair in Y-bracket.
|
||||
|
||||
When "writing" we store/update this auxilary info about the current
|
||||
position:
|
||||
1. join->cur_embedding_map - bitmap of pairs of brackets (aka nested
|
||||
joins) we've opened but didn't close.
|
||||
2. {each NESTED_JOIN structure not simplified away}->counter - number
|
||||
of this nested join's children that have already been added to to
|
||||
the partial join order.
|
||||
|
||||
RETURN
|
||||
FALSE Join order extended, nested joins info about current join order
|
||||
(see NOTE section) updated.
|
||||
TRUE Requested join order extension not allowed.
|
||||
*/
|
||||
|
||||
static bool check_interleaving_with_nj(JOIN_TAB *last_tab, JOIN_TAB *next_tab)
|
||||
{
|
||||
TABLE_LIST *next_emb= next_tab->table->pos_in_table_list->embedding;
|
||||
JOIN *join= last_tab->join;
|
||||
|
||||
if (join->cur_embedding_map & ~next_tab->embedding_map)
|
||||
{
|
||||
/*
|
||||
next_tab is outside of the "pair of brackets" we're currently in.
|
||||
Cannot add it.
|
||||
*/
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
Do update counters for "pairs of brackets" that we've left (marked as
|
||||
X,Y,Z in the above picture)
|
||||
*/
|
||||
for (;next_emb; next_emb= next_emb->embedding)
|
||||
{
|
||||
next_emb->nested_join->counter++;
|
||||
if (next_emb->nested_join->counter == 1)
|
||||
{
|
||||
/*
|
||||
next_emb is the first table inside a nested join we've "entered". In
|
||||
the picture above, we're looking at the 'X' bracket. Don't exit yet as
|
||||
X bracket might have Y pair bracket.
|
||||
*/
|
||||
join->cur_embedding_map |= next_emb->nested_join->nj_map;
|
||||
}
|
||||
|
||||
if (next_emb->nested_join->join_list.elements !=
|
||||
next_emb->nested_join->counter)
|
||||
break;
|
||||
|
||||
/*
|
||||
We're currently at Y or Z-bracket as depicted in the above picture.
|
||||
Mark that we've left it and continue walking up the brackets hierarchy.
|
||||
*/
|
||||
join->cur_embedding_map &= ~next_emb->nested_join->nj_map;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Nested joins perspective: Remove the last table from the join order
|
||||
|
||||
SYNOPSIS
|
||||
restore_prev_nj_state()
|
||||
last join table to remove, it is assumed to be the last in current
|
||||
partial join order.
|
||||
|
||||
DESCRIPTION
|
||||
Remove the last table from the partial join order and update the nested
|
||||
joins counters and join->cur_embedding_map. It is ok to call this
|
||||
function for the first table in join order (for which
|
||||
check_interleaving_with_nj has not been called)
|
||||
*/
|
||||
|
||||
static void restore_prev_nj_state(JOIN_TAB *last)
|
||||
{
|
||||
TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding;
|
||||
JOIN *join= last->join;
|
||||
while (last_emb && !(--last_emb->nested_join->counter))
|
||||
{
|
||||
join->cur_embedding_map &= last_emb->nested_join->nj_map;
|
||||
last_emb= last_emb->embedding;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static COND *
|
||||
optimize_cond(JOIN *join, COND *conds, List<TABLE_LIST> *join_list,
|
||||
|
Reference in New Issue
Block a user