mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Manual merge from mysql-trunk.
Conflicts: - client/mysqltest.cc - mysql-test/collections/default.experimental - mysql-test/suite/rpl/t/disabled.def - sql/mysqld.cc - sql/opt_range.cc - sql/sp.cc - sql/sql_acl.cc - sql/sql_partition.cc - sql/sql_table.cc
This commit is contained in:
526
sql/opt_range.cc
526
sql/opt_range.cc
@ -1,4 +1,4 @@
|
||||
/* Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc.
|
||||
/* Copyright 2000-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
|
||||
|
||||
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
|
||||
@ -438,8 +438,19 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns a number of keypart values appended to the key buffer */
|
||||
int store_min_key(KEY_PART *key, uchar **range_key, uint *range_key_flag)
|
||||
/*
|
||||
Returns a number of keypart values appended to the key buffer
|
||||
for min key and max key. This function is used by both Range
|
||||
Analysis and Partition pruning. For partition pruning we have
|
||||
to ensure that we don't store also subpartition fields. Thus
|
||||
we have to stop at the last partition part and not step into
|
||||
the subpartition fields. For Range Analysis we set last_part
|
||||
to MAX_KEY which we should never reach.
|
||||
*/
|
||||
int store_min_key(KEY_PART *key,
|
||||
uchar **range_key,
|
||||
uint *range_key_flag,
|
||||
uint last_part)
|
||||
{
|
||||
SEL_ARG *key_tree= first();
|
||||
uint res= key_tree->store_min(key[key_tree->part].store_length,
|
||||
@ -447,15 +458,21 @@ public:
|
||||
*range_key_flag|= key_tree->min_flag;
|
||||
if (key_tree->next_key_part &&
|
||||
key_tree->next_key_part->type == SEL_ARG::KEY_RANGE &&
|
||||
key_tree->part != last_part &&
|
||||
key_tree->next_key_part->part == key_tree->part+1 &&
|
||||
!(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN)))
|
||||
res+= key_tree->next_key_part->store_min_key(key, range_key,
|
||||
range_key_flag);
|
||||
res+= key_tree->next_key_part->store_min_key(key,
|
||||
range_key,
|
||||
range_key_flag,
|
||||
last_part);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* returns a number of keypart values appended to the key buffer */
|
||||
int store_max_key(KEY_PART *key, uchar **range_key, uint *range_key_flag)
|
||||
int store_max_key(KEY_PART *key,
|
||||
uchar **range_key,
|
||||
uint *range_key_flag,
|
||||
uint last_part)
|
||||
{
|
||||
SEL_ARG *key_tree= last();
|
||||
uint res=key_tree->store_max(key[key_tree->part].store_length,
|
||||
@ -463,10 +480,13 @@ public:
|
||||
(*range_key_flag)|= key_tree->max_flag;
|
||||
if (key_tree->next_key_part &&
|
||||
key_tree->next_key_part->type == SEL_ARG::KEY_RANGE &&
|
||||
key_tree->part != last_part &&
|
||||
key_tree->next_key_part->part == key_tree->part+1 &&
|
||||
!(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX)))
|
||||
res+= key_tree->next_key_part->store_max_key(key, range_key,
|
||||
range_key_flag);
|
||||
res+= key_tree->next_key_part->store_max_key(key,
|
||||
range_key,
|
||||
range_key_flag,
|
||||
last_part);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -634,6 +654,14 @@ public:
|
||||
using_real_indexes==TRUE
|
||||
*/
|
||||
uint real_keynr[MAX_KEY];
|
||||
|
||||
/*
|
||||
Used to store 'current key tuples', in both range analysis and
|
||||
partitioning (list) analysis
|
||||
*/
|
||||
uchar min_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH],
|
||||
max_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH];
|
||||
|
||||
/* Number of SEL_ARG objects allocated by SEL_ARG::clone_tree operations */
|
||||
uint alloced_sel_args;
|
||||
};
|
||||
@ -645,8 +673,6 @@ public:
|
||||
longlong baseflag;
|
||||
uint max_key_part, range_count;
|
||||
|
||||
uchar min_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH],
|
||||
max_key[MAX_KEY_LENGTH+MAX_FIELD_WIDTH];
|
||||
bool quick; // Don't calulate possible keys
|
||||
|
||||
uint fields_bitmap_size;
|
||||
@ -708,7 +734,8 @@ static
|
||||
TABLE_READ_PLAN *get_best_disjunct_quick(PARAM *param, SEL_IMERGE *imerge,
|
||||
double read_time);
|
||||
static
|
||||
TRP_GROUP_MIN_MAX *get_best_group_min_max(PARAM *param, SEL_TREE *tree);
|
||||
TRP_GROUP_MIN_MAX *get_best_group_min_max(PARAM *param, SEL_TREE *tree,
|
||||
double read_time);
|
||||
static double get_index_only_read_time(const PARAM* param, ha_rows records,
|
||||
int keynr);
|
||||
|
||||
@ -2050,7 +2077,7 @@ public:
|
||||
class TRP_GROUP_MIN_MAX : public TABLE_READ_PLAN
|
||||
{
|
||||
private:
|
||||
bool have_min, have_max;
|
||||
bool have_min, have_max, have_agg_distinct;
|
||||
KEY_PART_INFO *min_max_arg_part;
|
||||
uint group_prefix_len;
|
||||
uint used_key_parts;
|
||||
@ -2062,11 +2089,13 @@ private:
|
||||
SEL_TREE *range_tree; /* Represents all range predicates in the query. */
|
||||
SEL_ARG *index_tree; /* The SEL_ARG sub-tree corresponding to index_info. */
|
||||
uint param_idx; /* Index of used key in param->key. */
|
||||
/* Number of records selected by the ranges in index_tree. */
|
||||
bool is_index_scan; /* Use index_next() instead of random read */
|
||||
public:
|
||||
/* Number of records selected by the ranges in index_tree. */
|
||||
ha_rows quick_prefix_records;
|
||||
public:
|
||||
TRP_GROUP_MIN_MAX(bool have_min_arg, bool have_max_arg,
|
||||
TRP_GROUP_MIN_MAX(bool have_min_arg, bool have_max_arg,
|
||||
bool have_agg_distinct_arg,
|
||||
KEY_PART_INFO *min_max_arg_part_arg,
|
||||
uint group_prefix_len_arg, uint used_key_parts_arg,
|
||||
uint group_key_parts_arg, KEY *index_info_arg,
|
||||
@ -2075,11 +2104,12 @@ public:
|
||||
SEL_TREE *tree_arg, SEL_ARG *index_tree_arg,
|
||||
uint param_idx_arg, ha_rows quick_prefix_records_arg)
|
||||
: have_min(have_min_arg), have_max(have_max_arg),
|
||||
have_agg_distinct(have_agg_distinct_arg),
|
||||
min_max_arg_part(min_max_arg_part_arg),
|
||||
group_prefix_len(group_prefix_len_arg), used_key_parts(used_key_parts_arg),
|
||||
group_key_parts(group_key_parts_arg), index_info(index_info_arg),
|
||||
index(index_arg), key_infix_len(key_infix_len_arg), range_tree(tree_arg),
|
||||
index_tree(index_tree_arg), param_idx(param_idx_arg),
|
||||
index_tree(index_tree_arg), param_idx(param_idx_arg), is_index_scan(FALSE),
|
||||
quick_prefix_records(quick_prefix_records_arg)
|
||||
{
|
||||
if (key_infix_len)
|
||||
@ -2089,6 +2119,7 @@ public:
|
||||
|
||||
QUICK_SELECT_I *make_quick(PARAM *param, bool retrieve_full_rows,
|
||||
MEM_ROOT *parent_alloc);
|
||||
void use_index_scan() { is_index_scan= TRUE; }
|
||||
};
|
||||
|
||||
|
||||
@ -2350,7 +2381,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
|
||||
Try to construct a QUICK_GROUP_MIN_MAX_SELECT.
|
||||
Notice that it can be constructed no matter if there is a range tree.
|
||||
*/
|
||||
group_trp= get_best_group_min_max(¶m, tree);
|
||||
group_trp= get_best_group_min_max(¶m, tree, best_read_time);
|
||||
if (group_trp)
|
||||
{
|
||||
param.table->quick_condition_rows= min(group_trp->records,
|
||||
@ -2600,6 +2631,8 @@ typedef struct st_part_prune_param
|
||||
/* Same as above for subpartitioning */
|
||||
my_bool *is_subpart_keypart;
|
||||
|
||||
my_bool ignore_part_fields; /* Ignore rest of partioning fields */
|
||||
|
||||
/***************************************************************
|
||||
Following fields form find_used_partitions() recursion context:
|
||||
**************************************************************/
|
||||
@ -2613,8 +2646,13 @@ typedef struct st_part_prune_param
|
||||
/* Iterator to be used to obtain the "current" set of used partitions */
|
||||
PARTITION_ITERATOR part_iter;
|
||||
|
||||
/* Initialized bitmap of no_subparts size */
|
||||
/* Initialized bitmap of num_subparts size */
|
||||
MY_BITMAP subparts_bitmap;
|
||||
|
||||
uchar *cur_min_key;
|
||||
uchar *cur_max_key;
|
||||
|
||||
uint cur_min_flag, cur_max_flag;
|
||||
} PART_PRUNE_PARAM;
|
||||
|
||||
static bool create_partition_index_description(PART_PRUNE_PARAM *prune_par);
|
||||
@ -2732,6 +2770,11 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond)
|
||||
prune_param.arg_stack_end= prune_param.arg_stack;
|
||||
prune_param.cur_part_fields= 0;
|
||||
prune_param.cur_subpart_fields= 0;
|
||||
|
||||
prune_param.cur_min_key= prune_param.range_param.min_key;
|
||||
prune_param.cur_max_key= prune_param.range_param.max_key;
|
||||
prune_param.cur_min_flag= prune_param.cur_max_flag= 0;
|
||||
|
||||
init_all_partitions_iterator(part_info, &prune_param.part_iter);
|
||||
if (!tree->keys[0] || (-1 == (res= find_used_partitions(&prune_param,
|
||||
tree->keys[0]))))
|
||||
@ -2869,8 +2912,8 @@ static void mark_full_partition_used_no_parts(partition_info* part_info,
|
||||
static void mark_full_partition_used_with_parts(partition_info *part_info,
|
||||
uint32 part_id)
|
||||
{
|
||||
uint32 start= part_id * part_info->no_subparts;
|
||||
uint32 end= start + part_info->no_subparts;
|
||||
uint32 start= part_id * part_info->num_subparts;
|
||||
uint32 end= start + part_info->num_subparts;
|
||||
DBUG_ENTER("mark_full_partition_used_with_parts");
|
||||
|
||||
for (; start != end; start++)
|
||||
@ -2968,6 +3011,11 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge)
|
||||
ppar->arg_stack_end= ppar->arg_stack;
|
||||
ppar->cur_part_fields= 0;
|
||||
ppar->cur_subpart_fields= 0;
|
||||
|
||||
ppar->cur_min_key= ppar->range_param.min_key;
|
||||
ppar->cur_max_key= ppar->range_param.max_key;
|
||||
ppar->cur_min_flag= ppar->cur_max_flag= 0;
|
||||
|
||||
init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
|
||||
SEL_ARG *key_tree= (*ptree)->keys[0];
|
||||
if (!key_tree || (-1 == (res |= find_used_partitions(ppar, key_tree))))
|
||||
@ -3091,9 +3139,14 @@ static
|
||||
int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
{
|
||||
int res, left_res=0, right_res=0;
|
||||
int partno= (int)key_tree->part;
|
||||
bool pushed= FALSE;
|
||||
int key_tree_part= (int)key_tree->part;
|
||||
bool set_full_part_if_bad_ret= FALSE;
|
||||
bool ignore_part_fields= ppar->ignore_part_fields;
|
||||
bool did_set_ignore_part_fields= FALSE;
|
||||
RANGE_OPT_PARAM *range_par= &(ppar->range_param);
|
||||
|
||||
if (check_stack_overrun(range_par->thd, 3*STACK_MIN_SIZE, NULL))
|
||||
return -1;
|
||||
|
||||
if (key_tree->left != &null_element)
|
||||
{
|
||||
@ -3101,56 +3154,177 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Push SEL_ARG's to stack to enable looking backwards as well */
|
||||
ppar->cur_part_fields+= ppar->is_part_keypart[key_tree_part];
|
||||
ppar->cur_subpart_fields+= ppar->is_subpart_keypart[key_tree_part];
|
||||
*(ppar->arg_stack_end++)= key_tree;
|
||||
|
||||
if (key_tree->type == SEL_ARG::KEY_RANGE)
|
||||
{
|
||||
if (partno == 0 && (NULL != ppar->part_info->get_part_iter_for_interval))
|
||||
if (ppar->part_info->get_part_iter_for_interval &&
|
||||
key_tree->part <= ppar->last_part_partno)
|
||||
{
|
||||
/*
|
||||
Partitioning is done by RANGE|INTERVAL(monotonic_expr(fieldX)), and
|
||||
we got "const1 CMP fieldX CMP const2" interval <-- psergey-todo: change
|
||||
if (ignore_part_fields)
|
||||
{
|
||||
/*
|
||||
We come here when a condition on the first partitioning
|
||||
fields led to evaluating the partitioning condition
|
||||
(due to finding a condition of the type a < const or
|
||||
b > const). Thus we must ignore the rest of the
|
||||
partitioning fields but we still want to analyse the
|
||||
subpartitioning fields.
|
||||
*/
|
||||
if (key_tree->next_key_part)
|
||||
res= find_used_partitions(ppar, key_tree->next_key_part);
|
||||
else
|
||||
res= -1;
|
||||
goto pop_and_go_right;
|
||||
}
|
||||
/* Collect left and right bound, their lengths and flags */
|
||||
uchar *min_key= ppar->cur_min_key;
|
||||
uchar *max_key= ppar->cur_max_key;
|
||||
uchar *tmp_min_key= min_key;
|
||||
uchar *tmp_max_key= max_key;
|
||||
key_tree->store_min(ppar->key[key_tree->part].store_length,
|
||||
&tmp_min_key, ppar->cur_min_flag);
|
||||
key_tree->store_max(ppar->key[key_tree->part].store_length,
|
||||
&tmp_max_key, ppar->cur_max_flag);
|
||||
uint flag;
|
||||
if (key_tree->next_key_part &&
|
||||
key_tree->next_key_part->part == key_tree->part+1 &&
|
||||
key_tree->next_key_part->part <= ppar->last_part_partno &&
|
||||
key_tree->next_key_part->type == SEL_ARG::KEY_RANGE)
|
||||
{
|
||||
/*
|
||||
There are more key parts for partition pruning to handle
|
||||
This mainly happens when the condition is an equality
|
||||
condition.
|
||||
*/
|
||||
if ((tmp_min_key - min_key) == (tmp_max_key - max_key) &&
|
||||
(memcmp(min_key, max_key, (uint)(tmp_max_key - max_key)) == 0) &&
|
||||
!key_tree->min_flag && !key_tree->max_flag)
|
||||
{
|
||||
/* Set 'parameters' */
|
||||
ppar->cur_min_key= tmp_min_key;
|
||||
ppar->cur_max_key= tmp_max_key;
|
||||
uint save_min_flag= ppar->cur_min_flag;
|
||||
uint save_max_flag= ppar->cur_max_flag;
|
||||
|
||||
ppar->cur_min_flag|= key_tree->min_flag;
|
||||
ppar->cur_max_flag|= key_tree->max_flag;
|
||||
|
||||
res= find_used_partitions(ppar, key_tree->next_key_part);
|
||||
|
||||
/* Restore 'parameters' back */
|
||||
ppar->cur_min_key= min_key;
|
||||
ppar->cur_max_key= max_key;
|
||||
|
||||
ppar->cur_min_flag= save_min_flag;
|
||||
ppar->cur_max_flag= save_max_flag;
|
||||
goto pop_and_go_right;
|
||||
}
|
||||
/* We have arrived at the last field in the partition pruning */
|
||||
uint tmp_min_flag= key_tree->min_flag,
|
||||
tmp_max_flag= key_tree->max_flag;
|
||||
if (!tmp_min_flag)
|
||||
key_tree->next_key_part->store_min_key(ppar->key,
|
||||
&tmp_min_key,
|
||||
&tmp_min_flag,
|
||||
ppar->last_part_partno);
|
||||
if (!tmp_max_flag)
|
||||
key_tree->next_key_part->store_max_key(ppar->key,
|
||||
&tmp_max_key,
|
||||
&tmp_max_flag,
|
||||
ppar->last_part_partno);
|
||||
flag= tmp_min_flag | tmp_max_flag;
|
||||
}
|
||||
else
|
||||
flag= key_tree->min_flag | key_tree->max_flag;
|
||||
|
||||
if (tmp_min_key != range_par->min_key)
|
||||
flag&= ~NO_MIN_RANGE;
|
||||
else
|
||||
flag|= NO_MIN_RANGE;
|
||||
if (tmp_max_key != range_par->max_key)
|
||||
flag&= ~NO_MAX_RANGE;
|
||||
else
|
||||
flag|= NO_MAX_RANGE;
|
||||
|
||||
/*
|
||||
We need to call the interval mapper if we have a condition which
|
||||
makes sense to prune on. In the example of COLUMNS on a and
|
||||
b it makes sense if we have a condition on a, or conditions on
|
||||
both a and b. If we only have conditions on b it might make sense
|
||||
but this is a harder case we will solve later. For the harder case
|
||||
this clause then turns into use of all partitions and thus we
|
||||
simply set res= -1 as if the mapper had returned that.
|
||||
TODO: What to do here is defined in WL#4065.
|
||||
*/
|
||||
DBUG_EXECUTE("info", dbug_print_segment_range(key_tree,
|
||||
ppar->range_param.
|
||||
key_parts););
|
||||
res= ppar->part_info->
|
||||
get_part_iter_for_interval(ppar->part_info,
|
||||
FALSE,
|
||||
key_tree->min_value,
|
||||
key_tree->max_value,
|
||||
key_tree->min_flag | key_tree->max_flag,
|
||||
&ppar->part_iter);
|
||||
if (!res)
|
||||
goto go_right; /* res==0 --> no satisfying partitions */
|
||||
if (ppar->arg_stack[0]->part == 0)
|
||||
{
|
||||
uint32 i;
|
||||
uint32 store_length_array[MAX_KEY];
|
||||
uint32 num_keys= ppar->part_fields;
|
||||
|
||||
for (i= 0; i < num_keys; i++)
|
||||
store_length_array[i]= ppar->key[i].store_length;
|
||||
res= ppar->part_info->
|
||||
get_part_iter_for_interval(ppar->part_info,
|
||||
FALSE,
|
||||
store_length_array,
|
||||
range_par->min_key,
|
||||
range_par->max_key,
|
||||
tmp_min_key - range_par->min_key,
|
||||
tmp_max_key - range_par->max_key,
|
||||
flag,
|
||||
&ppar->part_iter);
|
||||
if (!res)
|
||||
goto pop_and_go_right; /* res==0 --> no satisfying partitions */
|
||||
}
|
||||
else
|
||||
res= -1;
|
||||
|
||||
if (res == -1)
|
||||
{
|
||||
//get a full range iterator
|
||||
/* get a full range iterator */
|
||||
init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
|
||||
}
|
||||
/*
|
||||
Save our intent to mark full partition as used if we will not be able
|
||||
to obtain further limits on subpartitions
|
||||
*/
|
||||
if (key_tree_part < ppar->last_part_partno)
|
||||
{
|
||||
/*
|
||||
We need to ignore the rest of the partitioning fields in all
|
||||
evaluations after this
|
||||
*/
|
||||
did_set_ignore_part_fields= TRUE;
|
||||
ppar->ignore_part_fields= TRUE;
|
||||
}
|
||||
set_full_part_if_bad_ret= TRUE;
|
||||
goto process_next_key_part;
|
||||
}
|
||||
|
||||
if (partno == ppar->last_subpart_partno &&
|
||||
if (key_tree_part == ppar->last_subpart_partno &&
|
||||
(NULL != ppar->part_info->get_subpart_iter_for_interval))
|
||||
{
|
||||
PARTITION_ITERATOR subpart_iter;
|
||||
DBUG_EXECUTE("info", dbug_print_segment_range(key_tree,
|
||||
ppar->range_param.
|
||||
key_parts););
|
||||
range_par->key_parts););
|
||||
res= ppar->part_info->
|
||||
get_subpart_iter_for_interval(ppar->part_info,
|
||||
TRUE,
|
||||
NULL, /* Currently not used here */
|
||||
key_tree->min_value,
|
||||
key_tree->max_value,
|
||||
key_tree->min_flag | key_tree->max_flag,
|
||||
0, 0, /* Those are ignored here */
|
||||
key_tree->min_flag |
|
||||
key_tree->max_flag,
|
||||
&subpart_iter);
|
||||
DBUG_ASSERT(res); /* We can't get "no satisfying subpartitions" */
|
||||
if (res == -1)
|
||||
return -1; /* all subpartitions satisfy */
|
||||
goto pop_and_go_right; /* all subpartitions satisfy */
|
||||
|
||||
uint32 subpart_id;
|
||||
bitmap_clear_all(&ppar->subparts_bitmap);
|
||||
@ -3163,23 +3337,19 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) !=
|
||||
NOT_A_PARTITION_ID)
|
||||
{
|
||||
for (uint i= 0; i < ppar->part_info->no_subparts; i++)
|
||||
for (uint i= 0; i < ppar->part_info->num_subparts; i++)
|
||||
if (bitmap_is_set(&ppar->subparts_bitmap, i))
|
||||
bitmap_set_bit(&ppar->part_info->used_partitions,
|
||||
part_id * ppar->part_info->no_subparts + i);
|
||||
part_id * ppar->part_info->num_subparts + i);
|
||||
}
|
||||
goto go_right;
|
||||
goto pop_and_go_right;
|
||||
}
|
||||
|
||||
if (key_tree->is_singlepoint())
|
||||
{
|
||||
pushed= TRUE;
|
||||
ppar->cur_part_fields+= ppar->is_part_keypart[partno];
|
||||
ppar->cur_subpart_fields+= ppar->is_subpart_keypart[partno];
|
||||
*(ppar->arg_stack_end++) = key_tree;
|
||||
|
||||
if (partno == ppar->last_part_partno &&
|
||||
ppar->cur_part_fields == ppar->part_fields)
|
||||
if (key_tree_part == ppar->last_part_partno &&
|
||||
ppar->cur_part_fields == ppar->part_fields &&
|
||||
ppar->part_info->get_part_iter_for_interval == NULL)
|
||||
{
|
||||
/*
|
||||
Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all partitioning
|
||||
@ -3208,7 +3378,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
goto process_next_key_part;
|
||||
}
|
||||
|
||||
if (partno == ppar->last_subpart_partno &&
|
||||
if (key_tree_part == ppar->last_subpart_partno &&
|
||||
ppar->cur_subpart_fields == ppar->subpart_fields)
|
||||
{
|
||||
/*
|
||||
@ -3232,7 +3402,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
NOT_A_PARTITION_ID)
|
||||
{
|
||||
bitmap_set_bit(&part_info->used_partitions,
|
||||
part_id * part_info->no_subparts + subpart_id);
|
||||
part_id * part_info->num_subparts + subpart_id);
|
||||
}
|
||||
res= 1; /* Some partitions were marked as used */
|
||||
goto pop_and_go_right;
|
||||
@ -3245,8 +3415,11 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree)
|
||||
we're processing subpartititoning's key parts, this means we'll not be
|
||||
able to infer any suitable condition, so bail out.
|
||||
*/
|
||||
if (partno >= ppar->last_part_partno)
|
||||
return -1;
|
||||
if (key_tree_part >= ppar->last_part_partno)
|
||||
{
|
||||
res= -1;
|
||||
goto pop_and_go_right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3255,7 +3428,17 @@ process_next_key_part:
|
||||
res= find_used_partitions(ppar, key_tree->next_key_part);
|
||||
else
|
||||
res= -1;
|
||||
|
||||
|
||||
if (did_set_ignore_part_fields)
|
||||
{
|
||||
/*
|
||||
We have returned from processing all key trees linked to our next
|
||||
key part. We are ready to be moving down (using right pointers) and
|
||||
this tree is a new evaluation requiring its own decision on whether
|
||||
to ignore partitioning fields.
|
||||
*/
|
||||
ppar->ignore_part_fields= FALSE;
|
||||
}
|
||||
if (set_full_part_if_bad_ret)
|
||||
{
|
||||
if (res == -1)
|
||||
@ -3278,18 +3461,14 @@ process_next_key_part:
|
||||
init_all_partitions_iterator(ppar->part_info, &ppar->part_iter);
|
||||
}
|
||||
|
||||
if (pushed)
|
||||
{
|
||||
pop_and_go_right:
|
||||
/* Pop this key part info off the "stack" */
|
||||
ppar->arg_stack_end--;
|
||||
ppar->cur_part_fields-= ppar->is_part_keypart[partno];
|
||||
ppar->cur_subpart_fields-= ppar->is_subpart_keypart[partno];
|
||||
}
|
||||
/* Pop this key part info off the "stack" */
|
||||
ppar->arg_stack_end--;
|
||||
ppar->cur_part_fields-= ppar->is_part_keypart[key_tree_part];
|
||||
ppar->cur_subpart_fields-= ppar->is_subpart_keypart[key_tree_part];
|
||||
|
||||
if (res == -1)
|
||||
return -1;
|
||||
go_right:
|
||||
if (key_tree->right != &null_element)
|
||||
{
|
||||
if (-1 == (right_res= find_used_partitions(ppar,key_tree->right)))
|
||||
@ -3371,13 +3550,14 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar)
|
||||
uint used_part_fields, used_subpart_fields;
|
||||
|
||||
used_part_fields= fields_ok_for_partition_index(part_info->part_field_array) ?
|
||||
part_info->no_part_fields : 0;
|
||||
part_info->num_part_fields : 0;
|
||||
used_subpart_fields=
|
||||
fields_ok_for_partition_index(part_info->subpart_field_array)?
|
||||
part_info->no_subpart_fields : 0;
|
||||
part_info->num_subpart_fields : 0;
|
||||
|
||||
uint total_parts= used_part_fields + used_subpart_fields;
|
||||
|
||||
ppar->ignore_part_fields= FALSE;
|
||||
ppar->part_fields= used_part_fields;
|
||||
ppar->last_part_partno= (int)used_part_fields - 1;
|
||||
|
||||
@ -3412,10 +3592,10 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar)
|
||||
if (ppar->subpart_fields)
|
||||
{
|
||||
my_bitmap_map *buf;
|
||||
uint32 bufsize= bitmap_buffer_size(ppar->part_info->no_subparts);
|
||||
uint32 bufsize= bitmap_buffer_size(ppar->part_info->num_subparts);
|
||||
if (!(buf= (my_bitmap_map*) alloc_root(alloc, bufsize)))
|
||||
return TRUE;
|
||||
bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->no_subparts,
|
||||
bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->num_subparts,
|
||||
FALSE);
|
||||
}
|
||||
range_par->key_parts= key_part;
|
||||
@ -3426,12 +3606,8 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar)
|
||||
{
|
||||
key_part->key= 0;
|
||||
key_part->part= part;
|
||||
key_part->store_length= key_part->length= (uint16) (*field)->key_length();
|
||||
if ((*field)->real_maybe_null())
|
||||
key_part->store_length+= HA_KEY_NULL_LENGTH;
|
||||
if ((*field)->type() == MYSQL_TYPE_BLOB ||
|
||||
(*field)->real_type() == MYSQL_TYPE_VARCHAR)
|
||||
key_part->store_length+= HA_KEY_BLOB_LENGTH;
|
||||
key_part->length= (uint16)(*field)->key_length();
|
||||
key_part->store_length= (uint16)get_partition_field_store_length(*field);
|
||||
|
||||
DBUG_PRINT("info", ("part %u length %u store_length %u", part,
|
||||
key_part->length, key_part->store_length));
|
||||
@ -5707,7 +5883,8 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field,
|
||||
value->result_type() == STRING_RESULT &&
|
||||
key_part->image_type == Field::itRAW &&
|
||||
((Field_str*)field)->charset() != conf_func->compare_collation() &&
|
||||
!(conf_func->compare_collation()->state & MY_CS_BINSORT))
|
||||
!(conf_func->compare_collation()->state & MY_CS_BINSORT &&
|
||||
(type == Item_func::EQUAL_FUNC || type == Item_func::EQ_FUNC)))
|
||||
goto end;
|
||||
|
||||
if (key_part->image_type == Field::itMBR)
|
||||
@ -7608,12 +7785,16 @@ check_quick_keys(PARAM *param, uint idx, SEL_ARG *key_tree,
|
||||
tmp_max_flag=key_tree->max_flag;
|
||||
if (!tmp_min_flag)
|
||||
tmp_min_keypart+=
|
||||
key_tree->next_key_part->store_min_key(param->key[idx], &tmp_min_key,
|
||||
&tmp_min_flag);
|
||||
key_tree->next_key_part->store_min_key(param->key[idx],
|
||||
&tmp_min_key,
|
||||
&tmp_min_flag,
|
||||
MAX_KEY);
|
||||
if (!tmp_max_flag)
|
||||
tmp_max_keypart+=
|
||||
key_tree->next_key_part->store_max_key(param->key[idx], &tmp_max_key,
|
||||
&tmp_max_flag);
|
||||
key_tree->next_key_part->store_max_key(param->key[idx],
|
||||
&tmp_max_key,
|
||||
&tmp_max_flag,
|
||||
MAX_KEY);
|
||||
min_key_length= (uint) (tmp_min_key - param->min_key);
|
||||
max_key_length= (uint) (tmp_max_key - param->max_key);
|
||||
}
|
||||
@ -7883,11 +8064,15 @@ get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key,
|
||||
{
|
||||
uint tmp_min_flag=key_tree->min_flag,tmp_max_flag=key_tree->max_flag;
|
||||
if (!tmp_min_flag)
|
||||
min_part+= key_tree->next_key_part->store_min_key(key, &tmp_min_key,
|
||||
&tmp_min_flag);
|
||||
min_part+= key_tree->next_key_part->store_min_key(key,
|
||||
&tmp_min_key,
|
||||
&tmp_min_flag,
|
||||
MAX_KEY);
|
||||
if (!tmp_max_flag)
|
||||
max_part+= key_tree->next_key_part->store_max_key(key, &tmp_max_key,
|
||||
&tmp_max_flag);
|
||||
max_part+= key_tree->next_key_part->store_max_key(key,
|
||||
&tmp_max_key,
|
||||
&tmp_max_flag,
|
||||
MAX_KEY);
|
||||
flag=tmp_min_flag | tmp_max_flag;
|
||||
}
|
||||
}
|
||||
@ -9180,15 +9365,10 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts,
|
||||
double *read_cost, ha_rows *records);
|
||||
|
||||
|
||||
/*
|
||||
/**
|
||||
Test if this access method is applicable to a GROUP query with MIN/MAX
|
||||
functions, and if so, construct a new TRP object.
|
||||
|
||||
SYNOPSIS
|
||||
get_best_group_min_max()
|
||||
param Parameter from test_quick_select
|
||||
sel_tree Range tree generated by get_mm_tree
|
||||
|
||||
DESCRIPTION
|
||||
Test whether a query can be computed via a QUICK_GROUP_MIN_MAX_SELECT.
|
||||
Queries computable via a QUICK_GROUP_MIN_MAX_SELECT must satisfy the
|
||||
@ -9299,17 +9479,16 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts,
|
||||
- Lift the limitation in condition (B3), that is, make this access method
|
||||
applicable to ROLLUP queries.
|
||||
|
||||
RETURN
|
||||
If mem_root != NULL
|
||||
- valid TRP_GROUP_MIN_MAX object if this QUICK class can be used for
|
||||
the query
|
||||
- NULL o/w.
|
||||
If mem_root == NULL
|
||||
- NULL
|
||||
@param param Parameter from test_quick_select
|
||||
@param sel_tree Range tree generated by get_mm_tree
|
||||
@param read_time Best read time so far (=table/index scan time)
|
||||
@return table read plan
|
||||
@retval NULL Loose index scan not applicable or mem_root == NULL
|
||||
@retval !NULL Loose index scan table read plan
|
||||
*/
|
||||
|
||||
static TRP_GROUP_MIN_MAX *
|
||||
get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
|
||||
{
|
||||
THD *thd= param->thd;
|
||||
JOIN *join= thd->lex->current_select->join;
|
||||
@ -9330,25 +9509,33 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
ORDER *tmp_group;
|
||||
Item *item;
|
||||
Item_field *item_field;
|
||||
bool is_agg_distinct;
|
||||
List<Item_field> agg_distinct_flds;
|
||||
|
||||
DBUG_ENTER("get_best_group_min_max");
|
||||
|
||||
/* Perform few 'cheap' tests whether this access method is applicable. */
|
||||
if (!join)
|
||||
DBUG_RETURN(NULL); /* This is not a select statement. */
|
||||
if ((join->tables != 1) || /* The query must reference one table. */
|
||||
((!join->group_list) && /* Neither GROUP BY nor a DISTINCT query. */
|
||||
(!join->select_distinct)) ||
|
||||
(join->select_lex->olap == ROLLUP_TYPE)) /* Check (B3) for ROLLUP */
|
||||
DBUG_RETURN(NULL);
|
||||
if (table->s->keys == 0) /* There are no indexes to use. */
|
||||
DBUG_RETURN(NULL);
|
||||
|
||||
/* Analyze the query in more detail. */
|
||||
List_iterator<Item> select_items_it(join->fields_list);
|
||||
|
||||
/* Check (SA1,SA4) and store the only MIN/MAX argument - the C attribute.*/
|
||||
if (join->make_sum_func_list(join->all_fields, join->fields_list, 1))
|
||||
DBUG_RETURN(NULL);
|
||||
|
||||
List_iterator<Item> select_items_it(join->fields_list);
|
||||
is_agg_distinct = is_indexed_agg_distinct(join, &agg_distinct_flds);
|
||||
|
||||
if ((!join->group_list) && /* Neither GROUP BY nor a DISTINCT query. */
|
||||
(!join->select_distinct) &&
|
||||
!is_agg_distinct)
|
||||
DBUG_RETURN(NULL);
|
||||
/* Analyze the query in more detail. */
|
||||
|
||||
if (join->sum_funcs[0])
|
||||
{
|
||||
Item_sum *min_max_item;
|
||||
@ -9359,6 +9546,10 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
have_min= TRUE;
|
||||
else if (min_max_item->sum_func() == Item_sum::MAX_FUNC)
|
||||
have_max= TRUE;
|
||||
else if (min_max_item->sum_func() == Item_sum::COUNT_DISTINCT_FUNC ||
|
||||
min_max_item->sum_func() == Item_sum::SUM_DISTINCT_FUNC ||
|
||||
min_max_item->sum_func() == Item_sum::AVG_DISTINCT_FUNC)
|
||||
continue;
|
||||
else
|
||||
DBUG_RETURN(NULL);
|
||||
|
||||
@ -9375,13 +9566,12 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
DBUG_RETURN(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check (SA5). */
|
||||
if (join->select_distinct)
|
||||
{
|
||||
while ((item= select_items_it++))
|
||||
{
|
||||
if (item->type() != Item::FIELD_ITEM)
|
||||
if (item->real_item()->type() != Item::FIELD_ITEM)
|
||||
DBUG_RETURN(NULL);
|
||||
}
|
||||
}
|
||||
@ -9389,7 +9579,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
/* Check (GA4) - that there are no expressions among the group attributes. */
|
||||
for (tmp_group= join->group_list; tmp_group; tmp_group= tmp_group->next)
|
||||
{
|
||||
if ((*tmp_group->item)->type() != Item::FIELD_ITEM)
|
||||
if ((*tmp_group->item)->real_item()->type() != Item::FIELD_ITEM)
|
||||
DBUG_RETURN(NULL);
|
||||
}
|
||||
|
||||
@ -9408,6 +9598,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
uint best_param_idx= 0;
|
||||
|
||||
const uint pk= param->table->s->primary_key;
|
||||
uint max_key_part;
|
||||
SEL_ARG *cur_index_tree= NULL;
|
||||
ha_rows cur_quick_prefix_records= 0;
|
||||
uint cur_param_idx=MAX_KEY;
|
||||
@ -9461,6 +9652,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
}
|
||||
}
|
||||
|
||||
max_key_part= 0;
|
||||
used_key_parts_map.clear_all();
|
||||
/*
|
||||
Check (GA1) for GROUP BY queries.
|
||||
*/
|
||||
@ -9484,6 +9677,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
{
|
||||
cur_group_prefix_len+= cur_part->store_length;
|
||||
++cur_group_key_parts;
|
||||
max_key_part= cur_part - cur_index_info->key_part + 1;
|
||||
used_key_parts_map.set_bit(max_key_part);
|
||||
}
|
||||
else
|
||||
goto next_index;
|
||||
@ -9497,14 +9692,26 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
Later group_fields_array of ORDER objects is used to convert the query
|
||||
to a GROUP query.
|
||||
*/
|
||||
else if (join->select_distinct)
|
||||
if ((!join->group_list && join->select_distinct) ||
|
||||
is_agg_distinct)
|
||||
{
|
||||
select_items_it.rewind();
|
||||
used_key_parts_map.clear_all();
|
||||
uint max_key_part= 0;
|
||||
while ((item= select_items_it++))
|
||||
if (!is_agg_distinct)
|
||||
{
|
||||
item_field= (Item_field*) item; /* (SA5) already checked above. */
|
||||
select_items_it.rewind();
|
||||
}
|
||||
|
||||
List_iterator<Item_field> agg_distinct_flds_it (agg_distinct_flds);
|
||||
while (NULL != (item = (is_agg_distinct ?
|
||||
(Item *) agg_distinct_flds_it++ : select_items_it++)))
|
||||
{
|
||||
/* (SA5) already checked above. */
|
||||
item_field= (Item_field*) item->real_item();
|
||||
DBUG_ASSERT(item->real_item()->type() == Item::FIELD_ITEM);
|
||||
|
||||
/* not doing loose index scan for derived tables */
|
||||
if (!item_field->field)
|
||||
goto next_index;
|
||||
|
||||
/* Find the order of the key part in the index. */
|
||||
key_part_nr= get_field_keypart(cur_index_info, item_field->field);
|
||||
/*
|
||||
@ -9513,7 +9720,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
*/
|
||||
if (used_key_parts_map.is_set(key_part_nr))
|
||||
continue;
|
||||
if (key_part_nr < 1 || key_part_nr > join->fields_list.elements)
|
||||
if (key_part_nr < 1 ||
|
||||
(!is_agg_distinct && key_part_nr > join->fields_list.elements))
|
||||
goto next_index;
|
||||
cur_part= cur_index_info->key_part + key_part_nr - 1;
|
||||
cur_group_prefix_len+= cur_part->store_length;
|
||||
@ -9533,10 +9741,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
if (all_parts != cur_parts)
|
||||
goto next_index;
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_ASSERT(FALSE);
|
||||
}
|
||||
|
||||
/* Check (SA2). */
|
||||
if (min_max_arg_item)
|
||||
@ -9690,7 +9894,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
|
||||
/* The query passes all tests, so construct a new TRP object. */
|
||||
read_plan= new (param->mem_root)
|
||||
TRP_GROUP_MIN_MAX(have_min, have_max, min_max_arg_part,
|
||||
TRP_GROUP_MIN_MAX(have_min, have_max, is_agg_distinct,
|
||||
min_max_arg_part,
|
||||
group_prefix_len, used_key_parts,
|
||||
group_key_parts, index_info, index,
|
||||
key_infix_len,
|
||||
@ -9704,6 +9909,11 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree)
|
||||
|
||||
read_plan->read_cost= best_read_cost;
|
||||
read_plan->records= best_records;
|
||||
if (read_time < best_read_cost && is_agg_distinct)
|
||||
{
|
||||
read_plan->read_cost= 0;
|
||||
read_plan->use_index_scan();
|
||||
}
|
||||
|
||||
DBUG_PRINT("info",
|
||||
("Returning group min/max plan: cost: %g, records: %lu",
|
||||
@ -10213,11 +10423,12 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows,
|
||||
|
||||
quick= new QUICK_GROUP_MIN_MAX_SELECT(param->table,
|
||||
param->thd->lex->current_select->join,
|
||||
have_min, have_max, min_max_arg_part,
|
||||
have_min, have_max,
|
||||
have_agg_distinct, min_max_arg_part,
|
||||
group_prefix_len, group_key_parts,
|
||||
used_key_parts, index_info, index,
|
||||
read_cost, records, key_infix_len,
|
||||
key_infix, parent_alloc);
|
||||
key_infix, parent_alloc, is_index_scan);
|
||||
if (!quick)
|
||||
DBUG_RETURN(NULL);
|
||||
|
||||
@ -10297,6 +10508,9 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows,
|
||||
key_infix_len Length of the key infix appended to the group prefix
|
||||
key_infix Infix of constants from equality predicates
|
||||
parent_alloc Memory pool for this and quick_prefix_select data
|
||||
is_index_scan get the next different key not by jumping on it via
|
||||
index read, but by scanning until the end of the
|
||||
rows with equal key value.
|
||||
|
||||
RETURN
|
||||
None
|
||||
@ -10304,20 +10518,22 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows,
|
||||
|
||||
QUICK_GROUP_MIN_MAX_SELECT::
|
||||
QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join_arg, bool have_min_arg,
|
||||
bool have_max_arg,
|
||||
bool have_max_arg, bool have_agg_distinct_arg,
|
||||
KEY_PART_INFO *min_max_arg_part_arg,
|
||||
uint group_prefix_len_arg, uint group_key_parts_arg,
|
||||
uint used_key_parts_arg, KEY *index_info_arg,
|
||||
uint use_index, double read_cost_arg,
|
||||
ha_rows records_arg, uint key_infix_len_arg,
|
||||
uchar *key_infix_arg, MEM_ROOT *parent_alloc)
|
||||
uchar *key_infix_arg, MEM_ROOT *parent_alloc,
|
||||
bool is_index_scan_arg)
|
||||
:join(join_arg), index_info(index_info_arg),
|
||||
group_prefix_len(group_prefix_len_arg),
|
||||
group_key_parts(group_key_parts_arg), have_min(have_min_arg),
|
||||
have_max(have_max_arg), seen_first_key(FALSE),
|
||||
min_max_arg_part(min_max_arg_part_arg), key_infix(key_infix_arg),
|
||||
key_infix_len(key_infix_len_arg), min_functions_it(NULL),
|
||||
max_functions_it(NULL)
|
||||
have_max(have_max_arg), have_agg_distinct(have_agg_distinct_arg),
|
||||
seen_first_key(FALSE), min_max_arg_part(min_max_arg_part_arg),
|
||||
key_infix(key_infix_arg), key_infix_len(key_infix_len_arg),
|
||||
min_functions_it(NULL), max_functions_it(NULL),
|
||||
is_index_scan(is_index_scan_arg)
|
||||
{
|
||||
head= table;
|
||||
file= head->file;
|
||||
@ -10880,6 +11096,56 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_max()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Find the next different key value by skiping all the rows with the same key
|
||||
value.
|
||||
|
||||
Implements a specialized loose index access method for queries
|
||||
containing aggregate functions with distinct of the form:
|
||||
SELECT [SUM|COUNT|AVG](DISTINCT a,...) FROM t
|
||||
This method comes to replace the index scan + Unique class
|
||||
(distinct selection) for loose index scan that visits all the rows of a
|
||||
covering index instead of jumping in the begining of each group.
|
||||
TODO: Placeholder function. To be replaced by a handler API call
|
||||
|
||||
@param is_index_scan hint to use index scan instead of random index read
|
||||
to find the next different value.
|
||||
@param file table handler
|
||||
@param key_part group key to compare
|
||||
@param record row data
|
||||
@param group_prefix current key prefix data
|
||||
@param group_prefix_len length of the current key prefix data
|
||||
@param group_key_parts number of the current key prefix columns
|
||||
@return status
|
||||
@retval 0 success
|
||||
@retval !0 failure
|
||||
*/
|
||||
|
||||
static int index_next_different (bool is_index_scan, handler *file,
|
||||
KEY_PART_INFO *key_part, uchar * record,
|
||||
const uchar * group_prefix,
|
||||
uint group_prefix_len,
|
||||
uint group_key_parts)
|
||||
{
|
||||
if (is_index_scan)
|
||||
{
|
||||
int result= 0;
|
||||
|
||||
while (!key_cmp (key_part, group_prefix, group_prefix_len))
|
||||
{
|
||||
result= file->index_next(record);
|
||||
if (result)
|
||||
return(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
return file->index_read_map(record, group_prefix,
|
||||
make_prev_keypart_map(group_key_parts),
|
||||
HA_READ_AFTER_KEY);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Determine the prefix of the next group.
|
||||
|
||||
@ -10926,9 +11192,9 @@ int QUICK_GROUP_MIN_MAX_SELECT::next_prefix()
|
||||
else
|
||||
{
|
||||
/* Load the first key in this group into record. */
|
||||
result= file->index_read_map(record, group_prefix,
|
||||
make_prev_keypart_map(group_key_parts),
|
||||
HA_READ_AFTER_KEY);
|
||||
result= index_next_different (is_index_scan, file, index_info->key_part,
|
||||
record, group_prefix, group_prefix_len,
|
||||
group_key_parts);
|
||||
if (result)
|
||||
DBUG_RETURN(result);
|
||||
}
|
||||
|
Reference in New Issue
Block a user