Commit e2cd3dd1 authored by timour@mysql.com's avatar timour@mysql.com

WL#1724 "Min/Max Optimization for Queries with Group By Clause"

- after-review changes
- merged with the source tree from 204-08-27
parent a5f063e0
......@@ -200,19 +200,19 @@ select distinct 1 from t1,t3 where t1.a=t3.a;
1
explain SELECT distinct t1.a from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by
explain SELECT distinct t1.a from t1 order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by; Using temporary; Using filesort
explain SELECT t1.a from t1 group by a order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by; Using temporary; Using filesort
explain SELECT distinct t1.a from t1 order by a desc limit 1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 index NULL PRIMARY 4 NULL 5 Using index
explain SELECT distinct a from t3 order by a desc limit 2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t3 index NULL a 5 NULL 204 Using index
1 SIMPLE t3 index NULL a 5 NULL 10 Using index
explain SELECT distinct a,b from t3 order by a+1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t3 ALL NULL NULL NULL NULL 204 Using temporary; Using filesort
......
This diff is collapsed.
This diff is collapsed.
......@@ -1515,8 +1515,9 @@ longlong ha_myisam::get_auto_increment()
int error;
byte key[MI_MAX_KEY_LENGTH];
(void) extra(HA_EXTRA_KEYREAD);
key_copy(key,table,table->next_number_index,
table->next_number_key_offset);
key_copy(key, table->record[0],
table->key_info + table->next_number_index,
table->next_number_key_offset);
error=mi_rkey(file,table->record[1],(int) table->next_number_index,
key,table->next_number_key_offset,HA_READ_PREFIX_LAST);
if (error)
......
......@@ -984,7 +984,8 @@ longlong handler::get_auto_increment()
else
{
byte key[MAX_KEY_LENGTH];
key_copy(key,table,table->next_number_index,
key_copy(key, table->record[0],
table->key_info + table->next_number_index,
table->next_number_key_offset);
error=index_read(table->record[1], key, table->next_number_key_offset,
HA_READ_PREFIX_LAST);
......
......@@ -159,6 +159,46 @@ bool Item_ident::remove_dependence_processor(byte * arg)
}
/*
Store the pointer to this item field into a list if not already there.
SYNOPSIS
Item_field::collect_item_field_processor()
arg pointer to a List<Item_field>
DESCRIPTION
The method is used by Item::walk to collect all unique Item_field objects
from a tree of Items into a set of items represented as a list.
IMPLEMENTATION
Item_cond::walk() and Item_func::walk() stop the evaluation of the
processor function for its arguments once the processor returns
true.Therefore in order to force this method being called for all item
arguments in a condition the method must return false.
RETURN
false on success (force the evaluation of collect_item_field_processor
for the subsequent items.)
true o/w (stop evaluation of subsequent items.)
*/
bool Item_field::collect_item_field_processor(byte *arg)
{
DBUG_ENTER("Item_field::collect_item_field_processor");
DBUG_PRINT("info", ("%s", field->field_name ? field->field_name : "noname"));
List<Item_field> *item_list= (List<Item_field>*) arg;
List_iterator<Item_field> item_list_it(*item_list);
Item_field *curr_item;
while ((curr_item= item_list_it++))
{
if (curr_item->eq(this, 1))
DBUG_RETURN(false); /* Already in the set. */
}
item_list->push_back(this);
DBUG_RETURN(false);
}
bool Item::check_cols(uint c)
{
if (c != 1)
......
......@@ -260,6 +260,22 @@ class Item {
virtual bool remove_dependence_processor(byte * arg) { return 0; }
virtual bool remove_fixed(byte * arg) { fixed= 0; return 0; }
/*
All collect_* methods are used as arguments to walk() to collect
specific types items.
TODO:
A more generic implementation would add a special class
Collect_processor_param that can store arbitrary sets of item kinds
(currently specified as enums), along with a list to store items of the
specified kinds. This would allow to collect combinations of items of
arbitrary kinds without having to add a new collect method each time.
There can be one generic collect_processor method that checks the item type
and compares it with the item types in Collect_processor_param.
*/
virtual bool collect_item_field_processor(byte * arg) { return 0; }
virtual bool collect_item_sum_min_processor(byte * arg) { return 0; }
virtual bool collect_item_sum_max_processor(byte * arg) { return 0; }
virtual bool has_non_min_max_sum_processor(byte * arg) { return 0; }
virtual Item *this_item() { return this; } /* For SPs mostly. */
virtual Item *this_const_item() const { return const_cast<Item*>(this); } /* For SPs mostly. */
......@@ -493,6 +509,7 @@ class Item_field :public Item_ident
bool get_time(TIME *ltime);
bool is_null() { return field->is_null(); }
Item *get_tmp_table_item(THD *thd);
bool collect_item_field_processor(byte * arg);
void cleanup();
inline uint32 max_disp_length() { return field->max_length(); }
friend class Item_default_value;
......@@ -984,6 +1001,8 @@ class Item_ref :public Item_ident
(*ref)->save_in_field(result_field, no_conversions);
}
Item *real_item() { return *ref; }
bool walk(Item_processor processor, byte *arg)
{ return (*ref)->walk(processor, arg); }
void print(String *str);
void cleanup();
};
......
......@@ -182,6 +182,45 @@ bool Item_sum::walk (Item_processor processor, byte *argument)
return (this->*processor)(argument);
}
/*
Store the pointer to this item into a list if not already there.
SYNOPSIS
Item_sum::collect()
item_list pointer to a List<Item_sum> where item_sum objects are collected
DESCRIPTION
The method is used by collect_item_sum_*_processor, called by
Item_sum::walk, to collect all unique Item_sum_min and Item_sum_max objects
from a tree of Items into a set of items represented as a list.
IMPLEMENTATION
Item_cond::walk() and Item_func::walk() stop the evaluation of the
processor function for its arguments once the processor returns
true.Therefore in order to force this method being called for all item
arguments in a condition the method must return false.
RETURN
FALSE on success (force the evaluation of collect_item_sum_*_processor
for the subsequent items.)
TRUE o/w (stop evaluation of subsequent items.)
*/
bool Item_sum::collect(List<Item_sum> *item_list)
{
List_iterator<Item_sum> item_list_it(*item_list);
Item_sum *curr_item;
while ((curr_item= item_list_it++))
{
if (curr_item == this)
return FALSE; /* Already in the set. */
}
item_list->push_back(this);
return FALSE;
}
String *
Item_sum_num::val_str(String *str)
{
......
......@@ -27,6 +27,8 @@ class Item_arena;
class Item_sum :public Item_result_field
{
private:
bool collect(List<Item_sum> *item_list);
public:
enum Sumfunctype
{ COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC,
......@@ -98,6 +100,33 @@ class Item_sum :public Item_result_field
bool save_args(Item_arena* stmt);
bool walk (Item_processor processor, byte *argument);
/* Collect Item_sum_min objects into a list supplied by the caller. */
bool collect_item_sum_min_processor(byte *arg)
{
if (Item_sum::MIN_FUNC == this->sum_func())
return collect((List<Item_sum>*) arg);
else
return FALSE;
}
/* Collect Item_sum_max objects into a list supplied by the caller. */
bool collect_item_sum_max_processor(byte *arg)
{
if (Item_sum::MAX_FUNC == this->sum_func())
return collect((List<Item_sum>*) arg);
else
return FALSE;
}
/* Check if there are any aggregate functions other than MIN and MAX. */
bool has_non_min_max_sum_processor(byte * arg)
{
Sumfunctype sum_type= this->sum_func();
if ((sum_type != Item_sum::MIN_FUNC) && (sum_type != Item_sum::MAX_FUNC))
return TRUE;
return FALSE;
}
};
......
......@@ -66,94 +66,121 @@ int find_ref_key(TABLE *table,Field *field, uint *key_length)
}
/* Copy a key from record to some buffer */
/* if length == 0 then copy whole key */
/*
Copy part of a record that forms a key or key prefix to a buffer.
SYNOPSIS
key_copy()
to_key buffer that will be used as a key
from_record full record to be copied from
key_info descriptor of the index
key_length specifies length of all keyparts that will be copied
DESCRIPTION
The function takes a complete table record (as e.g. retrieved by
handler::index_read()), and a description of an index on the same table,
and extracts the first key_length bytes of the record which are part of a
key into to_key. If length == 0 then copy all bytes from the record that
form a key.
RETURN
None
*/
void key_copy(byte *key,TABLE *table,uint idx,uint key_length)
void key_copy(byte *to_key, byte *from_record, KEY *key_info, uint key_length)
{
uint length;
KEY *key_info=table->key_info+idx;
KEY_PART_INFO *key_part;
if (key_length == 0)
key_length=key_info->key_length;
for (key_part=key_info->key_part;
(int) key_length > 0 ;
key_part++)
key_length= key_info->key_length;
for (key_part= key_info->key_part; (int) key_length > 0; key_part++)
{
if (key_part->null_bit)
{
*key++= test(table->record[0][key_part->null_offset] &
*to_key++= test(from_record[key_part->null_offset] &
key_part->null_bit);
key_length--;
}
if (key_part->key_part_flag & HA_BLOB_PART)
{
char *pos;
ulong blob_length=((Field_blob*) key_part->field)->get_length();
key_length-=2;
ulong blob_length= ((Field_blob*) key_part->field)->get_length();
key_length-= 2;
((Field_blob*) key_part->field)->get_ptr(&pos);
length=min(key_length,key_part->length);
set_if_smaller(blob_length,length);
int2store(key,(uint) blob_length);
key+=2; // Skip length info
memcpy(key,pos,blob_length);
length=min(key_length, key_part->length);
set_if_smaller(blob_length, length);
int2store(to_key, (uint) blob_length);
to_key+= 2; // Skip length info
memcpy(to_key, pos, blob_length);
}
else
{
length=min(key_length,key_part->length);
memcpy(key,table->record[0]+key_part->offset,(size_t) length);
length= min(key_length, key_part->length);
memcpy(to_key, from_record + key_part->offset, (size_t) length);
}
key+=length;
key_length-=length;
to_key+= length;
key_length-= length;
}
} /* key_copy */
}
/* restore a key from some buffer to record */
/*
Restore a key from some buffer to record.
SYNOPSIS
key_restore()
to_record record buffer where the key will be restored to
from_key buffer that contains a key
key_info descriptor of the index
key_length specifies length of all keyparts that will be restored
DESCRIPTION
This function converts a key into record format. It can be used in cases
when we want to return a key as a result row.
RETURN
None
*/
void key_restore(TABLE *table,byte *key,uint idx,uint key_length)
void key_restore(byte *to_record, byte *from_key, KEY *key_info,
uint key_length)
{
uint length;
KEY *key_info=table->key_info+idx;
KEY_PART_INFO *key_part;
if (key_length == 0)
{
if (idx == (uint) -1)
return;
key_length=key_info->key_length;
key_length= key_info->key_length;
}
for (key_part=key_info->key_part;
(int) key_length > 0 ;
key_part++)
for (key_part= key_info->key_part ; (int) key_length > 0 ; key_part++)
{
if (key_part->null_bit)
{
if (*key++)
table->record[0][key_part->null_offset]|= key_part->null_bit;
if (*from_key++)
to_record[key_part->null_offset]|= key_part->null_bit;
else
table->record[0][key_part->null_offset]&= ~key_part->null_bit;
to_record[key_part->null_offset]&= ~key_part->null_bit;
key_length--;
}
if (key_part->key_part_flag & HA_BLOB_PART)
{
uint blob_length=uint2korr(key);
key+=2;
key_length-=2;
uint blob_length= uint2korr(from_key);
from_key+= 2;
key_length-= 2;
((Field_blob*) key_part->field)->set_ptr((ulong) blob_length,
(char*) key);
length=key_part->length;
(char*) from_key);
length= key_part->length;
}
else
{
length=min(key_length,key_part->length);
memcpy(table->record[0]+key_part->offset,key,(size_t) length);
length= min(key_length, key_part->length);
memcpy(to_record + key_part->offset, from_key, (size_t) length);
}
key+=length;
key_length-=length;
from_key+= length;
key_length-= length;
}
} /* key_restore */
}
/*
......
......@@ -733,7 +733,8 @@ bool add_proc_to_list(THD *thd, Item *item);
TABLE *unlink_open_table(THD *thd,TABLE *list,TABLE *find);
SQL_SELECT *make_select(TABLE *head, table_map const_tables,
table_map read_tables, COND *conds, int *error);
table_map read_tables, COND *conds, int *error,
bool allow_null_cond= false);
enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND,
IGNORE_ERRORS};
extern const Item **not_found_item;
......@@ -814,8 +815,9 @@ void print_plan(JOIN* join, double read_time, double record_count,
void mysql_print_status(THD *thd);
/* key.cc */
int find_ref_key(TABLE *form,Field *field, uint *offset);
void key_copy(byte *key,TABLE *form,uint index,uint key_length);
void key_restore(TABLE *form,byte *key,uint index,uint key_length);
void key_copy(byte *to_key, byte *from_record, KEY *key_info, uint key_length);
void key_restore(byte *to_record, byte *from_key, KEY *key_info,
uint key_length);
bool key_cmp_if_same(TABLE *form,const byte *key,uint index,uint key_length);
void key_unpack(String *to,TABLE *form,uint index);
bool check_if_key_used(TABLE *table, uint idx, List<Item> &fields);
......
This diff is collapsed.
......@@ -146,7 +146,8 @@ class QUICK_SELECT_I
QS_TYPE_RANGE_DESC = 2,
QS_TYPE_FULLTEXT = 3,
QS_TYPE_ROR_INTERSECT = 4,
QS_TYPE_ROR_UNION = 5
QS_TYPE_ROR_UNION = 5,
QS_TYPE_GROUP_MIN_MAX = 6
};
/* Get type of this quick select - one of the QS_TYPE_* values */
......@@ -278,14 +279,12 @@ class QUICK_RANGE_SELECT : public QUICK_SELECT_I
int init();
int get_next();
void range_end();
int get_next_prefix(uint prefix_length, byte *cur_prefix);
bool reverse_sorted() { return 0; }
bool unique_key_range();
int init_ror_merged_scan(bool reuse_handler);
void save_last_pos()
{
file->position(record);
};
{ file->position(record); }
int get_type() { return QS_TYPE_RANGE; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
void add_info_string(String *str);
......@@ -518,6 +517,103 @@ class QUICK_ROR_UNION_SELECT : public QUICK_SELECT_I
};
/*
Index scan for GROUP-BY queries with MIN/MAX aggregate functions.
This class provides a specialized index access method for GROUP-BY queries
of the forms:
SELECT A_1,...,A_k, [B_1,...,B_m], [MIN(C)], [MAX(C)]
FROM T
WHERE [RNG(A_1,...,A_p ; where p <= k)]
[AND EQ(B_1,...,B_m)]
[AND PC(C)]
[AND PA(A_i1,...,A_iq)]
GROUP BY A_1,...,A_k;
or
SELECT DISTINCT A_i1,...,A_ik
FROM T
WHERE [RNG(A_1,...,A_p ; where p <= k)]
[AND PA(A_i1,...,A_iq)];
where all selected fields are parts of the same index.
The class of queries that can be processed by this quick select is fully
specified in the description of get_best_trp_group_min_max() in opt_range.cc.
The get_next() method directly produces result tuples, thus obviating the
need to call end_send_group() because all grouping is already done inside
get_next().
Since one of the requirements is that all select fields are part of the same
index, this class produces only index keys, and not complete records.
*/
class QUICK_GROUP_MIN_MAX_SELECT : public QUICK_SELECT_I
{
private:
handler *file; /* The handler used to get data. */
JOIN *join; /* Descriptor of the current query */
KEY *index_info; /* The index chosen for data access */
byte *record; /* Buffer where the next record is returned. */
byte *tmp_record; /* Temporary storage for next_min(), next_max(). */
byte *group_prefix; /* Key prefix consisting of the GROUP fields. */
uint group_prefix_len; /* Length of the group prefix. */
byte *last_prefix; /* Prefix of the last group for detecting EOF. */
bool have_min; /* Specify whether we are computing */
bool have_max; /* a MIN, a MAX, or both. */
bool seen_first_key; /* Denotes whether the first key was retrieved.*/
KEY_PART_INFO *min_max_arg_part; /* The keypart of the only argument field */
/* of all MIN/MAX functions. */
uint min_max_arg_len; /* The length of the MIN/MAX argument field */
byte *key_infix; /* Infix of constants from equality predicates. */
uint key_infix_len;
DYNAMIC_ARRAY min_max_ranges; /* Array of range ptrs for the MIN/MAX field. */
uint real_prefix_len; /* Length of key prefix extended with key_infix. */
List<Item_sum> *min_functions;
List<Item_sum> *max_functions;
List_iterator<Item_sum> *min_functions_it;
List_iterator<Item_sum> *max_functions_it;
public:
/*
The following two members are public to allow easy access from
TRP_GROUP_MIN_MAX::make_quick()
*/
MEM_ROOT alloc; /* Memory pool for this and quick_prefix_select data. */
QUICK_RANGE_SELECT *quick_prefix_select;/* For retrieval of group prefixes. */
private:
int next_prefix();
int next_min_in_range();
int next_max_in_range();
int next_min();
int next_max();
void update_min_result();
void update_max_result();
public:
QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join, bool have_min,
bool have_max, KEY_PART_INFO *min_max_arg_part,
uint group_prefix_len, uint used_key_parts,
KEY *index_info, uint use_index, double read_cost,
ha_rows records, uint key_infix_len,
byte *key_infix, MEM_ROOT *parent_alloc);
~QUICK_GROUP_MIN_MAX_SELECT();
bool add_range(SEL_ARG *sel_range);
void update_key_stat();
bool alloc_buffers();
int init();
int reset();
int get_next();
bool reverse_sorted() { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_GROUP_MIN_MAX; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
#ifndef DBUG_OFF
void dbug_dump(int indent, bool verbose);
#endif
};
class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT
{
public:
......
......@@ -336,7 +336,7 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
1 Otherwise
*/
static bool simple_pred(Item_func *func_item, Item **args, bool *inv_order)
bool simple_pred(Item_func *func_item, Item **args, bool *inv_order)
{
Item *item;
*inv_order= 0;
......
......@@ -1884,7 +1884,7 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs)
col_privs->field[1]->pack_length()+
col_privs->field[2]->pack_length()+
col_privs->field[3]->pack_length());
key_copy(key,col_privs,0,key_len);
key_copy(key,col_privs->record[0],col_privs->key_info,key_len);
col_privs->field[4]->store("",0, &my_charset_latin1);
col_privs->file->ha_index_init(0);
if (col_privs->file->index_read(col_privs->record[0],
......@@ -1996,7 +1996,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
table->field[3]->store(table_name,(uint) strlen(table_name), &my_charset_latin1);
key_length=(table->field[0]->pack_length()+ table->field[1]->pack_length()+
table->field[2]->pack_length()+ table->field[3]->pack_length());
key_copy(key,table,0,key_length);
key_copy(key,table->record[0],table->key_info,key_length);
rights &= COL_ACLS; // Only ACL for columns
......@@ -2009,7 +2009,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
{
ulong privileges = xx->rights;
bool old_row_exists=0;
key_restore(table,key,0,key_length);
key_restore(table->record[0],key,table->key_info,key_length);
table->field[4]->store(xx->column.ptr(),xx->column.length(),
&my_charset_latin1);
......@@ -2025,7 +2025,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
}
old_row_exists = 0;
restore_record(table,default_values); // Get empty record
key_restore(table,key,0,key_length);
key_restore(table->record[0],key,table->key_info,key_length);
table->field[4]->store(xx->column.ptr(),xx->column.length(),
&my_charset_latin1);
}
......
......@@ -333,7 +333,7 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables,
send_error(thd,ER_OUTOFMEMORY);
goto err;
}
key_copy(key, table, keyno, key_len);
key_copy(key, table->record[0], table->key_info + keyno, key_len);
err=table->file->index_read(table->record[0],
key,key_len,ha_rkey_mode);
mode=rkey_to_rnext[(int)ha_rkey_mode];
......
......@@ -635,7 +635,7 @@ int write_record(TABLE *table,COPY_INFO *info)
goto err;
}
}
key_copy((byte*) key,table,key_nr,0);
key_copy((byte*) key,table->record[0],table->key_info+key_nr,0);
if ((error=(table->file->index_read_idx(table->record[1],key_nr,
(byte*) key,
table->key_info[key_nr].
......
This diff is collapsed.
......@@ -399,6 +399,7 @@ bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
uint find_shortest_key(TABLE *table, const key_map *usable_keys);
/* functions from opt_sum.cc */
bool simple_pred(Item_func *func_item, Item **args, bool *inv_order);
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds);
/* from sql_delete.cc, used by opt_range.cc */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment