1
0
Files
irix-657m-src/eoe/cmd/sss/ssdb/ssdbserver/sql/sql_select.cc
2022-09-29 17:59:04 +03:00

5084 lines
141 KiB
C++

/* Copyright (C) 1979-1996 TcX AB & Monty Program KB & Detron HB
This software is distributed with NO WARRANTY OF ANY KIND. No author or
distributor accepts any responsibility for the consequences of using it, or
for whether it serves any particular purpose or works at all, unless he or
she says so in writing. Refer to the Free Public License (the "License")
for full details.
Every copy of this file must include a copy of the License, normally in a
plain ASCII text file named PUBLIC. The License grants you the right to
copy, modify and redistribute this file, but only under certain conditions
described in the License. Among other things, the License requires that
the copyright notice and this notice be preserved on all copies. */
/* mysql_select and join optimization */
#include "mysql_priv.h"
#include "sql_select.h"
#include <nisam.h>
my_string join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
"MAYBE_REF","ALL","range","index" };
static bool make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
DYNAMIC_ARRAY *keyuse);
static uint update_ref_and_keys(DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
uint tables,COND *conds,table_map table_map);
static int sort_keyuse(KEYUSE *a,KEYUSE *b);
static void set_position(JOIN *join,uint index,JOIN_TAB *table,KEYUSE *key);
static void find_best_combination(JOIN *join);
static void find_best(JOIN *join,table_map rest_tables,uint index,
double record_count,double read_time);
static uint cache_record_length(JOIN *join,uint index);
static double prev_record_reads(JOIN *join,table_map found_ref);
static void get_best_combination(JOIN *join);
static void make_simple_join(JOIN *join,TABLE *tmp_table);
static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item);
static void make_join_readinfo(JOIN *join,uint options);
static void join_free(JOIN *join);
static ORDER *remove_const(JOIN *join,ORDER *first_order,bool *simple_order);
static void return_zero_rows(select_result *res,TABLE_LIST *tables,
List<Item> &fields, bool send_row,
uint select_options, const char *info,
Item *having);
static COND *optimize_cond(COND *conds,Item::cond_result *cond_value);
static COND *remove_eq_conds(COND *cond,Item::cond_result *cond_value);
static TABLE * create_tmp_table(THD *thd,JOIN *join,List<Item> &fields,
ORDER *group,
bool distinct,bool save_sum_fields,
bool no_distinct_limit);
static void free_tmp_table(THD *thd, TABLE *entry);
static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table,
Procedure *proc);
static int sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records);
static int sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records);
static int flush_cacheed_records(JOIN *join,JOIN_TAB *join_tab,bool skipp_last);
static int end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_send_group(JOIN *join, JOIN_TAB *join_tab,bool end_of_records);
static int end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_write_group(JOIN *join, JOIN_TAB *join_tab,
bool end_of_records);
static void copy_fields(JOIN *join);
static int test_if_group_changed(List<Item_buff> &list);
static int join_read_const_tables(JOIN *join);
static int join_read_system(JOIN_TAB *tab);
static int join_read_const(JOIN_TAB *tab);
static int join_read_key(JOIN_TAB *tab);
static int join_read_always_key(JOIN_TAB *tab);
static int join_no_more_records(READ_RECORD *info);
static int join_read_next(READ_RECORD *info);
static int join_init_quick_read_record(JOIN_TAB *tab);
static int test_if_quick_select(JOIN_TAB *tab);
static int join_init_read_record(JOIN_TAB *tab);
static int join_init_read_first_with_key(JOIN_TAB *tab);
static int join_init_read_next_with_key(READ_RECORD *info);
static int join_init_read_last_with_key(JOIN_TAB *tab);
static int join_init_read_prev_with_key(READ_RECORD *info);
static COND *make_cond_for_table(COND *cond,table_map table,
table_map used_table);
static REF_FIELD* part_of_refkey(TABLE *form,Field *field);
static uint find_shortest_key(TABLE *table, key_map usable_keys);
static int create_sort_index(JOIN_TAB *tab,ORDER *order,ha_rows select_limit);
static int remove_duplicates(TABLE *entry,List<Item> &fields);
static SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length);
static int join_init_cache(THD *thd,JOIN_TAB *tables,uint table_count);
static ulong used_blob_length(CACHE_FIELD **ptr);
static bool store_record_in_cache(JOIN_CACHE *cache);
static void reset_cache(JOIN_CACHE *cache);
static void read_cacheed_record(JOIN_TAB *tab);
static void cp_buffer_from_ref(byte *buffer,REF_FIELD *fields,uint length);
static int setup_order(THD *thd,TABLE_LIST *tables, List<Item> &fields,
List <Item> &all_fields, ORDER *order);
static int setup_group(THD *thd,TABLE_LIST *tables,List<Item> &fields,
List<Item> &all_fields, ORDER *order, bool *hidden);
static bool setup_new_fields(THD *thd,TABLE_LIST *tables,List<Item> &fields,
List<Item> &all_fields,ORDER *new_order);
static ORDER *add_all_fields_to_order(JOIN *join,ORDER *order,
List<Item> &fields);
static void count_field_types(JOIN *join,List<Item> &fields);
static bool test_if_subpart(ORDER *a,ORDER *b);
static TABLE *get_sort_by_table(ORDER *a,ORDER *b,TABLE_LIST *tables);
static void calc_group_buffer(JOIN *join,ORDER *group);
static void alloc_group_fields(JOIN *join,ORDER *group);
static void setup_copy_fields(JOIN *join,List<Item> &fields);
static void make_sum_func_list(JOIN *join,List<Item> &fields);
static void change_to_use_tmp_fields(List<Item> &func);
static void change_refs_to_tmp_fields(List<Item> &func);
static void init_tmptable_sum_functions(Item_sum **func);
static void update_tmptable_sum_func(Item_sum **func,TABLE *tmp_table);
static void copy_funcs(Item_result_field **func_ptr);
static void copy_sum_funcs(Item_sum **func_ptr);
static void add_ref_to_table_cond(JOIN_TAB *join_tab);
static void init_sum_functions(Item_sum **func);
static void update_sum_func(Item_sum **func);
static void select_describe(JOIN *join);
static void describe_info(const char *info);
/*****************************************************************************
** check fields, find best join, do the select and output fields.
** mysql_select assumes that all tables are allready opened
*****************************************************************************/
int
mysql_select(THD *thd,TABLE_LIST *tables,List<Item> &fields,COND *conds,
ORDER *order, ORDER *group,Item *having,ORDER *proc_param,
uint select_options,select_result *result)
{
TABLE *tmp_table;
int error;
bool need_tmp,hidden_group_fields;
bool simple_order,simple_group;
Item::cond_result cond_value;
SQL_SELECT *select;
DYNAMIC_ARRAY keyuse;
JOIN join;
Procedure *procedure;
List<Item> all_fields(fields);
bool select_distinct;
DBUG_ENTER("mysql_select");
/* Check that all tables, fields, conds and order are ok */
select_distinct=test(select_options & SELECT_DISTINCT);
tmp_table=0;
select=0;
bzero((char*) &keyuse,sizeof(keyuse));
thd->proc_info="init";
if (setup_fields(thd,tables,fields,1,&all_fields) ||
setup_conds(thd,tables,conds) ||
setup_order(thd,tables,fields,all_fields,order) ||
setup_group(thd,tables,fields,all_fields,group,&hidden_group_fields))
DBUG_RETURN(-1); /* purecov: inspected */
if (having)
{
thd->where="having clause";
thd->allow_sum_func=1;
if (having->fix_fields(thd,tables) || thd->fatal_error)
DBUG_RETURN(-1); /* purecov: inspected */
if (having->with_sum_func)
having->split_sum_func(all_fields);
}
/*
Check if one one uses a not constant column with group functions
and no GROUP BY.
TODO: Add check of calculation of GROUP functions and fields:
SELECT COUNT(*)+table.col1 from table1;
*/
join.table=0;
join.tables=0;
{
if (!group)
{
uint flag=0;
List_iterator<Item> it(fields);
Item *item;
while ((item= it++))
{
if (item->with_sum_func)
flag|=1;
else if (!item->const_item())
flag|=2;
}
if (flag == 3)
{
my_error(ER_MIX_OF_GROUP_FUNC_AND_FIELDS,MYF(0));
DBUG_RETURN(-1);
}
}
TABLE_LIST *table;
for (table=tables ; table ; table=table->next)
join.tables++;
}
procedure=setup_procedure(thd,proc_param,result,&error);
if (error)
DBUG_RETURN(-1); /* purecov: inspected */
if (procedure)
{
if (setup_new_fields(thd,tables,fields,all_fields,procedure->param_fields))
{ /* purecov: inspected */
delete procedure; /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
}
if (procedure->group)
{
if (!test_if_subpart(procedure->group,group))
{ /* purecov: inspected */
my_message(0,"Can't handle procedures with differents groups yet",
MYF(0)); /* purecov: inspected */
delete procedure; /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
}
}
#ifdef NOT_NEEDED
else if (!group && procedure->flags & PROC_GROUP)
{
my_message(0,"Select must have a group with this procedure",MYF(0));
delete procedure;
DBUG_RETURN(-1);
}
#endif
if (order && (procedure->flags & PROC_NO_SORT))
{ /* purecov: inspected */
my_message(0,"Can't use order with this procedure",MYF(0)); /* purecov: inspected */
delete procedure; /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
}
}
/* Init join struct */
join.thd=thd;
join.lock=thd->lock;
join.join_tab=0;
join.copy_field=0;
join.sum_funcs=0;
join.send_records=0L;
join.end_write_records= HA_POS_ERROR;
join.first_record=join.sort_and_group=0;
join.select_options=select_options;
join.result=result;
count_field_types(&join,all_fields);
join.const_tables=0;
join.having=0;
join.group= group != 0;
#ifdef RESTRICTED_GROUP
if (join.sum_func_count && !group && (join.func_count || join.field_count))
{
my_message(ER_WRONG_SUM_SELECT,ER(ER_WRONG_SUM_SELECT));
delete procedure;
DBUG_RETURN(-1);
}
#endif
if (result->prepare(fields))
{ /* purecov: inspected */
delete procedure; /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
}
#ifdef HAVE_REF_TO_FIELDS // Not done yet
/* Add HAVING to WHERE if possible */
if (having && !group && ! join.sum_func_count)
{
if (!conds)
{
conds=having;
having=0;
}
else if ((conds=new Item_cond_and(conds,having)))
{
conds->fix_fields(thd,tables);
conds->change_ref_to_fields(thd,tables);
having=0;
}
}
#endif
conds=optimize_cond(conds,&cond_value);
if (cond_value == Item::COND_FALSE || !thd->select_limit)
{ /* Impossible cond */
return_zero_rows(result, tables, fields,join.sum_func_count != 0 && !group,
select_options,"Impossible WHERE",join.having);
delete procedure;
DBUG_RETURN(0);
}
/* Optimize count(*) */
if (tables && join.sum_func_count && ! group)
{
int res;
if ((res=opt_sum_query(tables, all_fields, conds)))
{
if (res < 0)
{
return_zero_rows(result, tables, fields, !group,
select_options,"No matching min/max row",join.having);
delete procedure;
DBUG_RETURN(0);
}
if (select_options & SELECT_DESCRIBE)
{
describe_info("Select tables optimized away");
delete procedure;
DBUG_RETURN(0);
}
tables=0; // All tables resolved
}
}
if (!tables)
{ // Only test of functions
if (select_options & SELECT_DESCRIBE)
describe_info("No tables used");
else
{
result->send_fields(fields,1);
if (!having || having->val_int())
{
if (result->send_data(fields))
result->send_error(0,NullS); /* purecov: inspected */
else
result->send_eof();
}
else
result->send_eof();
}
delete procedure;
DBUG_RETURN(0);
}
error = -1;
join.sort_by_table=get_sort_by_table(order,group,tables);
/* Calculate how to do the join */
thd->proc_info="statistics";
if (make_join_statistics(&join,tables,conds,&keyuse))
goto err;
thd->proc_info="preparing";
if (join_read_const_tables(&join) && ! (select_options & SELECT_DESCRIBE))
{
error=0;
return_zero_rows(result,tables,fields,
join.sum_func_count != 0 && !group,0,"",join.having);
goto err;
}
if (!(thd->options & OPTION_BIG_SELECTS) &&
join.best_read > (double) max_join_size &&
!(select_options & SELECT_DESCRIBE))
{ /* purecov: inspected */
result->send_error(ER_TOO_BIG_SELECT,ER(ER_TOO_BIG_SELECT)); /* purecov: inspected */
error= 1; /* purecov: inspected */
goto err; /* purecov: inspected */
}
if (!thd->locked_tables)
mysql_unlock_some_tables(join.table,join.const_tables);
select=make_select(join.table,join.tables,join.const_tables,
join.const_tables,conds,&error);
if (error)
{ /* purecov: inspected */
error= -1; /* purecov: inspected */
goto err; /* purecov: inspected */
}
if (make_join_select(&join,select,conds))
{
error=0;
return_zero_rows(result,tables,fields,join.sum_func_count != 0 && !group,
select_options,
"Impossible WHERE noticed after reading const tables",
join.having);
goto err;
}
error= -1; /* if goto err */
/* Optimize distinct away if possible */
if (group || join.sum_func_count)
{
if (! hidden_group_fields)
select_distinct=0;
}
else if (select_distinct && join.tables - join.const_tables == 1 &&
(order || thd->select_limit == HA_POS_ERROR))
{
if ((group=add_all_fields_to_order(&join,order,fields)))
{
select_distinct=0;
order=0;
join.group=1; // For end_write_group
}
}
order=remove_const(&join,order,&simple_order);
group=remove_const(&join,group,&simple_group);
calc_group_buffer(&join,group);
join.send_group_parts=join.group_parts; /* Save org parts */
if (procedure && procedure->group)
{
group=procedure->group=remove_const(&join,procedure->group,&simple_group);
calc_group_buffer(&join,group);
}
if (test_if_subpart(group,order) || (!group && join.sum_func_count))
order=0;
// Can't use sort on head table if using cache
if (join.full_join)
{
if (group)
simple_group=0;
if (order)
simple_order=0;
}
need_tmp= (join.const_tables != join.tables &&
((select_distinct || !simple_order || !simple_group) ||
(group && order)));
make_join_readinfo(&join,
(select_options & SELECT_DESCRIBE) | SELECT_USE_CACHE);
DBUG_EXECUTE("info",TEST_join(&join););
if (select_options & SELECT_DESCRIBE)
{
select_describe(&join);
error=0;
goto err;
}
/*
Because filesort always does a full table scan or a quick range scan
one must add the removed reference to the select for the table.
We only need to do this when we have a simple_order or simple_group
as in other cases the join is done before the sort.
*/
if ((order || group) && join.join_tab[join.const_tables].type != JT_ALL &&
(simple_order || simple_group))
{
#ifndef SAFE
add_ref_to_table_cond(&join.join_tab[join.const_tables]);
#else
// Do first a temporary table that contains the few found records.
need_tmp=1; simple_order=simple_group=0; // Force tmp table without sort
#endif
}
if (select_options & SELECT_SMALL_RESULT && (group || select_distinct))
{
need_tmp=1; simple_order=simple_group=0; // Force tmp table without sort
}
/* Create a tmp table if distinct or if the sort is too complicated */
if (need_tmp)
{
DBUG_PRINT("info",("Creating tmp table"));
thd->proc_info="Creating tmp table";
if (!(tmp_table =
create_tmp_table(thd,&join,all_fields,
((!simple_group && !procedure &&
!(test_flags & TEST_NO_KEY_GROUP)) ?
group : (ORDER*) 0),
group ? 0 : select_distinct,
group && simple_group,
order == 0)))
goto err; /* purecov: inspected */
if (having && (join.sort_and_group || tmp_table->distinct && !group))
join.having=having;
/* if group or order on first table, sort first */
if (group && simple_group)
{
DBUG_PRINT("info",("Sorting for group"));
thd->proc_info="Sorting for group";
if (create_sort_index(&join.join_tab[join.const_tables],group,
HA_POS_ERROR))
goto err; /* purecov: inspected */
make_sum_func_list(&join,all_fields);
alloc_group_fields(&join,group);
group=0;
}
else
{
make_sum_func_list(&join,all_fields);
if (!group && ! tmp_table->distinct && order && simple_order)
{
DBUG_PRINT("info",("Sorting for order"));
thd->proc_info="Sorting for order";
if (create_sort_index(&join.join_tab[join.const_tables],order,
HA_POS_ERROR))
goto err; /* purecov: inspected */
order=0;
}
}
thd->proc_info="Copying to tmp table";
if (do_select(&join,(List<Item> *) 0,tmp_table,0))
goto err; /* purecov: inspected */
if (join.having)
join.having=having=0; // Allready done
/* Change sum_fields reference to calculated fields in tmp_table */
if (join.sort_and_group || tmp_table->group)
{
change_to_use_tmp_fields(all_fields);
join.field_count+=join.sum_func_count+join.func_count;
join.sum_func_count=join.func_count=0;
}
else
{
change_refs_to_tmp_fields(all_fields);
join.field_count+=join.func_count;
join.func_count=0;
}
if (tmp_table->group)
{ // Already grouped
if (!order)
order=group; /* order by group */
group=0;
}
/*
** If we have different sort & group then we must sort the data by group
** and copy it to another tmp table
*/
if (group && (!test_if_subpart(group,order) || select_distinct))
{ /* Must copy to another table */
TABLE *tmp_table2;
DBUG_PRINT("info",("Creating group table"));
/* Free first data from old join */
join_free(&join);
make_simple_join(&join,tmp_table);
calc_group_buffer(&join,group);
count_field_types(&join,all_fields);
/* group data to new table */
if (!(tmp_table2 = create_tmp_table(thd,&join,all_fields,(ORDER*) 0,
0,1,0)))
goto err; /* purecov: inspected */
if (group)
{
thd->proc_info="Creating sort index";
if (create_sort_index(join.join_tab,group,HA_POS_ERROR))
{
free_tmp_table(thd,tmp_table2); /* purecov: inspected */
goto err; /* purecov: inspected */
}
alloc_group_fields(&join,group);
group=0;
}
make_sum_func_list(&join,all_fields); // Is this possible ?
thd->proc_info="Copying to group table";
if (do_select(&join,(List<Item> *) 0,tmp_table2,0))
goto err; /* purecov: inspected */
free_tmp_table(thd,tmp_table);
join.const_tables=join.tables; // Mark free for join_free()
tmp_table=tmp_table2;
join.join_tab[0].table=0; // Table is freed
change_to_use_tmp_fields(all_fields); // No sum funcs anymore
join.field_count+=join.sum_func_count;
join.sum_func_count=0;
}
if (tmp_table->distinct)
select_distinct=0; /* Each row is uniq */
if (select_distinct && ! group)
{
thd->proc_info="Removing duplicates";
if (remove_duplicates(tmp_table,fields))
goto err; /* purecov: inspected */
select_distinct=0;
}
tmp_table->reginfo.lock_type=TL_UNLOCK;
join_free(&join); /* Free quick selects */
make_simple_join(&join,tmp_table);
calc_group_buffer(&join,group);
count_field_types(&join,all_fields);
}
if (procedure)
{
procedure->change_columns(fields);
count_field_types(&join,all_fields);
}
if (group || join.sum_func_count ||
(procedure && (procedure->flags & PROC_GROUP)))
{
alloc_group_fields(&join,group);
setup_copy_fields(&join,all_fields);
make_sum_func_list(&join,all_fields);
if (thd->fatal_error)
goto err; /* purecov: inspected */
}
if (group || order)
{
DBUG_PRINT("info",("Sorting for send_fields"));
thd->proc_info="Sorting result";
/* If we have already done the group, add HAVING to sorted table */
if (having && ! group && ! join.sort_and_group)
{
having->update_used_tables(); // Tablenr may have changed
table_map used_tables=(1L << join.const_tables+1)-1L;
JOIN_TAB *table=&join.join_tab[join.const_tables];
Item* sort_table_cond=make_cond_for_table(having,used_tables,used_tables);
if (sort_table_cond)
{
if (!table->select)
if (!(table->select=new SQL_SELECT))
goto err;
if (!table->select->cond)
table->select->cond=sort_table_cond;
else // This should never happen
if (!(table->select->cond=new Item_cond_and(table->select->cond,
sort_table_cond)))
goto err;
DBUG_EXECUTE("where",print_where(table->select->cond,
"select and having"););
having=make_cond_for_table(having,~0L,~used_tables);
DBUG_EXECUTE("where",print_where(conds,"having after sort"););
}
}
if (create_sort_index(&join.join_tab[join.const_tables],
group ? group : order,
(having || group ||
join.const_tables != join.tables - 1) ?
HA_POS_ERROR : thd->select_limit))
goto err; /* purecov: inspected */
}
join.having=having; // Actually a parameter
thd->proc_info="Sending data";
error=do_select(&join,&fields,NULL,procedure);
err:
thd->proc_info="end";
join_free(&join);
thd->proc_info="end2"; // QQ
if (tmp_table)
free_tmp_table(thd,tmp_table);
thd->proc_info="end3"; // QQ
delete select;
delete_dynamic(&keyuse);
delete procedure;
thd->proc_info="end4"; // QQ
DBUG_RETURN(error);
}
/*****************************************************************************
** Create JOIN_TABS, make a guess about the table types,
** Approximate how many records will be used in each table
*****************************************************************************/
static inline Item *and_conds(Item *a,Item *b)
{
if (!b) return a;
if (!a) return b;
Item *cond=new Item_cond_and(a,b);
cond->update_used_tables();
return cond;
}
/*****************************************************************************
* Approximate how many records will be read from table
*****************************************************************************/
static ha_rows get_quick_record_count(SQL_SELECT *select,uint table,uint keys)
{
int error;
DBUG_ENTER("get_quick_record_count");
if (select)
{
select->head=select->tables[table];
if ((error=select->test_quick_select(keys,0L,HA_POS_ERROR)) == 1)
DBUG_RETURN(select->quick->records);
if (error == -1)
DBUG_RETURN(0);
DBUG_PRINT("warning",("Couldn't use record count on const keypart"));
}
DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */
}
static bool
make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
DYNAMIC_ARRAY *keyuse_array)
{
int error;
uint i,table_count,const_count,found_ref,refs,key,const_ref,eq_part;
table_map const_bits;
TABLE **table_vector,*table;
JOIN_TAB *stat,*stat_end,*s,**stat_ref;
SQL_SELECT *select;
KEYUSE *keyuse,*start_keyuse;
table_map outer_join=0;
JOIN_TAB *stat_vector[MAX_TABLES+1];
DBUG_ENTER("make_join_statistics");
table_count=join->tables;
stat=(JOIN_TAB*) sql_calloc(sizeof(JOIN_TAB)*table_count);
stat_ref=(JOIN_TAB**) sql_calloc(sizeof(JOIN_TAB*)*table_count);
table_vector=(TABLE**) sql_alloc(sizeof(TABLE**)*(table_count*2));
if (!stat || !stat_ref || !table_vector)
DBUG_RETURN(1); // Eom /* purecov: inspected */
select=0;
join->best_ref=stat_vector;
stat_end=stat+table_count;
const_bits=const_count=0;
for (s=stat,i=0 ; tables ; s++,tables=tables->next,i++)
{
TABLE *table;
stat_vector[i]=s;
table_vector[i]=s->table=table=tables->table;
ha_info(table,2); // Get record count
table->quick_keys=0;
if ((s->on_expr=tables->on_expr))
{
table->maybe_null=table->outer_join=1; // Mark for send fields
s->key_dependent=s->dependent=
s->on_expr->used_tables() & ~(table->map);
outer_join|=table->map;
continue;
}
if (tables->straight) // We don't have to move this
s->dependent= table_vector[i-1]->map;
else
s->dependent=0L;
s->key_dependent=0L;
if (table->system || table->keyfile_info.records <= 1L)
{
s->type=JT_SYSTEM;
const_bits|=table->map;
set_position(join,const_count++,s,(KEYUSE*) 0);
}
}
stat_vector[i]=0;
/*
** If outer join: Re-arrange tables in stat_vector so that outer join
** tables are after all tables it is dependent of.
** For example: SELECT * from A LEFT JOIN B ON B.c=C.c, C WHERE A.C=C.C
** Will shift table B after table C.
*/
if (outer_join)
{
table_map used_tables=0L;
for (i=0 ; i < join->tables-1 ; i++)
{
while (stat_vector[i]->dependent & ~used_tables)
{
table_map tmp_tables=used_tables;
JOIN_TAB *save= stat_vector[i];
uint j=i;
do
{
stat_vector[j]=stat_vector[j+1];
if ((stat_vector[j]->dependent & save->table->map) &&
(save->dependent & stat_vector[j]->table->map))
{
join->tables=0; // Don't use join->table
my_error(ER_WRONG_OUTER_JOIN,MYF(0));
DBUG_RETURN(1);
}
tmp_tables|= stat_vector[j]->table->map;
j++;
}
while (save->dependent & ~tmp_tables);
stat_vector[j]=save;
}
used_tables|= stat_vector[i]->table->map;
}
}
if (conds || outer_join)
VOID(update_ref_and_keys(keyuse_array,stat,join->tables,conds,
~outer_join));
/* loop until no more const tables are found */
do
{
found_ref=0;
for (JOIN_TAB **pos=stat_vector+const_count; s= *pos ; pos++)
{
if (s->on_expr) // If outer join table
{
if (s->dependent & ~(const_bits)) // All dep. must be constants
continue;
if (s->table->keyfile_info.records <= 1L)
{ // system table
s->type=JT_SYSTEM;
const_bits|=s->table->map;
set_position(join,const_count++,s,(KEYUSE*) 0);
continue;
}
}
/* check if table can be read by key or table only uses const refs */
if ((keyuse=s->keyuse))
{
s->type= JT_REF;
table=s->table;
while (keyuse->table == table)
{
start_keyuse=keyuse;
key=keyuse->key;
s->keys|= 1L << key; // QQ: remove this ?
refs=const_ref=eq_part=0;
do
{
if (!keyuse->field ||
const_bits & keyuse->field->table->map)
const_ref|= 1L << keyuse->keypart;
else
refs|=keyuse->field->table->map;
eq_part|= 1L << keyuse->keypart;
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
if (eq_part == PREV_BITS(table->key_info[key].key_parts) &&
!table->key_info[key].dupp_key)
{
if (const_ref == eq_part)
{ // Found everything for ref.
s->type=JT_CONST;
const_bits|=table->map;
set_position(join,const_count++,s,start_keyuse);
break;
}
else
found_ref|= refs; // Is const is theese are const
}
}
}
}
} while (const_bits & found_ref);
/* Calc how many (possible) matched records in each table */
for (s=stat ; s < stat_end ; s++)
{
if (s->type == JT_SYSTEM || s->type == JT_CONST)
{
s->found_records=s->records=s->read_time=1; /* System or ref */
continue;
}
/* Approxomite found rows and time to read them */
s->found_records=s->records=s->table->keyfile_info.records;
s->read_time=(ha_rows) ((s->table->keyfile_info.data_file_length)/IO_SIZE)+1;
/* if (s->type == JT_EQ_REF)
continue; */
if (s->const_keys)
{
ha_rows records;
if (!select)
select=make_select(table_vector,table_count,s->table->tablenr,
0,and_conds(conds,s->on_expr),&error);
records=get_quick_record_count(select,s->table->tablenr,
s->const_keys);
s->quick=select->quick;
s->needed_reg=select->needed_reg;
select->quick=0;
if (records != HA_POS_ERROR)
{
s->found_records=records;
s->read_time= s->quick ? s->quick->read_time : 0;
}
else
s->const_keys=0; /* Don't use const key */
}
}
delete select;
/* Find best combination and return it */
join->join_tab=stat;
join->map2table=stat_ref;
join->table= table_vector;
join->const_tables=const_count;
join->const_bits=const_bits;
if (join->const_tables != join->tables)
find_best_combination(join);
else
{
memcpy((gptr) join->best_positions,(gptr) join->positions,
sizeof(POSITION)*join->const_tables);
join->best_read=1.0;
}
get_best_combination(join);
DBUG_RETURN(0);
}
/*****************************************************************************
** check with keys are used and with tables references with tables
** updates in stat:
** keys Bitmap of all used keys
** const_keys Bitmap of all keys with may be used with quick_select
** keyuse Pointer to possible keys
*****************************************************************************/
typedef struct key_field_t { // Used when finding key fields
Field *field;
Item *val; // May be empty if diff constant
uint level,const_level; // QQ: Remove const_level
bool eq_func;
} KEY_FIELD;
/* merge new key definitions to old ones, remove those not used in both */
static KEY_FIELD *
merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
uint and_level)
{
if (start == new_fields)
return start; // Impossible or
if (new_fields == end)
return start; // No new fields, skipp all
KEY_FIELD *first_free=new_fields;
/* Mark all found fields in old array */
for (; new_fields != end ; new_fields++)
{
for (KEY_FIELD *old=start ; old != first_free ; old++)
{
if (old->field == new_fields->field)
{
if (new_fields->val->type() == Item::FIELD_ITEM)
{
if (old->val->eq(new_fields->val))
old->level=old->const_level=and_level;
}
else if (old->val->eq(new_fields->val) && old->eq_func &&
new_fields->eq_func)
{
old->level=old->const_level=and_level;
}
else // Impossible; remove it
{
if (old == --first_free) // If last item
break;
*old= *first_free; // Remove old value
old--; // Retry this value
}
}
}
}
/* Remove all not used items */
for (KEY_FIELD *old=start ; old != first_free ;)
{
if (old->level != and_level && old->const_level != and_level)
{ // Not used in all levels
if (old == --first_free)
break;
*old= *first_free; // Remove old value
continue;
}
old++;
}
return first_free;
}
static void
add_key_field(JOIN_TAB *stat,KEY_FIELD **key_fields,uint and_level,
Field *field,bool eq_func,Item *value,table_map usable_tables)
{
if (!(field->flags & PART_KEY_FLAG))
return; // Not a key. Skipp it
// Mark as possible quick key
uint tablenr=field->table->tablenr;
if (!(usable_tables & (table_map) 1 << tablenr))
return; // outer join table
stat+=tablenr;
stat[0].keys|=field->key_start; // Add possible keys
/* Save the following cases:
Field op constant
Field LIKE constant where constant doesn't start with a wildcard
field = field2 where field2 is in a different table
*/
if (!value)
{ // Probably BETWEEN or IN
stat[0].const_keys |= field->key_start;
return; // Can't be used as eq key
}
if ((value->type() == Item::FIELD_ITEM))
{
if (((Item_field *) value)->field->table->tablenr == tablenr ||
!eq_func)
return;
stat[0].key_dependent|=value->used_tables();
}
else if (!value->const_item())
return;
else
stat[0].const_keys |= field->key_start;
(*key_fields)->field=field;
(*key_fields)->eq_func=eq_func;
(*key_fields)->val=value;
(*key_fields)->level=(*key_fields)->const_level=and_level;
(*key_fields)++;
}
static void
add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
COND *cond, table_map usable_tables)
{
if (cond->type() == Item_func::COND_ITEM)
{
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
KEY_FIELD *org_key_fields= *key_fields;
if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
{
Item *item;
while ((item=li++))
add_key_fields(stat,key_fields,and_level,item,usable_tables);
for (; org_key_fields != *key_fields ; org_key_fields++)
{
if (org_key_fields->const_level == org_key_fields->level)
org_key_fields->const_level=org_key_fields->level= *and_level;
else
org_key_fields->const_level= *and_level;
}
}
else
{
(*and_level)++;
add_key_fields(stat,key_fields,and_level,li++,usable_tables);
Item *item;
while ((item=li++))
{
KEY_FIELD *start_key_fields= *key_fields;
(*and_level)++;
add_key_fields(stat,key_fields,and_level,item,usable_tables);
*key_fields=merge_key_fields(org_key_fields,start_key_fields,
*key_fields,++(*and_level));
}
}
return;
}
/* If item is of type 'field op field/constant' add it to key_fields */
if (cond->type() != Item::FUNC_ITEM)
return;
Item_func *cond_func= (Item_func*) cond;
switch (cond_func->select_optimize()) {
case Item_func::OPTIMIZE_NONE:
break;
case Item_func::OPTIMIZE_KEY:
if (cond_func->key_item()->type() == Item::FIELD_ITEM)
add_key_field(stat,key_fields,*and_level,
((Item_field*) (cond_func->key_item()))->field,
0,(Item*) 0,usable_tables);
break;
case Item_func::OPTIMIZE_OP:
if (cond_func->arguments()[0]->type() == Item::FIELD_ITEM)
{
add_key_field(stat,key_fields,*and_level,
((Item_field*) (cond_func->arguments()[0]))->field,
cond_func->functype() == Item_func::EQ_FUNC,
(cond_func->arguments()[1]),usable_tables);
}
if (cond_func->arguments()[1]->type() == Item::FIELD_ITEM &&
cond_func->functype() != Item_func::LIKE_FUNC)
{
add_key_field(stat,key_fields,*and_level,
((Item_field*) (cond_func->arguments()[1]))->field,
cond_func->functype() == Item_func::EQ_FUNC,
(cond_func->arguments()[0]),usable_tables);
}
break;
}
return;
}
/*
** Add all keys with uses 'field' for some keypart
** If field->and_level != and_level then only mark key_part as const_part
*/
static uint
max_part_bit(table_map bits)
{
uint found;
for (found=0; bits & 1 ; found++,bits>>=1) ;
return found;
}
static void
add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field)
{
Field *field=key_field->field,*cmp_field;
TABLE *form= field->table;
KEYUSE keyuse;
cmp_field= ((key_field->val->type() == Item::FIELD_ITEM) ?
((Item_field*) key_field->val)->field : 0);
for (uint key=0 ; key < form->keys ; key++)
{
bool test_eq= (key_field->eq_func);
if (test_eq || (1L << key) && field->key_start)
{
uint key_parts= test_eq ? (uint) form->key_info[key].key_parts : 1;
for (uint part=0 ; part < key_parts ; part++)
{
/*
QQ: Fix this to remove the pack_length() test.
One should be able to use different packed fields
*/
if (field->eq(form->key_info[key].key_part[part].field) &&
(!cmp_field ||
(cmp_field->pack_length() == field->pack_length() &&
cmp_field->type() == field->type())))
{
if (test_eq)
{
keyuse.table= field->table;
keyuse.field= cmp_field;
keyuse.val = key_field->val;
keyuse.key = key;
keyuse.keypart=part;
VOID(insert_dynamic(keyuse_array,(gptr) &keyuse));
}
}
}
}
}
}
static int
sort_keyuse(KEYUSE *a,KEYUSE *b)
{
if (a->table->tablenr != b->table->tablenr)
return (int) (a->table->tablenr - b->table->tablenr);
if (a->key != b->key)
return (int) (a->key - b->key);
if (a->keypart != b->keypart)
return (int) (a->keypart - b->keypart);
return test(a->field) - test(b->field); // Place const first
}
/*
** Update keyuse array with all possible keys we can use to fetch rows
** join_tab is a array in tablenr_order
** stat is a reference array in 'prefered' order.
*/
static uint
update_ref_and_keys(DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,uint tables,
COND *cond, table_map normal_tables)
{
uint and_level,i,tablenr,found_eq_constant;
KEY_FIELD *key_fields,*end;
if (!(key_fields=(KEY_FIELD*)
my_malloc(sizeof(key_fields[0])*
(current_thd->cond_count+1)*2,MYF(0))))
return 0; /* purecov: inspected */
and_level=0; end=key_fields;
if (cond)
add_key_fields(join_tab,&end,&and_level,cond,normal_tables);
for (i=0 ; i < tables ; i++)
{
if (join_tab[i].on_expr)
{
add_key_fields(join_tab,&end,&and_level,join_tab[i].on_expr,
join_tab[i].table->map);
}
}
VOID(init_dynamic_array(keyuse,sizeof(KEYUSE),20,20));
/* fill keyuse with found key parts */
for (KEY_FIELD *field=key_fields ; field != end ; field++)
add_key_part(keyuse,field);
my_free((gptr) key_fields,MYF(0));
/*
** remove ref if there is a keypart which is a ref and a const.
** remove keyparts without previous keyparts.
*/
if (keyuse->elements)
{
KEYUSE end,*prev,*save_pos,*use;
qsort(keyuse->buffer,keyuse->elements,sizeof(KEYUSE),
(qsort_cmp) sort_keyuse);
bzero((char*) &end,sizeof(end)); /* Add for easy testing */
VOID(insert_dynamic(keyuse,(gptr) &end));
use=save_pos=dynamic_element(keyuse,0,KEYUSE*);
prev=&end;
found_eq_constant=0;
for (i=0 ; i < keyuse->elements-1 ; i++,use++)
{
if (use->key == prev->key && use->table == prev->table)
{
if (prev->keypart+1 < use->keypart ||
prev->keypart == use->keypart && found_eq_constant)
continue; /* remove */
}
else if (use->keypart != 0) // First found must be 0
continue;
*save_pos= *use;
prev=use;
found_eq_constant= !use->field;
tablenr=use->table->tablenr;
if (!join_tab[tablenr].keyuse)
join_tab[tablenr].keyuse= save_pos; /* Save ptr to first use */
save_pos++;
}
i=(uint) (save_pos-(KEYUSE*) keyuse->buffer);
VOID(set_dynamic(keyuse,(gptr) &end,i));
keyuse->elements=i;
}
return and_level;
}
/*****************************************************************************
** Go through all combinations of not marked tables and find the one
** which uses least records
*****************************************************************************/
/* Save const tables first as used tables */
static void
set_position(JOIN *join,uint index,JOIN_TAB *table,KEYUSE *key)
{
join->positions[index].table= table;
join->positions[index].key=key;
join->positions[index].records_read=1.0; /* This is a const table */
join->index[table->table->tablenr]= (table_map) 1L << index;
/* Move the const table as down as possible in best_ref */
JOIN_TAB **pos=join->best_ref+index+1;
JOIN_TAB *next=join->best_ref[index];
for ( ;next != table ; pos++)
{
JOIN_TAB *tmp=pos[0];
pos[0]=next;
next=tmp;
}
join->best_ref[index]=table;
}
static void
find_best_combination(JOIN *join)
{
DBUG_ENTER("find_best_combination");
join->best_read=DBL_MAX;
find_best(join,PREV_BITS(join->tables) & ~join->const_bits,
join->const_tables,1.0,0.0);
DBUG_VOID_RETURN;
}
static void
find_best(JOIN *join,table_map rest_tables,uint index,double record_count,
double read_time)
{
uint keypart;
ulong rec;
double tmp;
if (!rest_tables)
{
DBUG_PRINT("best",("read_time: %g record_count: %g",read_time,
record_count));
read_time+=record_count/(double) TIME_FOR_COMPARE;
if (join->sort_by_table &&
join->sort_by_table != join->positions[join->const_tables].table->table)
read_time+=record_count; // We have to make a temp table
if (read_time < join->best_read)
{
memcpy((gptr) join->best_positions,(gptr) join->positions,
sizeof(POSITION)*index);
join->best_read=read_time;
}
return;
}
if (read_time+record_count/10.0 >= join->best_read)
return; /* Found better before */
JOIN_TAB *s;
double best_record_count=DBL_MAX,best_read_time=DBL_MAX;
for (JOIN_TAB **pos=join->best_ref+index ; (s=*pos) ; pos++)
{
table_map real_table_bit=s->table->map;
if (rest_tables & real_table_bit && !(rest_tables & s->dependent))
{
double best,records;
best=records=DBL_MAX;
KEYUSE *best_key=0;
if (s->keyuse)
{ /* Use key if possible */
TABLE *table=s->table;
KEYUSE *keyuse,*start_key=0;
double best_records=DBL_MAX;
/* Test how we can use keys */
rec= s->records/10; /* Assume 10 records/key */
for (keyuse=s->keyuse ; keyuse->table == table ;)
{
key_map found_part=0;
table_map found_ref=0;
uint key=keyuse->key;
KEY *keyinfo=table->key_info+key;
start_key=keyuse;
do
{
keypart=keyuse->keypart;
do
{
if (!keyuse->field ||
!(rest_tables & keyuse->field->table->map))
{
found_part|=1L << keypart;
if (keyuse->field)
found_ref|= join->index[keyuse->field->table->tablenr];
}
/*
** If we find a ref, assume this table matches a proportional
** part of this table.
** For example 100 records matching this table with 5000 records
** gives 5000/100 = 50 records per key
*/
if (keyuse->field &&
rec > keyuse->field->table->keyfile_info.records)
rec=keyuse->field->table->keyfile_info.records;
keyuse++;
} while (keyuse->table == table && keyuse->key == key &&
keyuse->keypart == keypart);
} while (keyuse->table == table && keyuse->key == key);
/*
** Assume that that each key matches a proportional part of table.
*/
if (!found_part)
continue; // Nothing usable found
if (rec == 0)
rec=1L; // Fix for small tables
/*
** Check if we found full key
*/
if (found_part == PREV_BITS(keyinfo->key_parts))
{ /* use eq key */
if (!keyinfo->dupp_key)
{
tmp=prev_record_reads(join,found_ref);
records=1.0;
}
else
{
if (!found_ref)
{
if (table->quick_keys & ((key_map) 1 << key))
records= (double) table->quick_rows[key];
else
records= (double) s->records; // quick_range couldn't use key!
}
else
{
if (!table->keyfile_info.rec_per_key ||
!(records=table->keyfile_info.rec_per_key[key]))
{ // Prefere longer keys
records=
((double) s->records / (double) rec *
(1.0 +
((double) (table->max_key_length-keyinfo->key_length) /
(double) table->max_key_length)));
// Fix ranges for small tables */
if (records > (double) s->records/5.0)
records= (double) s->records/5.0;
if (records < 2.0)
records=2.0; // Can't be as good as a unique
}
}
if (table->used_keys & ((key_map) 1 << key))
{
/* we can use only index tree */
uint keys_per_block= table->keyfile_info.block_size/2/
table->key_info[key].key_length+1;
tmp=record_count*(records+keys_per_block-1)/keys_per_block;
}
else
tmp=record_count*records;
}
}
else
{
/*
** Use as much key-parts as possible and a uniq key is better
** than a not unique key
** Set tmp to (previous record count) * (records / combination)
*/
if (found_part & 1)
{
/*
** Assume that the first key part matches 1% of the file
** and that the hole key matches 10 (dupplicates) or 1
** (unique) records.
** Assume also that more key matches proportionally more
** records
** This gives the formula:
** records= (x * (b-a) + a*c-b)/(c-1)
**
** b = records matched by whole key
** a = records matched by first key part (10% of all records?)
** c = number of key parts in key
** x = used key parts (1 <= x <= c)
*/
double rec_per_key;
if (keyinfo->dupp_key)
rec_per_key=1;
if (!table->keyfile_info.rec_per_key ||
!(rec_per_key=(double) table->keyfile_info.rec_per_key[key]))
rec_per_key=(double) s->records/rec+1;
if (rec_per_key/(double) s->records >= 0.01)
tmp=rec_per_key;
else
{
double a=s->records*0.01;
tmp=(max_part_bit(found_part) * (rec_per_key - a) +
a*keyinfo->key_parts - rec_per_key)/(keyinfo->key_parts-1);
set_if_bigger(tmp,1.0);
}
records=(ulong) tmp;
if (table->used_keys & ((key_map) 1 << key))
{
/* we can use only index tree */
uint keys_per_block= table->keyfile_info.block_size/2/
table->key_info[key].key_length+1;
tmp=(tmp+keys_per_block-1)/keys_per_block;
}
tmp*=record_count;
}
else
tmp=best; // Do nothing
}
if (tmp < best)
{
best=tmp;
best_records=records;
best_key=start_key;
}
}
records=best_records;
}
if (records >= s->found_records || best > s->read_time)
{ // Check full join
if (s->on_expr)
{
tmp=s->found_records; // Can't use read cache
}
else
{
tmp=(double) s->read_time;
/* Calculate time to read trough cache */
tmp*=(1.0+floor((double) cache_record_length(join,index)*
record_count/(double) join_buff_size));
}
if (best == DBL_MAX ||
(tmp + record_count/10.0*s->found_records <
best + record_count/10.0*records))
{
best=tmp;
records=s->found_records;
best_key=0;
}
}
join->positions[index].records_read=(double) records;
join->positions[index].key=best_key;
join->positions[index].table= s;
join->index[s->table->tablenr]= (table_map) 1L << index;
if (!best_key && index == join->const_tables &&
s->table == join->sort_by_table)
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 ||
best_read_time > current_read_time ||
index == join->const_tables && s->table == join->sort_by_table)
{
if (best_record_count >= current_record_count &&
best_read_time >= current_read_time &&
(!(s->key_dependent & rest_tables) || records < 2.0))
{
best_record_count=current_record_count;
best_read_time=current_read_time;
}
swap(JOIN_TAB*,join->best_ref[index],*pos);
find_best(join,rest_tables & ~real_table_bit,index+1,
current_record_count,current_read_time);
swap(JOIN_TAB*,join->best_ref[index],*pos);
}
if (join->select_options & SELECT_STRAIGHT_JOIN)
break; // Don't test all combinations
}
}
}
/*
** Find how much space the prevous read not const tables takes in cache
*/
static uint
cache_record_length(JOIN *join,uint index)
{
uint length;
JOIN_TAB **pos,**end;
THD *thd=current_thd;
length=0;
for (pos=join->best_ref+join->const_tables,end=join->best_ref+index ;
pos != end ;
pos++)
{
JOIN_TAB *join_tab= *pos;
if (!join_tab->used_fieldlength)
{ /* Not calced yet */
uint null_fields,blobs,fields,rec_length;
null_fields=blobs=fields=rec_length=0;
Field **f_ptr,*field;
for (f_ptr=join_tab->table->field ; (field= *f_ptr) ; f_ptr++)
{
if (field->query_id == thd->query_id)
{
uint flags=field->flags;
fields++;
rec_length+=field->pack_length();
if (flags & BLOB_FLAG)
blobs++;
if (!(flags & NOT_NULL_FLAG))
null_fields++;
}
}
if (null_fields)
rec_length+=(join_tab->table->null_fields+7)/8;
if (join_tab->table->maybe_null)
rec_length+=sizeof(my_bool);
if (blobs)
{
uint blob_length=(uint) (join_tab->table->
keyfile_info.mean_rec_length-
(join_tab->table->reclength-
rec_length));
rec_length+=(uint) max(4,blob_length);
}
join_tab->used_fields=fields;
join_tab->used_fieldlength=rec_length;
join_tab->used_blobs=blobs;
}
length+=join_tab->used_fieldlength;
}
return length;
}
static double
prev_record_reads(JOIN *join,table_map found_ref)
{
double found=1.0;
for (POSITION *pos=join->positions ; found_ref ; pos++,found_ref>>=1)
{
if ((found_ref & 1) || !pos->key)
found*=pos->records_read;
}
return found;
}
/*****************************************************************************
** Set up join struct according to best position.
** Change tablenr to new order
*****************************************************************************/
static void
get_best_combination(JOIN *join)
{
uint i,ref_count,key,length,tablenr;
table_map used_tables;
TABLE *table;
JOIN_TAB *join_tab,*j;
KEYUSE *keyuse;
KEY *keyinfo;
uint table_count;
table_count=join->tables;
join->join_tab=join_tab=(JOIN_TAB*) sql_alloc(sizeof(JOIN_TAB)*table_count);
join->const_tables=0; /* for checking */
join->full_join=0;
used_tables=0;
for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++)
{
TABLE *form;
*j= *join->best_positions[tablenr].table;
used_tables|= j->table->map;
form=join->table[tablenr]=j->table;
form->reginfo.ref_fields=0;
form->reginfo.ref_key= -1;
j->info=0; // For describe
if (j->type == JT_SYSTEM)
{
j->table->const_table=1;
if (join->const_tables == tablenr)
join->const_tables++;
continue;
}
if (!j->keys || !(keyuse= join->best_positions[tablenr].key))
{
j->type=JT_ALL;
if (tablenr != join->const_tables)
join->full_join=1;
}
else
{
uint keyparts;
/*
** Use best key from find_best
*/
table=j->table;
key=keyuse->key;
keyparts=length=0;
keyinfo=table->key_info+key;
do
{
if ((!keyuse->field ||
(used_tables & keyuse->field->table->map)))
{
if (keyparts == keyuse->keypart)
{
keyparts++;
length+=keyinfo->key_part[keyuse->keypart].length;
}
}
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
/* set up fieldref */
keyinfo=table->key_info+key;
form->reginfo.ref_fields=keyparts;
form->reginfo.ref_length=length;
form->reginfo.ref_key=(int) key;
form->reginfo.key_buff= (byte*) sql_calloc(length);
ref_count=0;
keyuse=join->best_positions[tablenr].key;
REF_FIELD *ref_field=form->reginfo.ref_field;
for (i=0 ; i < keyparts ; keyuse++,ref_field++,i++)
{
while (keyuse->keypart != i ||
(keyuse->field &&
!(used_tables & keyuse->field->table->map)))
keyuse++; /* Skipp other parts */
if (!(ref_field->field=keyuse->field))
{ // Compare against constant
Field *field=keyinfo->key_part[i].field;
store_val_in_field(field,keyuse->val);
ref_field->length=field->pack_length();
ref_field->ptr=(char*) sql_alloc(ref_field->length);
field->get_image(ref_field->ptr,ref_field->length);
ref_field->field=field->new_field(0); // Make a new field for cmp()
ref_field->field->move_field(ref_field->ptr);
/* Make a nice field name for 'explain' */
ref_field->field->table_name=0;
ref_field->field->field_name= (char*) keyuse->val->full_name();
}
else
{
ref_count++;
ref_field->ptr=keyuse->field->ptr;
ref_field->length=form->key_info[key].key_part[i].length;
}
/* Fix last key part if last key is only a keypart */
ref_field->length=min(ref_field->length,length);
length-=ref_field->length;
}
if (j->type == JT_CONST)
{
j->table->const_table=1;
if (join->const_tables == tablenr)
join->const_tables++;
}
else if (keyinfo->dupp_key || keyparts != keyinfo->key_parts)
j->type=JT_REF; /* Must read with repeat */
else if (!ref_count)
{ /* Should never be reached */
j->type=JT_CONST; /* purecov: deadcode */
if (join->const_tables == tablenr)
join->const_tables++; /* purecov: deadcode */
}
else
j->type=JT_EQ_REF;
}
}
for (i=0 ; i < table_count ; i++)
{
join->table[i]->tablenr=i;
join->table[i]->map = (table_map) 1 << i;
join->map2table[i]=join->join_tab+i; // For MySQL 3.23
}
return;
}
/*
** This function is only called for const items on fields with are keys
** (NOT NULL fields*)
** returns 1 if there was some conversion made when the field was stored.
*/
bool
store_val_in_field(Field *field,Item *val)
{
if (val->null_value)
{
field->reset();
return 0;
}
THD *thd=current_thd;
ulong cuted_fields=thd->cuted_fields;
thd->count_cuted_fields=1;
if (val->result_type() == STRING_RESULT)
{
String *result=val->str((String*) 0); // Safe because it's a const
field->store(result->ptr(),result->length());
}
else if (val->result_type() == INT_RESULT)
field->store(val->val_int());
else
field->store(val->val());
current_thd->count_cuted_fields=0;
return cuted_fields != thd->cuted_fields;
}
static void
make_simple_join(JOIN *join,TABLE *tmp_table)
{
TABLE **tableptr;
JOIN_TAB *join_tab;
tableptr=(TABLE**) sql_alloc(sizeof(TABLE*));
join_tab=(JOIN_TAB*) sql_alloc(sizeof(JOIN_TAB));
join->join_tab=join_tab;
join->table=tableptr; tableptr[0]=tmp_table;
join->tables=1;
join->const_tables=0;
join->copy_field_count=join->field_count=join->sum_func_count=
join->func_count=0;
join->first_record=join->sort_and_group=0;
join->sum_funcs=0;
join->send_records=0L;
join->copy_field=0;
join_tab->cache.buff=0; /* No cacheing */
join_tab->table=tmp_table;
join_tab->select=0;
join_tab->quick=0;
join_tab->type= JT_ALL; /* Map through all records */
join_tab->keys= (uint) ~0; /* test everything in quick */
join_tab->info=0;
join_tab->on_expr=0;
tmp_table->status=0;
tmp_table->null_row=0;
join_tab->read_first_record= join_init_read_record;
}
static bool
make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
{
DBUG_ENTER("make_join_select");
if (select)
{
if (join->tables > 1)
cond->update_used_tables(); // Tablenr may have changed
{ // Check const tables
COND *const_cond=
make_cond_for_table(cond,((table_map) 1 << join->const_tables)-1L,0L);
DBUG_EXECUTE("where",print_where(const_cond,"constants"););
if (const_cond && !const_cond->val_int())
DBUG_RETURN(1); // Impossible const condition
}
select->const_tables=((table_map) 1L << join->const_tables)-1L;
for (uint i=join->const_tables ; i < join->tables ; i++)
{
JOIN_TAB *tab=join->join_tab+i;
COND *tmp=make_cond_for_table(cond,((table_map) 1L << (i+1))-1L,
(table_map) 1L << i);
if (tmp)
{
DBUG_EXECUTE("where",print_where(tmp,tab->table->table_name););
SQL_SELECT *sel=tab->select=(SQL_SELECT*)
sql_memdup((gptr) select, sizeof(SQL_SELECT));
sel->cond=tmp;
sel->head=sel->tables[i];
if (tab->quick && tab->needed_reg == 0)
{
sel->quick=tab->quick; // Use value from get_quick_...
sel->quick_keys=sel->needed_reg=0;
tab->quick=0;
}
else
{
delete tab->quick;
tab->quick=0;
}
uint ref_key=(uint) select->head->reginfo.ref_key+1;
if (i == join->const_tables && ref_key)
{
if (!sel->quick &&
sel->test_quick_select(ref_key,0L,join->thd->select_limit) < 0)
DBUG_RETURN(1); // Impossible range
}
else if (tab->type == JT_ALL)
{
if (!sel->quick &&
sel->test_quick_select(tab->keys, ((table_map) 1L << i) -1L,
join->thd->select_limit) < 0)
DBUG_RETURN(1); // Impossible range
if (sel->quick_keys | sel->needed_reg)
{
tab->keys=sel->quick_keys | sel->needed_reg;
tab->use_quick= (sel->needed_reg &&
(!select->quick_keys ||
(select->quick &&
(select->quick->records >= 100L)))) ?
2 : 1;
sel->read_tables=PREV_BITS(i);
}
if (i != join->const_tables && tab->use_quick != 2)
{ /* Read with cache */
if ((tmp=make_cond_for_table(cond,
(PREV_BITS(join->const_tables) |
((table_map) 1L << i)),
(table_map) 1L << i)))
{
DBUG_EXECUTE("where",print_where(tmp,"cache"););
tab->cache.select=(SQL_SELECT*) sql_memdup((gptr) sel,
sizeof(SQL_SELECT));
tab->cache.select->cond=tmp;
tab->cache.select->read_tables=PREV_BITS(join->const_tables);
}
}
}
}
}
}
DBUG_RETURN(0);
}
static void
make_join_readinfo(JOIN *join,uint options)
{
uint i;
for (i=join->const_tables ; i < join->tables ; i++)
{
JOIN_TAB *tab=join->join_tab+i;
TABLE *table=tab->table;
tab->read_record.form= table;
tab->next_select=sub_select; /* normal select */
switch (tab->type) {
case JT_SYSTEM: // Only happens with left join
table->status=STATUS_NO_RECORD;
tab->read_first_record= join_read_system;
tab->read_record.read_record= join_no_more_records;
break;
case JT_CONST: // Only happens with left join
table->status=STATUS_NO_RECORD;
tab->read_first_record= join_read_const;
tab->read_record.read_record= join_no_more_records;
break;
case JT_EQ_REF:
table->status=STATUS_NO_RECORD;
tab->read_first_record= join_read_key;
tab->read_record.read_record= join_no_more_records;
if (table->used_keys & ((key_map) 1 << table->reginfo.ref_key))
{
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
break;
case JT_REF:
table->status=STATUS_NO_RECORD;
tab->read_first_record= join_read_always_key;
tab->read_record.read_record= join_read_next;
if (table->used_keys & ((key_map) 1 << table->reginfo.ref_key))
{
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
break;
case JT_ALL:
/*
** if previous table use cache
*/
table->status=STATUS_NO_RECORD;
if (i != join->const_tables && (options & SELECT_USE_CACHE) &&
tab->use_quick != 2 && !tab->on_expr)
{
if ((options & SELECT_DESCRIBE) ||
!join_init_cache(join->thd,join->join_tab+join->const_tables,
i-join->const_tables))
{
tab[-1].next_select=sub_select_cache; /* Patch previous */
}
}
/* These init changes read_record */
if (tab->use_quick == 2)
tab->read_first_record= join_init_quick_read_record;
else
{
tab->read_first_record= join_init_read_record;
if (tab->select && tab->select->quick &&
table->used_keys & ((key_map) 1 << tab->select->quick->index))
{
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
else if (table->used_keys)
{ // Only read index tree
tab->index=find_shortest_key(table, table->used_keys);
tab->read_first_record= join_init_read_first_with_key;
tab->type=JT_NEXT; // Read with ha_rfirst(), ha_rnext()
}
}
break;
default:
DBUG_PRINT("error",("Table type %d found",tab->type)); /* purecov: deadcode */
break; /* purecov: deadcode */
case JT_UNKNOWN:
case JT_MAYBE_REF:
abort(); /* purecov: deadcode */
}
}
join->join_tab[join->tables-1].next_select=0; /* Set by do_select */
}
static void
join_free(JOIN *join)
{
JOIN_TAB *tab,*end;
if (join->table)
{
/* only sorted table is cached */
if (join->tables > join->const_tables)
free_io_cache(join->table[join->const_tables]);
for (tab=join->join_tab,end=tab+join->tables ; tab != end ; tab++)
{
delete tab->select;
delete tab->quick;
x_free(tab->cache.buff);
end_read_record(&tab->read_record);
if (tab->table && tab->table->key_read)
{
tab->table->key_read=0;
ha_extra(tab->table,HA_EXTRA_NO_KEYREAD);
}
}
}
// We are not using tables anymore
// Unlock all tables. We may be in a an INSERT .... SELECT statement.
if (join->lock)
{
mysql_unlock_read_tables(join->lock); // Don't free join->lock
join->lock=0;
}
join->group_fields.delete_elements();
join->copy_funcs.delete_elements();
delete [] join->copy_field;
join->copy_field=0;
}
/*****************************************************************************
** Remove the following expressions from ORDER BY and GROUP BY:
** Constant expressions
** Expression that only uses tables that are of type EQ_REF and the reference
** is in the ORDER list or if all referee tables are of the above types.
*****************************************************************************/
static bool
eq_ref_table(JOIN *join, ORDER *start_order, JOIN_TAB *tab)
{
if (tab->cached_eq_ref_table) // If cached
return tab->eq_ref_table;
tab->cached_eq_ref_table=1;
for (;;)
{
if (tab->type == JT_CONST)
return (tab->eq_ref_table=1); // We can skip this /* purecov: inspected */
if (tab->type != JT_EQ_REF)
return (tab->eq_ref_table=0); // We must use this
REF_FIELD *ref_field=tab->table->reginfo.ref_field;
REF_FIELD *end=ref_field+tab->table->reginfo.ref_fields;
for (; ref_field != end ; ref_field++)
{
if (ref_field->field->table)
{ // Not a const ref
ORDER *order;
for (order=start_order ; order ; order=order->next)
{
if (order->item[0]->type() == Item::FIELD_ITEM &&
((Item_field*) order->item[0])->field == ref_field->field)
break;
}
if (order)
break; // Used in ORDER BY
if (!eq_ref_table(join,start_order,
join->map2table[ref_field->field->table->tablenr]))
return (tab->eq_ref_table=0);
}
}
return tab->eq_ref_table=1;
}
}
static bool
only_eq_ref_tables(JOIN *join,ORDER *order,table_map tables)
{
if (specialflag & SPECIAL_SAFE_MODE)
return 0; // skip this optimize /* purecov: inspected */
for (JOIN_TAB **tab=join->map2table ; tables ; tab++, tables>>=1)
{
if (tables & 1 && !eq_ref_table(join, order, *tab))
return 0;
}
return 1;
}
/*
** simple_order is set to 1 if sort_order only uses fields from head table
** and the head table is not a LEFT JOIN table
*/
static ORDER *
remove_const(JOIN *join,ORDER *first_order,bool *simple_order)
{
ORDER *order,**prev_ptr;
table_map first_table= (table_map) 1L << join->const_tables;
table_map not_const_tables= ~(first_table-1L);
table_map ref;
prev_ptr= &first_order;
*simple_order= (join->tables != join->const_tables &&
join->join_tab[join->const_tables].on_expr ? 0 : 1);
/* NOTE: A variable of not_const_tables ^ first_table; breaks gcc 2.7 */
DBUG_ENTER("remove_const");
for (order=first_order; order ; order=order->next)
{
order->item[0]->update_used_tables();
if (order->item[0]->with_sum_func)
*simple_order=0; // Must do a temp table to sort
else if (!(order->item[0]->used_tables() & not_const_tables))
continue; // skipp const item
else if ((ref=order->item[0]->used_tables() &
(not_const_tables ^ first_table)))
{
if (only_eq_ref_tables(join,first_order,ref))
continue;
*simple_order=0; // Must do a temp table to sort
}
*prev_ptr= order; // use this entry
prev_ptr= &order->next;
}
*prev_ptr=0;
DBUG_PRINT("exit",("simple_order: %d",(int) *simple_order));
DBUG_RETURN(first_order);
}
static void
return_zero_rows(select_result *result,TABLE_LIST *tables,List<Item> &fields,
bool send_row, uint select_options,const char *info,
Item *having)
{
DBUG_ENTER("return_zero_rows");
if (select_options & SELECT_DESCRIBE)
{
describe_info(info);
DBUG_VOID_RETURN;
}
else if (send_row)
{
for (TABLE_LIST *table=tables; table ; table=table->next)
table->table->null_row=1; // All fields are NULL
if (having && having->val_int() == 0.0)
send_row=0;
}
if (!tables || !(result->send_fields(fields,1)))
{
if (send_row)
result->send_data(fields);
if (tables) // Not from do_select()
result->send_eof();
}
DBUG_VOID_RETURN;
}
static void clear_tables(JOIN *join)
{
for (uint i=0 ; i < join->tables ; i++)
join->table[i]->null_row=1; // All fields are NULL
}
/*****************************************************************************
** Make som simple condition optimization:
** If there is a test 'field = const' change all refs to 'field' to 'const'
** Remove all dummy tests 'item = item', 'const op const'.
** Remove all 'item is NULL', when item can never be null!
** item->marker should be 0 for all items on entry
** Return in cond_value FALSE if condition is impossible (1 = 2)
*****************************************************************************/
class COND_CMP :public Sql_alloc {
public:
Item *and_level;
Item_func *cmp_func;
COND_CMP(Item *a,Item_func *b) :and_level(a),cmp_func(b) {}
};
#ifdef __GNUC__
template class List<COND_CMP>;
template class List_iterator<COND_CMP>;
#endif
/*
** change field = field to field = const for each found field = const in the
** and_level
*/
static void
change_cond_ref_to_const(List<COND_CMP> *save_list,Item *and_father,
Item *cond, Item *field, Item *value)
{
if (cond->type() == Item::COND_ITEM)
{
bool and_level= ((Item_cond*) cond)->functype() ==
Item_func::COND_AND_FUNC;
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
Item *item;
while ((item=li++))
change_cond_ref_to_const(save_list,and_level ? cond : item, item,
field, value);
return;
}
if (cond->eq_cmp_result() == Item::COND_OK)
return; // Not a boolean function
Item_bool_func2 *func= (Item_bool_func2*) cond;
Item *left_item= func->arguments()[0];
Item *right_item= func->arguments()[1];
Item_func::Functype functype= func->functype();
if (right_item->eq(field) && left_item != value)
{
#ifdef DELETE_ITEMS
delete right_item;
#endif
func->arguments()[1] = value->new_item(); // QQ; Add test
func->update_used_tables();
if (functype == Item_func::EQ_FUNC && and_father != cond &&
!left_item->const_item())
{
cond->marker=1;
save_list->push_back(new COND_CMP(and_father,func));
}
func->cmp_type=item_cmp_type(func->arguments()[0]->result_type(),
func->arguments()[1]->result_type());
}
else if (left_item->eq(field) && right_item != value)
{
#ifdef DELETE_ITEMS
delete left_item;
#endif
func->arguments()[0] = value = value->new_item(); // QQ; Add test
func->update_used_tables();
if (functype == Item_func::EQ_FUNC && and_father != cond &&
!right_item->const_item())
{
func->arguments()[0] = func->arguments()[1]; // For easy check
func->arguments()[1] = value;
cond->marker=1;
save_list->push_back(new COND_CMP(and_father,func));
}
func->cmp_type=item_cmp_type(func->arguments()[0]->result_type(),
func->arguments()[1]->result_type());
}
}
static void
propagate_cond_constants(List<COND_CMP> *save_list,COND *and_level,COND *cond)
{
if (cond->type() == Item::COND_ITEM)
{
bool and_level= ((Item_cond*) cond)->functype() ==
Item_func::COND_AND_FUNC;
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
Item *item;
List<COND_CMP> save;
while ((item=li++))
{
propagate_cond_constants(&save,and_level ? cond : item, item);
}
if (and_level)
{ // Handle other found items
List_iterator<COND_CMP> cond_itr(save);
COND_CMP *cond_cmp;
while ((cond_cmp=cond_itr++))
if (!cond_cmp->cmp_func->arguments()[0]->const_item())
change_cond_ref_to_const(&save,cond_cmp->and_level,
cond_cmp->and_level,
cond_cmp->cmp_func->arguments()[0],
cond_cmp->cmp_func->arguments()[1]);
}
}
else if (and_level != cond && !cond->marker) // In a AND group
{
if (cond->type() == Item::FUNC_ITEM &&
((Item_func*) cond)->functype() == Item_func::EQ_FUNC)
{
Item_func_eq *func=(Item_func_eq*) cond;
bool left_const= func->arguments()[0]->const_item();
bool right_const=func->arguments()[1]->const_item();
if (!(left_const && right_const))
{
if (right_const)
{
func->arguments()[1]=resolve_const_item(func->arguments()[1],
func->arguments()[0]);
func->update_used_tables();
change_cond_ref_to_const(save_list,and_level,and_level,
func->arguments()[0],
func->arguments()[1]);
}
else if (left_const)
{
func->arguments()[0]=resolve_const_item(func->arguments()[0],
func->arguments()[1]);
func->update_used_tables();
change_cond_ref_to_const(save_list,and_level,and_level,
func->arguments()[1],
func->arguments()[0]);
}
}
}
}
}
static COND *
optimize_cond(COND *conds,Item::cond_result *cond_value)
{
if (!conds)
{
*cond_value= Item::COND_TRUE;
return conds;
}
/* change field = field to field = const for each found field = const */
DBUG_EXECUTE("where",print_where(conds,"original"););
propagate_cond_constants((List<COND_CMP> *) 0,conds,conds);
/*
** Remove all instances of item == item
** Remove all and-levels where CONST item != CONST item
*/
DBUG_EXECUTE("where",print_where(conds,"after const change"););
conds=remove_eq_conds(conds,cond_value) ;
DBUG_EXECUTE("info",print_where(conds,"after remove"););
return conds;
}
/*
** remove const and eq items. Return new item, or NULL if no condition
** cond_value is set to according:
** COND_OK query is possible (field = constant)
** COND_TRUE always true ( 1 = 1 )
** COND_FALSE always false ( 1 = 2 )
*/
static COND *
remove_eq_conds(COND *cond,Item::cond_result *cond_value)
{
if (cond->type() == Item::COND_ITEM)
{
bool and_level= ((Item_cond*) cond)->functype()
== Item_func::COND_AND_FUNC;
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
Item::cond_result tmp_cond_value;
*cond_value=Item::COND_UNDEF;
Item *item;
while ((item=li++))
{
Item *new_item=remove_eq_conds(item,&tmp_cond_value);
if (!new_item)
{
#ifdef DELETE_ITEMS
delete item; // This may be shared
#endif
li.remove();
}
else if (item != new_item)
{
#ifdef DELETE_ITEMS
delete item; // This may be shared
#endif
VOID(li.replace(new_item));
}
if (*cond_value == Item::COND_UNDEF)
*cond_value=tmp_cond_value;
switch (tmp_cond_value) {
case Item::COND_OK: // Not TRUE or FALSE
if (and_level || *cond_value == Item::COND_FALSE)
*cond_value=tmp_cond_value;
break;
case Item::COND_FALSE:
if (and_level)
{
*cond_value=tmp_cond_value;
return (COND*) 0; // Always false
}
break;
case Item::COND_TRUE:
if (!and_level)
{
*cond_value= tmp_cond_value;
return (COND*) 0; // Always true
}
break;
case Item::COND_UNDEF: // Impossible
break; /* purecov: deadcode */
}
}
if (!((Item_cond*) cond)->argument_list()->elements ||
*cond_value != Item::COND_OK)
return (COND*) 0;
if (((Item_cond*) cond)->argument_list()->elements == 1)
{ // Remove list
Item *item= ((Item_cond*) cond)->argument_list()->head();
((Item_cond*) cond)->argument_list()->empty();
return item;
}
}
else if (cond->type() == Item::FUNC_ITEM &&
((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC)
{
/*
** Handles this special case for some ODBC applications:
** The are requesting the row that was just updated with a auto_increment
** value with this construct:
**
** SELECT * from table_name where auto_increment_column IS NULL
** This will be changed to:
** SELECT * from table_name where auto_increment_column = LAST_INSERT_ID
*/
Item_func_isnull *func=(Item_func_isnull*) cond;
Item **args= func->arguments();
if (args[0]->type() == Item::FIELD_ITEM)
{
Field *field=((Item_field*) args[0])->field;
if (field->flags & AUTO_INCREMENT_FLAG && !field->table->maybe_null)
{
COND *new_cond;
if ((new_cond= new Item_func_eq(args[0],
new Item_int("last_insert_id()",
current_thd->insert_id(),
21))))
{
cond=new_cond;
cond->fix_fields(current_thd,0);
}
}
}
}
else if (cond->const_item())
{
*cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE;
return (COND*) 0;
}
else if ((*cond_value= cond->eq_cmp_result()) != Item::COND_OK)
{ // boolan compare function
Item *left_item= ((Item_func*) cond)->arguments()[0];
Item *right_item= ((Item_func*) cond)->arguments()[1];
if (left_item->eq(right_item))
return (COND*) 0; // Compare of identical items
}
*cond_value=Item::COND_OK;
return cond; /* Point at next and level */
}
/****************************************************************************
** Create a temp table according to a field list
** set distinct if duplicates could be removed
** given fields field pointers are changed to point at tmp_table
** for send_fields
****************************************************************************/
static TABLE *
create_tmp_table(THD *thd,JOIN *join,List<Item> &fields,ORDER *group,
bool distinct,bool save_sum_fields,bool allow_distinct_limit)
{
int error;
TABLE *table;
uint i,field_count,reclength,old_reclength,null_count,blob_count;
char *tmpname,path[FN_REFLEN];
bool save_new_field=0;
byte *pos;
uchar *null_flags;
Field **reg_field,**from_field;
Copy_field *copy=0;
TABLE *form;
N_RECINFO *recinfo,*start_recinfo;
KEY *keyinfo;
KEY_PART_INFO *key_part_info;
Item_result_field **copy_func;
DBUG_ENTER("create_tmp_table");
DBUG_PRINT("enter",("distinct: %d save_sum_fields: %d allow_distinct_limit: %d group: %d",
(int) distinct, (int) save_sum_fields,
(int) allow_distinct_limit,test(group)));
created_tmp_tables++;
sprintf(path,"%s/SQL%lx_%x",mysql_tmpdir,thd->query_id,thd->tmp_table++);
tmpname=sql_strdup(path);
if (group)
{
if (join->group_parts > ha_max_key_parts[DB_TYPE_ISAM] ||
join->group_length > ha_max_key_length[DB_TYPE_ISAM] ||
!join->quick_group)
group=0; // Key is too big
else for (ORDER *tmp=group ; tmp ; tmp=tmp->next)
(*tmp->item)->marker=4; // Store null in key
}
field_count=join->field_count+join->func_count+join->sum_func_count;
if (!my_multi_malloc(MYF(MY_WME),
&table,sizeof(TABLE),
&reg_field,sizeof(Field*)*(field_count+1),
&from_field,sizeof(Field*)*field_count,
&copy_func,sizeof(Item**)*(join->func_count+1),
&keyinfo,sizeof(KEY),
&key_part_info,sizeof(KEY_PART_INFO),
&start_recinfo,sizeof(*recinfo)*(field_count*2+3),
NullS))
{
DBUG_RETURN(NULL); /* purecov: inspected */
}
if (!(join->copy_field=copy=new Copy_field[field_count]))
{
my_free((gptr) table,MYF(0)); /* purecov: inspected */
DBUG_RETURN(NULL); /* purecov: inspected */
}
join->funcs=copy_func;
table->field=reg_field;
table->tmp_table = 1; /* Mark as tmp table */
table->tablenr=0; /* Always head table */
table->map=1;
table->group=0;
table->io_cache=0;
table->distinct=0;
table->db_stat=0;
table->record[0]=0;
table->io_cache=0;
table->used_keys=0;
/* make nisam database according to fields */
form= table;
bzero((char*) form,sizeof(*form));
bzero((char*) reg_field,sizeof(Field*)*(field_count+1));
bzero((char*) from_field,sizeof(Field*)*field_count);
form->field=reg_field;
form->real_name=tmpname;
table->table_name=base_name(tmpname);
form->reginfo.lock_type=TL_WRITE; /* Will be updated */
form->db_stat=HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
form->reginfo.ref_key= -1; /* Not using key read */
/* Calculate with type of fields we will need in heap table */
reclength=blob_count=null_count=field_count=0;
List_iterator<Item> li(fields);
Item *item;
while ((item=li++))
{
Item::Type type;
Item *org_field=item;
Field *new_field=0;
bool maybe_null=0;
if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
continue;
type=item->type();
if (type == Item::SUM_FUNC_ITEM)
{
if (item->const_item())
type=Item::CONST_ITEM;
else if (!group && !save_sum_fields)
{ /* Can't calc group yet */
save_new_field=1; // Save new field later
item=((Item_sum*) item)->item;
type=item->type();
}
}
switch (type) {
case Item::SUM_FUNC_ITEM:
{
Item_sum *item_sum=(Item_sum*) item;
maybe_null=item_sum->maybe_null;
switch (item_sum->sum_func()) {
case Item_sum::AVG_FUNC: /* Place for sum & count */
if (group)
new_field= new Field_string(sizeof(double)+sizeof(longlong),
maybe_null, item->name,form,1);
else
new_field=new Field_double(item_sum->max_length,maybe_null,
item->name, form, item_sum->decimals);
break;
case Item_sum::STD_FUNC: /* Place for sum & count */
if (group)
new_field= new Field_string(sizeof(double)*2+sizeof(longlong),
maybe_null, item->name,form,1);
else
new_field=new Field_double(item_sum->max_length, maybe_null,
item->name,form,item_sum->decimals);
break;
case Item_sum::UNIQUE_USERS_FUNC:
new_field=new Field_long(9,maybe_null,item->name,form,1);
break;
default:
switch (item_sum->result_type()) {
case REAL_RESULT:
new_field=new Field_double(item_sum->max_length,maybe_null,
item->name,form,item_sum->decimals);
break;
case INT_RESULT:
new_field=new Field_longlong(item_sum->max_length,maybe_null,
item->name,form);
break;
case STRING_RESULT:
new_field= new Field_string(item_sum->max_length,maybe_null,
item->name,form,item->binary);
break;
}
break;
}
item_sum->result_field=new_field; /* Save result of func here */
break;
}
case Item::FIELD_ITEM:
{
Field *org_field=((Item_field*) item)->field;
from_field[field_count]=org_field;
new_field= org_field->new_field(form);
((Item_field*) item)->result_field= new_field;
if (org_field->flags & BLOB_FLAG)
blob_count++;
if ((maybe_null= org_field->maybe_null()))
new_field->flags&= ~NOT_NULL_FLAG; // Because of outer join
}
break;
case Item::FUNC_ITEM:
case Item::COND_ITEM:
case Item::FIELD_AVG_ITEM:
case Item::FIELD_STD_ITEM:
maybe_null=item->maybe_null;
switch (item->result_type()) {
case REAL_RESULT:
new_field=new Field_double(item->max_length,maybe_null,
item->name,form,item->decimals);
break;
case INT_RESULT:
new_field=new Field_longlong(item->max_length,maybe_null,
item->name,form);
break;
case STRING_RESULT:
new_field= new Field_string(item->max_length,maybe_null,
item->name,form,item->binary);
break;
}
*(copy_func++) = (Item_result_field*) item; // Save for copy_funcs
((Item_result_field*) item)->result_field=new_field;
break;
default: // Dosen't have to be stored
if (org_field->type() == Item::SUM_FUNC_ITEM)
((Item_sum *) org_field)->result_field=0;
continue;
}
if (save_new_field)
{
save_new_field=0;
((Item_sum *) org_field)->result_field= new_field;
}
if (!new_field)
goto err; // Of OOM
reclength+=new_field->pack_length();
if (org_field->marker == 4 && maybe_null)
{
reclength++; // group maybe_null item
new_field->flags|= GROUP_FLAG;
}
*(reg_field++) =new_field;
field_count++;
if (maybe_null)
null_count++;
}
/* If result table is small; use a heap */
if (blob_count ||
(join->select_options & (OPTION_BIG_TABLES | SELECT_SMALL_RESULT)) ==
OPTION_BIG_TABLES)
form->db_type=DB_TYPE_ISAM;
else
form->db_type=DB_TYPE_HEAP;
form->blob_fields=blob_count;
if (form->db_type == DB_TYPE_ISAM && blob_count == 0)
reclength++; /* For delete link */
old_reclength=reclength;
reclength+=(null_count+7)/8;
if (!reclength)
reclength=1; // Dummy select
form->fields=field_count;
form->reclength=reclength;
if (!(form->record[0]= (byte *) my_malloc(reclength*3, MYF(MY_WME))))
goto err;
form->record[1]= form->record[0]+reclength;
form->record[2]= form->record[1]+reclength;
null_flags=(uchar*) form->record[0]+old_reclength;
copy_func[0]=0; // End marker
recinfo=start_recinfo;
pos=form->record[0];
if (form->db_type == DB_TYPE_ISAM && blob_count == 0)
{ // Room for delete link
form->record[0][0]=(byte) 254;
pos++;
recinfo->base.type=FIELD_NORMAL;
recinfo->base.length=1;
recinfo++;
}
null_count=0;
for (i=0,reg_field=form->field; i < field_count; i++,reg_field++,recinfo++)
{
Field *field= *reg_field;
if (!(field->flags & NOT_NULL_FLAG))
{
if (field->flags & GROUP_FLAG)
{
*pos++=0; // Null is stored here
recinfo->base.length=1;
recinfo->base.type=FIELD_NORMAL;
recinfo++;
}
field->move_field((char*) pos,null_flags+null_count/8,
1 << (null_count & 7));
null_count++;
}
else
field->move_field((char*) pos,(uchar*) 0,0);
field->reset();
if (from_field[i])
{ /* Not a formula Item */
copy->set(field,from_field[i],save_sum_fields);
copy++;
}
uint length=field->pack_length();
pos+= length;
/* Make entry for create table */
recinfo->base.length=length;
if (field->flags & BLOB_FLAG)
recinfo->base.type= (int) FIELD_BLOB;
else if (!field->zero_pack() &&
(field->type() == FIELD_TYPE_STRING ||
field->type() == FIELD_TYPE_VAR_STRING) &&
length >= 10 && blob_count)
recinfo->base.type=FIELD_SKIPP_ENDSPACE;
else
recinfo->base.type=FIELD_NORMAL;
}
if (null_count)
{
recinfo->base.type=FIELD_NORMAL;
recinfo->base.length=(null_count+7)/8;
recinfo++;
}
recinfo->base.type=(int) FIELD_LAST;
join->copy_field_count=(uint) (copy - join->copy_field);
bfill(pos,(null_count+7)/8,255); // Set null fields
store_record(form,2); // Make empty default record
form->max_records=tmp_table_size/form->reclength;
set_if_bigger(form->max_records,1); // For dummy start options
if (group)
{
DBUG_PRINT("info",("Creating group key in temporary table"));
byte *group_buff;
table->group=group; /* Table is grouped by key */
join->group_buff=(byte*) sql_alloc(join->group_length);
key_part_info=(KEY_PART_INFO*) sql_alloc(sizeof(KEY_PART_INFO)*
join->group_parts);
if (!key_part_info)
goto err; /* purecov: inspected */
form->keys=1;
form->key_info=keyinfo;
keyinfo->key_part=key_part_info;
keyinfo->dupp_key=0;
keyinfo->usable_key_parts=keyinfo->key_parts= join->group_parts;
keyinfo->key_length=0;
group_buff=join->group_buff;
for (; group ; group=group->next,key_part_info++)
{
Field *field=(*group->item)->tmp_table_field();
bool maybe_null;
key_part_info->field= field;
key_part_info->offset= field->offset();
key_part_info->length= field->pack_length();
group->buff=(char*) group_buff;
if (!(group->field=field->new_field(form)))
goto err; /* purecov: inspected */
if ((maybe_null=(*group->item)->maybe_null))
{ // Here is the null marker
*group_buff= 0; // Init null byte
key_part_info->offset--;
key_part_info->length++;
}
group->field->move_field((char*) group_buff + (maybe_null ? 1 : 0),
(uchar*) 0,0);
key_part_info->key_type=
(field->key_type() != HA_KEYTYPE_TEXT ? FIELDFLAG_BINARY : 0);
keyinfo->key_length+= key_part_info->length;
group_buff+= key_part_info->length;
}
}
if (distinct && !group &&
reclength < ha_max_key_length[form->db_type] &&
(reclength < 256 || (join->select_options & SELECT_SMALL_RESULT) ||
allow_distinct_limit && thd->select_limit < form->max_records))
{
/* This should be fixed to have a key part per field */
if (distinct && allow_distinct_limit)
{
set_if_smaller(form->max_records,thd->select_limit);
join->end_write_records=thd->select_limit;
}
else
join->end_write_records= HA_POS_ERROR;
table->distinct=distinct;
form->keys=1;
form->key_info=keyinfo;
keyinfo->key_parts=1;
keyinfo->dupp_key=0;
keyinfo->key_length=(uint16) reclength;
keyinfo->key_part=key_part_info;
keyinfo->name="tmp";
key_part_info->field= *table->field;
key_part_info->offset= 0;
key_part_info->length= reclength;
key_part_info->key_type=FIELDFLAG_BINARY;
}
if (thd->fatal_error)
goto err; // End of memory /* purecov: inspected */
if (form->db_type == DB_TYPE_ISAM)
{
N_KEYDEF keydef;
if (form->keys)
{ // Get keys for ni_create
keydef.base.flag=HA_NOSAME;
keydef.base.keysegs= keyinfo->key_parts;
for (i=0; i < keydef.base.keysegs ; i++)
{
keydef.seg[i].base.flag=0; // No packing
keydef.seg[i].base.type=
((keyinfo->key_part[i].key_type & FIELDFLAG_BINARY) ?
HA_KEYTYPE_BINARY : HA_KEYTYPE_TEXT);
keydef.seg[i].base.length=keyinfo->key_part[i].length;
keydef.seg[i].base.start=keyinfo->key_part[i].offset;
}
keydef.seg[i].base.type=HA_KEYTYPE_END;
}
if ((error=ni_create(tmpname,form->keys,&keydef,start_recinfo,0L,0L,0,0,
0L)))
{
ha_error(form,error,MYF(0)); /* purecov: inspected */
form->db_stat=0;
goto err;
}
form->db_record_offset=form->reclength;
}
else
form->db_record_offset=1;
if ((error=ha_open(form,tmpname,O_RDWR,HA_OPEN_WAIT_IF_LOCKED)))
{
ha_error(form,error,MYF(0)); /* purecov: inspected */
form->db_stat=0;
goto err;
}
VOID(ha_lock(form,F_WRLCK)); /* Single thread table */
VOID(ha_extra(form,HA_EXTRA_NO_READCHECK)); /* Not needed */
DBUG_RETURN(table);
err:
free_tmp_table(thd,table); /* purecov: inspected */
DBUG_RETURN(NULL); /* purecov: inspected */
}
static void
free_tmp_table(THD *thd, TABLE *entry)
{
char *save_proc_info;
DBUG_ENTER("free_tmp_table");
DBUG_PRINT("enter",("table: %s",entry->table_name));
save_proc_info=thd->proc_info;
thd->proc_info="removing tmp table";
if (entry->db_stat)
VOID(ha_close(entry));
if (!(test_flags & TEST_KEEP_TMP_TABLES) || entry->db_type == DB_TYPE_HEAP)
VOID(ha_fdelete(entry->db_type,entry->real_name));
my_free((gptr) entry->record[0],MYF(0));
free_io_cache(entry);
my_free((gptr) entry,MYF(0));
thd->proc_info=save_proc_info;
DBUG_VOID_RETURN;
}
/*****************************************************************************
** Make a join of all tables and write it on socket or to table
*****************************************************************************/
static int
do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
{
int error;
JOIN_TAB *join_tab;
int (*end_select)(struct st_join *,struct st_join_table *,bool);
DBUG_ENTER("do_select");
join->procedure=procedure;
/*
** Tell the client how many fields there are in a row
*/
if (!table)
join->result->send_fields(*fields,1);
else
{
VOID(ha_extra(table,HA_EXTRA_WRITE_CACHE));
restore_record(table,2); /* Make empty */
}
join->tmp_table=table; /* Save for easy recursion */
join->fields= fields;
/* Set up select_end */
if (table)
{
if (table->group)
{
DBUG_PRINT("info",("Using end_update"));
end_select=end_update;
}
else if (join->sort_and_group)
{
DBUG_PRINT("info",("Using end_write_group"));
end_select=end_write_group;
}
else
{
DBUG_PRINT("info",("Using end_write"));
end_select=end_write;
}
}
else
{
if (join->sort_and_group || (join->procedure &&
join->procedure->flags & PROC_GROUP))
end_select=end_send_group;
else
end_select=end_send;
}
join->join_tab[join->tables-1].next_select=end_select;
join_tab=join->join_tab+join->const_tables;
join->send_records=0;
if (join->tables == join->const_tables)
{
if (!(error=(*end_select)(join,join_tab,0)) || error == -3)
error=(*end_select)(join,join_tab,1);
}
else
{
error=sub_select(join,join_tab,0);
if (error >= 0)
error=sub_select(join,join_tab,1);
if (error == -3)
error=0; /* select_limit used */
}
if (!table)
{
if (error < 0)
join->result->send_error(0,NullS); /* purecov: inspected */
else
join->result->send_eof();
}
if (error >= 0)
{
DBUG_PRINT("info",("%ld records output",join->send_records));
}
if (table)
VOID(ha_extra(table,HA_EXTRA_NO_CACHE));
DBUG_RETURN(error < 0);
}
static int
sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{
int error;
if (end_of_records)
{
if ((error=flush_cacheed_records(join,join_tab,FALSE)) < 0)
return error; /* purecov: inspected */
return sub_select(join,join_tab,end_of_records);
}
if (join_tab->use_quick != 2 || test_if_quick_select(join_tab) <= 0)
{
if (join->thd->killed)
{
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; // Aborted by user /* purecov: inspected */
}
if (!store_record_in_cache(&join_tab->cache))
return 0; // There is more room in cache
return flush_cacheed_records(join,join_tab,FALSE);
}
if ((error=flush_cacheed_records(join,join_tab,TRUE)) < 0)
return error; /* purecov: inspected */
return sub_select(join,join_tab,end_of_records); /* Use ordinary select */
}
static int
sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{
int error;
READ_RECORD *info;
join_tab->table->null_row=0;
if (end_of_records)
return (*join_tab->next_select)(join,join_tab+1,end_of_records);
bool found=0;
if ((error=(*join_tab->read_first_record)(join_tab)) >= 0)
{
info= &join_tab->read_record;
do
{
if (join->thd->killed) // Aborted by user
{
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; /* purecov: inspected */
}
if (!error && (!join_tab->on_expr || join_tab->on_expr->val_int()))
{
found=1;
if (!(join_tab->select && join_tab->select->skipp_record()))
{
if ((error=(*join_tab->next_select)(join,join_tab+1,0)) < 0)
return error;
}
}
} while ((error=info->read_record(info)) <= 0);
if (error != HA_ERR_END_OF_FILE)
{
ha_error(info->form,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
}
if (!found && join_tab->on_expr)
{ // OUTER JOIN
join_tab->table->null_row=1; // For group by without error
restore_record(join_tab->table,2); // Make empty record
if (!(join_tab->select && join_tab->select->skipp_record()))
{
if ((error=(*join_tab->next_select)(join,join_tab+1,0)) < 0)
return error; /* purecov: inspected */
}
}
return 0;
}
static int
flush_cacheed_records(JOIN *join,JOIN_TAB *join_tab,bool skipp_last)
{
int error;
READ_RECORD *info;
if (!join_tab->cache.records)
return 0; /* Nothing to do */
if (skipp_last)
(void) store_record_in_cache(&join_tab->cache); // Must save this for later
if (join_tab->use_quick == 2)
{
if (join_tab->select->quick)
{ /* Used quick select last. reset it */
delete join_tab->select->quick;
join_tab->select->quick=0;
}
}
/* read through all records */
if ((error=join_init_read_record(join_tab)) < 0)
return 1; /* No records (not fatal) */
for (JOIN_TAB *tmp=join->join_tab; tmp != join_tab ; tmp++)
{
tmp->status=tmp->table->status;
tmp->table->status=0;
}
info= &join_tab->read_record;
do
{
if (join->thd->killed)
{
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; // Aborted by user /* purecov: inspected */
}
SQL_SELECT *select=join_tab->select;
if (!error && (!join_tab->cache.select ||
!join_tab->cache.select->skipp_record()))
{
uint i;
reset_cache(&join_tab->cache);
for (i=(join_tab->cache.records- (skipp_last ? 1 : 0)) ; i-- > 0 ;)
{
read_cacheed_record(join_tab);
if (!select || !select->skipp_record())
if ((error=(join_tab->next_select)(join,join_tab+1,0)) < 0)
return error; /* purecov: inspected */
}
}
} while ((error=info->read_record(info)) <= 0);
if (skipp_last)
read_cacheed_record(join_tab); /* Restore current record */
reset_cache(&join_tab->cache);
join_tab->cache.records=0; join_tab->cache.ptr_record= (uint) ~0;
if (error != HA_ERR_END_OF_FILE)
{
ha_error(info->form,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
for (JOIN_TAB *tmp2=join->join_tab; tmp2 != join_tab ; tmp2++)
tmp2->table->status=tmp2->status;
return 0;
}
/*****************************************************************************
** The different ways to read a record
*****************************************************************************/
static int
join_read_const_tables(JOIN *join)
{
uint i;
DBUG_ENTER("join_read_const_tables");
for (i=0 ; i < join->const_tables ; i++)
{
TABLE *form=join->table[i];
form->null_row=0;
form->status=STATUS_NO_RECORD;
if (join->join_tab[i].type == JT_SYSTEM)
{
if (join_read_system(join->join_tab+i))
{ // Info for DESCRIBE
join->join_tab[i].info="const row not found";
join->best_positions[i].records_read=0.0;
if (!form->outer_join)
DBUG_RETURN(1);
}
}
else
{
if (join_read_const(join->join_tab+i))
{
join->join_tab[i].info="unique row not found";
join->best_positions[i].records_read=0.0;
if (!form->outer_join)
DBUG_RETURN(1);
}
}
if (join->join_tab[i].on_expr && !form->null_row)
{
if ((form->null_row= test(join->join_tab[i].on_expr->val_int() == 0)))
restore_record(form,2);
}
if (!form->null_row)
form->maybe_null=0;
}
DBUG_RETURN(0);
}
static int
join_read_system(JOIN_TAB *tab)
{
TABLE *form= tab->table;
if (form->status & STATUS_GARBAGE) // If first read
{
if (ha_readfirst(form,form->record[0]))
{
form->null_row=1; // This is ok.
restore_record(form,2); // Make empty record
return -1;
}
store_record(form,1);
}
else if (!form->status) // Only happens with left join
restore_record(form,1); // restore old record
form->null_row=0;
return form->status ? -1 : 0;
}
static int
join_read_const(JOIN_TAB *tab)
{
int error;
TABLE *form= tab->table;
if (form->status & STATUS_GARBAGE) // If first read
{
cp_buffer_from_ref(form->reginfo.key_buff,
form->reginfo.ref_field,
form->reginfo.ref_length);
if ((error=ha_rkey(form,form->record[0],form->reginfo.ref_key,
form->reginfo.key_buff,
form->reginfo.ref_length,HA_READ_KEY_EXACT)))
{
#ifdef EXTRA_DEBUG
if (error != HA_ERR_KEY_NOT_FOUND)
sql_print_error("read_const: Got error %d when reading table",error);
#endif
form->null_row=1;
restore_record(form,2); // Make empty record
return -1;
}
store_record(form,1);
}
else if (!form->status) // Only happens with left join
restore_record(form,1); // restore old record
form->null_row=0;
return form->status ? -1 : 0;
}
static int
join_read_key(JOIN_TAB *tab)
{
int error;
TABLE *form= tab->table;
if (cmp_buffer_with_ref(form->reginfo.key_buff,
form->reginfo.ref_field,
form->reginfo.ref_length) ||
(form->status & (STATUS_GARBAGE | STATUS_NO_PARENT)))
{
error=ha_rkey(form,form->record[0],form->reginfo.ref_key,
form->reginfo.key_buff,
form->reginfo.ref_length,HA_READ_KEY_EXACT);
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_KEY_NOT_FOUND)
sql_print_error("read_key: Got error %d when reading table",error);
#endif
}
return form->status ? -1 : 0;
}
static int
join_read_always_key(JOIN_TAB *tab)
{
int error;
TABLE *form= tab->table;
cp_buffer_from_ref(form->reginfo.key_buff,
form->reginfo.ref_field,
form->reginfo.ref_length);
if ((error=ha_rkey(form,form->record[0],form->reginfo.ref_key,
form->reginfo.key_buff,
form->reginfo.ref_length,HA_READ_KEY_EXACT)))
{
#ifdef EXTRA_DEBUG
if (error != HA_ERR_KEY_NOT_FOUND)
sql_print_error("read_const: Got error %d when reading table",error);
#endif
return -1; /* purecov: inspected */
}
return 0;
}
/* ARGSUSED */
static int
join_no_more_records(READ_RECORD *info __attribute__((unused)))
{
return HA_ERR_END_OF_FILE;
}
static int
join_read_next(READ_RECORD *info)
{
int error;
TABLE *form= info->form;
if ((error=ha_rnext(form,form->record[0],form->reginfo.ref_key)) ||
key_cmp(form,form->reginfo.key_buff,(uint) form->reginfo.ref_key,
form->reginfo.ref_length))
{
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_END_OF_FILE)
sql_print_error("read_next: Got error %d when reading table",error);
#endif
form->status= STATUS_GARBAGE;
return HA_ERR_END_OF_FILE;
}
return 0;
}
static int
join_init_quick_read_record(JOIN_TAB *tab)
{
if (test_if_quick_select(tab) == -1)
return -1; /* No possible records */
return join_init_read_record(tab);
}
static int
test_if_quick_select(JOIN_TAB *tab)
{
delete tab->select->quick;
tab->select->quick=0;
return tab->select->test_quick_select(tab->keys,0L,HA_POS_ERROR);
}
static int
join_init_read_record(JOIN_TAB *tab)
{
int result;
if (tab->select && tab->select->quick)
tab->select->quick->reset();
init_read_record(&tab->read_record, tab->table, tab->select);
result=(*tab->read_record.read_record)(&tab->read_record);
return result == HA_ERR_END_OF_FILE ? -1 : (result ? 1 : 0);
}
static int
join_init_read_first_with_key(JOIN_TAB *tab)
{
int error;
TABLE *table=tab->table;
if (!table->key_read && (table->used_keys & ((key_map) 1 << tab->index)))
{
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
tab->table->status=0;
tab->read_record.read_record=join_init_read_next_with_key;
tab->read_record.form=table;
tab->read_record.index=tab->index;
tab->read_record.record=table->record[0];
error=ha_rfirst(tab->table,tab->table->record[0],tab->index);
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
sql_print_error("read_first_with_key: Got error %d when reading table",error);
#endif
return error;
}
static int
join_init_read_next_with_key(READ_RECORD *info)
{
int error=ha_rnext(info->form,info->record,info->index);
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_END_OF_FILE)
sql_print_error("read_next_with_key: Got error %d when reading table",error);
#endif
return error;
}
static int
join_init_read_last_with_key(JOIN_TAB *tab)
{
TABLE *table=tab->table;
int error;
if (!table->key_read && (table->used_keys & ((key_map) 1 << tab->index)))
{
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
tab->table->status=0;
tab->read_record.read_record=join_init_read_prev_with_key;
tab->read_record.form=table;
tab->read_record.index=tab->index;
tab->read_record.record=table->record[0];
error=ha_rlast(tab->table,tab->table->record[0],tab->index);
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_END_OF_FILE)
sql_print_error("read_first_with_key: Got error %d when reading table",error);
#endif
return error;
}
static int
join_init_read_prev_with_key(READ_RECORD *info)
{
int error=ha_rprev(info->form,info->record,info->index);
#ifdef EXTRA_DEBUG
if (error && error != HA_ERR_END_OF_FILE)
sql_print_error("read_prev_with_key: Got error %d when reading table",error);
#endif
return error;
}
/*****************************************************************************
** The different end of select functions
*****************************************************************************/
/* ARGSUSED */
static int
end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
if (!end_of_records)
{
int error;
if (join->procedure)
error=join->procedure->send_row(*join->fields);
else
{
if (join->having && join->having->val_int() == 0.0)
return 0; // Didn't match having
error=join->result->send_data(*join->fields);
}
if (error)
return -1; /* purecov: inspected */
if (++join->send_records >= join->thd->select_limit)
return -3; // Abort nicely
}
return 0;
}
/* ARGSUSED */
static int
end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
int index= -1;
if (!join->first_record || end_of_records ||
(index=test_if_group_changed(join->group_fields)) >= 0)
{
if (join->first_record || (end_of_records && !join->group))
{
if (join->procedure)
join->procedure->end_group();
if (index < (int) join->send_group_parts)
{
int error;
if (join->procedure)
error=join->procedure->send_row(*join->fields) ? 1 : 0;
else
{
if (!join->first_record)
clear_tables(join);
if (join->having && join->having->val_int() == 0.0)
error= -1; // Didn't satisfy having
else
error=join->result->send_data(*join->fields) ? 1 : 0;
}
if (error > 0)
return -1; /* purecov: inspected */
if (end_of_records)
return 0;
if (!error && ++join->send_records >= join->thd->select_limit)
return -3; /* Abort nicely */
}
}
else
{
if (end_of_records)
return 0;
join->first_record=1;
VOID(test_if_group_changed(join->group_fields));
}
if (index < (int) join->send_group_parts)
{
copy_fields(join);
init_sum_functions(join->sum_funcs);
if (join->procedure)
join->procedure->add();
return(0);
}
}
update_sum_func(join->sum_funcs);
if (join->procedure)
join->procedure->add();
return(0);
}
/* ARGSUSED */
static int
end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
TABLE *table=join->tmp_table;
int error;
if (join->thd->killed) // Aborted by user
{
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; /* purecov: inspected */
}
if (!end_of_records)
{
copy_fields(join);
copy_funcs(join->funcs);
if (!join->having || join->having->val_int())
{
if ((error=ha_write(table,table->record[0])))
{
if (error != HA_ERR_FOUND_DUPP_KEY || !table->distinct)
{
ha_error(table,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
}
else
{
if (++join->send_records >= join->end_write_records)
return -3;
}
}
}
return 0;
}
/* Group by searching after group record and updating it if possible */
/* ARGSUSED */
static int
end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
TABLE *table=join->tmp_table;
ORDER *group;
int error;
if (end_of_records)
return 0;
if (join->thd->killed) // Aborted by user
{
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; /* purecov: inspected */
}
copy_fields(join); // Groups are copied twice.
/* Make a key of group index */
for (group=table->group ; group ; group=group->next)
{
Item *item= *group->item;
item->save_org_in_field(group->field);
if (item->maybe_null)
group->buff[0]=item->null_value ? 0: 1; // Save reversed value
}
if (!ha_rkey(table,table->record[1],0,join->group_buff,0,
HA_READ_KEY_EXACT))
{ /* Update old record */
restore_record(table,1);
update_tmptable_sum_func(join->sum_funcs,table);
if ((error=ha_update(table,table->record[1],
table->record[0])))
{
ha_error(table,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
return 0;
}
/* The null bits are already set */
KEY_PART_INFO *key_part;
for (group=table->group,key_part=table->key_info[0].key_part;
group ;
group=group->next,key_part++)
memcpy(table->record[0]+key_part->offset, group->buff, key_part->length);
init_tmptable_sum_functions(join->sum_funcs);
copy_funcs(join->funcs);
if ((error=ha_write(table,table->record[0])))
{
ha_error(table,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
join->send_records++;
return 0;
}
/* ARGSUSED */
static int
end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
TABLE *table=join->tmp_table;
int error;
int index= -1;
if (join->thd->killed)
{ // Aborted by user
my_error(ER_SERVER_SHUTDOWN,MYF(0)); /* purecov: inspected */
return -2; /* purecov: inspected */
}
if (!join->first_record || end_of_records ||
(index=test_if_group_changed(join->group_fields)) >= 0)
{
if (join->first_record || (end_of_records && !join->group))
{
if (join->procedure)
join->procedure->end_group();
if (index < (int) join->send_group_parts)
{
if (!join->first_record)
clear_tables(join);
copy_sum_funcs(join->sum_funcs);
if (!join->having || join->having->val_int())
{
if ((error=ha_write(table,table->record[0])))
{
ha_error(table,error,MYF(0)); /* purecov: inspected */
return -1; /* purecov: inspected */
}
else
join->send_records++;
}
if (end_of_records)
return 0;
}
}
else
{
join->first_record=1;
VOID(test_if_group_changed(join->group_fields));
}
if (index < (int) join->send_group_parts)
{
copy_fields(join);
copy_funcs(join->funcs);
init_sum_functions(join->sum_funcs);
if (join->procedure)
join->procedure->add();
return(0);
}
}
update_sum_func(join->sum_funcs);
if (join->procedure)
join->procedure->add();
return(0);
}
/*****************************************************************************
** Remove calculation with tables that aren't yet read. Remove also tests
** against fields that are read through key.
** We can't remove tests that are made against columns which are stored
** in sorted order.
*****************************************************************************/
static bool test_if_ref(Item_field *left_item,Item *right_item)
{
Field *field=left_item->field;
REF_FIELD *ref_field=part_of_refkey(field->table,field);
if (ref_field)
{ // FIELD = FIELD
if (right_item->type() == Item::FIELD_ITEM &&
((Item_field *) right_item)->field == ref_field->field)
{ // may be removed
return ((Item_field *) right_item)->field->pack_length() ==
ref_field->length;
}
else if (right_item->const_item() && !ref_field->field->table_name)
{ // FIELD = constant
if (!field->table->const_table) // Don't change const table
{
// We can remove binary fields and numerical fields except float,
// as float comparison isn't 100 % secure
if (field->binary() &&
(field->type() != FIELD_TYPE_FLOAT ||
((Field_float*) field)->decimals == 0))
{
if (!store_val_in_field(field,right_item) &&
!field->cmp(ref_field->ptr))
return 1;
}
}
}
}
return 0; // keep it
}
static COND *
make_cond_for_table(COND *cond,table_map tables,table_map used_table)
{
if (used_table && !(cond->used_tables() & used_table))
return (COND*) 0; // Already checked
if (cond->type() == Item::COND_ITEM)
{
if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
{
Item_cond_and *new_cond=new Item_cond_and;
if (!new_cond)
return (COND*) 0; // OOM /* purecov: inspected */
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
Item *item;
while ((item=li++))
{
Item *fix=make_cond_for_table(item,tables,used_table);
if (fix)
new_cond->argument_list()->push_back(fix);
}
switch (new_cond->argument_list()->elements) {
case 0:
return (COND*) 0; // Always true
case 1:
return new_cond->argument_list()->head();
default:
new_cond->used_tables_cache=((Item_cond*) cond)->used_tables_cache &
tables;
return new_cond;
}
}
else
{ // Or list
Item_cond_or *new_cond=new Item_cond_or;
if (!new_cond)
return (COND*) 0; // OOM /* purecov: inspected */
List_iterator<Item> li(*((Item_cond*) cond)->argument_list());
Item *item;
while ((item=li++))
{
Item *fix=make_cond_for_table(item,tables,0L);
if (!fix)
return (COND*) 0; // Always true
new_cond->argument_list()->push_back(fix);
}
new_cond->used_tables_cache=((Item_cond_or*) cond)->used_tables_cache;
return new_cond;
}
}
/*
** Because the following test takes a while and it can be done
** table_count times, we mark each item that we have examined with the result
** of the test
*/
if (cond->marker == 3 || (cond->used_tables() & ~tables))
return (COND*) 0; // Can't check this yet
if (cond->marker == 2 || cond->eq_cmp_result() == Item::COND_OK)
return cond; // Not boolean op
if (((Item_func*) cond)->functype() == Item_func::EQ_FUNC)
{
Item *left_item= ((Item_func*) cond)->arguments()[0];
Item *right_item= ((Item_func*) cond)->arguments()[1];
if (left_item->type() == Item::FIELD_ITEM &&
test_if_ref((Item_field*) left_item,right_item))
{
cond->marker=3; // Checked when read
return (COND*) 0;
}
if (right_item->type() == Item::FIELD_ITEM &&
test_if_ref((Item_field*) right_item,left_item))
{
cond->marker=3; // Checked when read
return (COND*) 0;
}
}
cond->marker=2;
return cond;
}
static REF_FIELD *
part_of_refkey(TABLE *table,Field *field)
{
uint key,keypart;
key=(uint) table->reginfo.ref_key;
for (keypart=0 ; keypart < table->reginfo.ref_fields ; keypart++)
if (field->eq(table->key_info[key].key_part[keypart].field))
return &table->reginfo.ref_field[keypart];
return (REF_FIELD*) 0;
}
/*****************************************************************************
** Test if one can use the key to resolve ORDER BY
** Returns: 1 if key is ok.
** 0 if key can't be used
** -1 if reverse key can be used
*****************************************************************************/
static int test_if_order_by_key(ORDER *order, TABLE *table, uint index)
{
KEY_PART_INFO *key_part,*key_part_end;
key_part=table->key_info[index].key_part;
key_part_end=key_part+table->key_info[index].key_parts;
int reverse=0;
for (; order ; order=order->next)
{
Field *field=((Item_field*) (*order->item))->field;
int flag;
if (key_part == key_part_end || key_part->field != field)
return 0;
flag=(order->asc == test(key_part->key_part_flag == 0)) ? 1 : -1;
if (reverse && flag != reverse)
return 1;
reverse=flag;
key_part++;
}
return reverse;
}
static uint find_shortest_key(TABLE *table, key_map usable_keys)
{
uint min_length= (uint) ~0;
uint best= MAX_KEY;
for (uint nr=0; usable_keys ; usable_keys>>=1, nr++)
{
if (usable_keys & 1)
{
if (table->key_info[nr].key_length < min_length)
{
min_length=table->key_info[nr].key_length;
best=nr;
}
}
}
return best;
}
/*****************************************************************************
** If not selecting by given key, create a index how records should be read
** return: 0 ok
** -1 some fatal error
** 1 no records
*****************************************************************************/
static int
create_sort_index(JOIN_TAB *tab,ORDER *order,ha_rows select_limit)
{
SORT_FIELD *sortorder;
int ref_key;
uint length;
TABLE *table=tab->table;
SQL_SELECT *select=tab->select;
key_map usable_keys;
DBUG_ENTER("create_sort_index");
/* Check which keys can be used to resolve ORDER BY */
usable_keys= ~(key_map) 0;
for (ORDER *tmp_order=order; tmp_order ; tmp_order=tmp_order->next)
{
if ((*tmp_order->item)->type() != Item::FIELD_ITEM)
{
usable_keys=0;
break;
}
usable_keys&=((Item_field*) (*tmp_order->item))->field->part_of_key;
}
ref_key= -1;
if (table->reginfo.ref_key >= 0) // Constant range in WHERE
ref_key=table->reginfo.ref_key;
else if (select && select->quick) // Range found by opt_range
ref_key=select->quick->index;
if (ref_key >= 0)
{
/* Check if we get the rows in requested sorted order by using the key */
if ((usable_keys & ((key_map) 1 << ref_key)) &&
test_if_order_by_key(order,table,ref_key) == 1)
DBUG_RETURN(0); /* No need to sort */
}
else
{
/* check if we can use a key to resolve the group */
/* Tables using JT_NEXT are handled here */
uint nr;
key_map keys=usable_keys;
/*
If not used with LIMIT, only use keys if the whole query can be
resolved with a key; This is because filesort() is usually faster than
retrieving all rows through an index.
*/
if (select_limit >= table->keyfile_info.records)
keys&= table->used_keys;
for (nr=0; keys ; keys>>=1, nr++)
{
if (keys & 1)
{
int flag;
if ((flag=test_if_order_by_key(order,table,nr)))
{
tab->index=nr;
tab->read_first_record= (flag > 0 ? join_init_read_first_with_key:
join_init_read_last_with_key);
tab->type=JT_NEXT; // Read with ha_rfirst(), ha_rnext()
DBUG_RETURN(0);
}
}
}
/* Check if we can use only indexes to resolve the group by */
if (!select && usable_keys)
{
uint index=find_shortest_key(table,usable_keys);
/* Make a quick range over the key to force filesort to use it */
QUICK_RANGE *tmp=new QUICK_RANGE();
if (!tmp || !(select= new SQL_SELECT) ||
!(select->quick=new QUICK_SELECT(table,index,1)))
DBUG_RETURN(-1);
select->quick->ranges.push_front(tmp);
table->key_read=1;
ha_extra(table,HA_EXTRA_KEYREAD);
}
}
if (!(sortorder=make_unireg_sortorder(order,&length)))
DBUG_RETURN(-1); /* purecov: inspected */
table->io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
MYF(MY_FAE | MY_ZEROFILL));
table->status=0; // May be wrong if quick_select
// If table has a range, move it to select
if (select && !select->quick && table->reginfo.ref_key >= 0 && tab->quick)
{
select->quick=tab->quick;
tab->quick=0;
}
table->found_records=filesort(&table,sortorder,length,
select, 0L, select_limit);
delete select; // filesort did select
tab->select=0;
tab->type=JT_ALL; // Read with normal read_record
tab->read_first_record= join_init_read_record;
if (table->key_read) // Restore if we used indexes
{
table->key_read=0;
ha_extra(table,HA_EXTRA_NO_KEYREAD);
}
DBUG_RETURN(table->found_records == HA_POS_ERROR);
}
/*****************************************************************************
** Remove duplicates from tmp table
** Table is a locked single thread table
** fields is the number of fields to check (from the end)
*****************************************************************************/
static int
remove_duplicates(TABLE *entry,List<Item> &fields)
{
READ_RECORD info;
int error;
uint reclength,offset,field_count;
char *org_record,*new_record;
DBUG_ENTER("remove_duplicates");
entry->reginfo.lock_type=TL_WRITE;
VOID(ha_extra(entry,HA_EXTRA_NO_READCHECK));
/* Calculate how many saved fields there is in list */
field_count=0;
List_iterator<Item> it(fields);
Item *item;
while ((item=it++))
if (item->tmp_table_field())
field_count++;
if (!field_count)
{ // only const items
ha_reset(entry); // Remove all execpt first
if (!(error=ha_r_rnd(entry,entry->record[0],(byte*) 0)))
{
while ((error=ha_r_rnd(entry,entry->record[0],(byte*) 0)) <= 0)
(void) ha_delete(entry,entry->record[0]);
}
DBUG_RETURN(0);
}
offset=entry->field[entry->fields - field_count]->offset();
reclength=entry->reclength-offset;
org_record=(char*) entry->record[0]+offset;
new_record=(char*) entry->record[1]+offset;
free_io_cache(entry); // Safety
init_read_record(&info,entry,(SQL_SELECT *) 0);
while ((error=info.read_record(&info)) <= 0)
{
if (error == 0)
{
ha_info(entry,1);
memcpy(new_record,org_record,reclength);
while ((error=info.read_record(&info)) <= 0)
{
if (error == 0)
{
if (memcmp(org_record,new_record,reclength) == 0)
if ((error=ha_delete(entry,entry->record[0])))
{
ha_error(entry,error,MYF(0)); /* purecov: inspected */
error= -1; /* purecov: inspected */
goto end; /* purecov: inspected */
}
}
}
if (ha_r_rnd(entry,entry->record[0],entry->keyfile_info.ref.refpos))
{
error= -1; /* purecov: inspected */
goto end; /* purecov: inspected */
}
VOID(ha_extra(entry,HA_EXTRA_REINIT_CACHE));
}
}
if (error != HA_ERR_END_OF_FILE)
{
ha_error(entry,error,MYF(0)); /* purecov: inspected */
error= -1; /* purecov: inspected */
goto end; /* purecov: inspected */
}
error=0;
end:
end_read_record(&info);
DBUG_RETURN(error);
}
static SORT_FIELD *
make_unireg_sortorder(ORDER *order, uint *length)
{
uint count;
SORT_FIELD *sort,*pos;
DBUG_ENTER("make_unireg_sortorder");
count=0;
for (ORDER *tmp = order; tmp; tmp=tmp->next)
count++;
pos=sort=(SORT_FIELD*) sql_alloc(sizeof(SORT_FIELD)*(count+1));
for (;order;order=order->next,pos++)
{
pos->field=0; pos->item=0;
if (order->item[0]->type() == Item::FIELD_ITEM)
pos->field= ((Item_field*) (*order->item))->field;
else if (order->item[0]->type() == Item::SUM_FUNC_ITEM &&
!order->item[0]->const_item())
pos->field= ((Item_sum*) order->item[0])->tmp_table_field();
else if (order->item[0]->type() == Item::COPY_STR_ITEM)
{ // Blob patch
pos->item= ((Item_copy_string*) (*order->item))->item;
}
else
pos->item= *order->item;
pos->reverse=! order->asc;
}
*length=count;
DBUG_RETURN(sort);
}
/*****************************************************************************
** Fill join cache with packed records
** Records are stored in tab->cache.buffer and last record in
** last record is stored with pointers to blobs to support very big
** records
******************************************************************************/
static int
join_init_cache(THD *thd,JOIN_TAB *tables,uint table_count)
{
reg1 uint i;
uint length,blobs,size;
CACHE_FIELD *copy,**blob_ptr;
JOIN_CACHE *cache;
DBUG_ENTER("join_init_cache");
cache= &tables[table_count].cache;
cache->fields=blobs=0;
for (i=0 ; i < table_count ; i++)
{
cache->fields+=tables[i].used_fields;
blobs+=tables[i].used_blobs;
}
if (!(cache->field=(CACHE_FIELD*)
sql_alloc(sizeof(CACHE_FIELD)*(cache->fields+table_count*2)+(blobs+1)*
sizeof(CACHE_FIELD*))))
{
my_free((gptr) cache->buff,MYF(0)); /* purecov: inspected */
cache->buff=0; /* purecov: inspected */
DBUG_RETURN(1); /* purecov: inspected */
}
copy=cache->field;
blob_ptr=cache->blob_ptr=(CACHE_FIELD**)
(cache->field+cache->fields+table_count*2);
length=0;
for (i=0 ; i < table_count ; i++)
{
uint null_fields=0,used_fields;
Field **f_ptr,*field;
for (f_ptr=tables[i].table->field,used_fields=tables[i].used_fields ;
used_fields ;
f_ptr++)
{
field= *f_ptr;
if (field->query_id == thd->query_id)
{
used_fields--;
length+=field->fill_cache_field(copy);
if (copy->blob_field)
(*blob_ptr++)=copy;
if (field->maybe_null())
null_fields++;
copy++;
}
}
/* Copy null bits from table */
if (null_fields && tables[i].table->null_fields)
{ /* must copy null bits */
copy->str=(char*) tables[i].table->null_flags;
copy->length=(tables[i].table->null_fields+7)/8;
copy->strip=0;
copy->blob_field=0;
length+=copy->length;
copy++;
cache->fields++;
}
/* If outer join table, copy null_row flag */
if (tables[i].table->maybe_null)
{
copy->str= (char*) &tables[i].table->null_row;
copy->length=sizeof(tables[i].table->null_row);
copy->strip=0;
copy->blob_field=0;
length+=copy->length;
copy++;
cache->fields++;
}
}
cache->records=0; cache->ptr_record= (uint) ~0;
cache->length=length+blobs*sizeof(char*);
cache->blobs=blobs;
*blob_ptr=0; /* End sequentel */
size=max(join_buff_size,cache->length);
if (!(cache->buff=(uchar*) my_malloc(size,MYF(0))))
DBUG_RETURN(1); /* Don't use cache */ /* purecov: inspected */
cache->end=cache->buff+size;
reset_cache(cache);
DBUG_RETURN(0);
}
static ulong
used_blob_length(CACHE_FIELD **ptr)
{
uint length,blob_length;
for (length=0 ; *ptr ; ptr++)
{
(*ptr)->blob_length=blob_length=(*ptr)->blob_field->get_length();
length+=blob_length;
(*ptr)->blob_field->get_ptr(&(*ptr)->str);
}
return length;
}
static bool
store_record_in_cache(JOIN_CACHE *cache)
{
ulong length;
uchar *pos;
CACHE_FIELD *copy,*end_field;
bool last_record;
pos=cache->pos;
end_field=cache->field+cache->fields;
length=cache->length;
if (cache->blobs)
length+=used_blob_length(cache->blob_ptr);
if ((last_record=(length > (uint) (cache->end - pos))))
cache->ptr_record=cache->records;
/*
** There is room in cache. Put record there
*/
cache->records++;
for (copy=cache->field ; copy < end_field; copy++)
{
if (copy->blob_field)
{
if (last_record)
{
copy->blob_field->get_image((char*) pos,copy->length+sizeof(char*));
pos+=copy->length+sizeof(char*);
}
else
{
copy->blob_field->get_image((char*) pos,copy->length); // blob length
memcpy(pos+copy->length,copy->str,copy->blob_length); // Blob data
pos+=copy->length+copy->blob_length;
}
}
else
{
if (copy->strip)
{
char *str,*end;
for (str=copy->str,end= str+copy->length;
end > str && end[-1] == ' ' ;
end--) ;
length=(uint) (end-str);
memcpy(pos+1,str,length);
*pos=(uchar) length;
pos+=length+1;
}
else
{
memcpy(pos,copy->str,copy->length);
pos+=copy->length;
}
}
}
cache->pos=pos;
return last_record || (uint) (cache->end -pos) < cache->length;
}
static void
reset_cache(JOIN_CACHE *cache)
{
cache->record_nr=0;
cache->pos=cache->buff;
}
static void
read_cacheed_record(JOIN_TAB *tab)
{
uchar *pos;
uint length;
bool last_record;
CACHE_FIELD *copy,*end_field;
last_record=tab->cache.record_nr++ == tab->cache.ptr_record;
pos=tab->cache.pos;
for (copy=tab->cache.field,end_field=copy+tab->cache.fields ;
copy < end_field;
copy++)
{
if (copy->blob_field)
{
if (last_record)
{
copy->blob_field->set_image((char*) pos,copy->length+sizeof(char*));
pos+=copy->length+sizeof(char*);
}
else
{
copy->blob_field->set_ptr((char*) pos,(char*) pos+copy->length);
pos+=copy->length+copy->blob_field->get_length();
}
}
else
{
if (copy->strip)
{
memcpy(copy->str,pos+1,length=(uint) *pos);
memset(copy->str+length,' ',copy->length-length);
pos+=1+length;
}
else
{
memcpy(copy->str,pos,copy->length);
pos+=copy->length;
}
}
}
tab->cache.pos=pos;
return;
}
bool
cmp_buffer_with_ref(byte *buffer,REF_FIELD *fields,uint length)
{
reg3 bool flag;
reg2 uint tmp_length;
for (flag=0 ; length ; fields++, buffer+=tmp_length, length-=tmp_length)
{
tmp_length=fields->length;
#ifdef NOT_NEEDED
if (tmp_length > length)
tmp_length=length; /* purecov: deadcode */
#endif
if (flag || fields->field->cmp_image((char*) buffer,(size_t) tmp_length))
{
flag=1;
fields->field->get_image((char*) buffer,(size_t) tmp_length);
}
}
return flag;
}
static void
cp_buffer_from_ref(byte *buffer,REF_FIELD *fields,uint length)
{
reg3 uint tmp_length;
for (; length ; fields++, buffer+=tmp_length, length-=tmp_length)
{
tmp_length=fields->length;
fields->field->get_image((char*) buffer,(size_t) tmp_length);
}
}
/*****************************************************************************
** Group and order functions
*****************************************************************************/
/*
** Find order/group item in requested columns and change the item to point at
** it. If item doesn't exists, add it first in the field list
** Return 0 if ok.
*/
static int
find_order_in_list(THD *thd,TABLE_LIST *tables,ORDER *order,List<Item> &fields,
List<Item> &all_fields)
{
if ((*order->item)->type() == Item::INT_ITEM)
{ /* Order by position */
Item *item=0;
List_iterator<Item> li(fields);
for (uint count= (uint) ((Item_int*) (*order->item))->value ;
count-- && (item=li++) ;) ;
if (!item)
{
my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR),
MYF(0),(*order->item)->full_name(),
thd->where);
return 1;
}
order->item=li.ref();
return 0;
}
char *save_where=thd->where;
thd->where=0; // No error if not found
Item **item=find_item_in_list(*order->item,fields);
thd->where=save_where;
if (item)
{
order->item=item; // use it
return 0;
}
if ((*order->item)->fix_fields(thd,tables) || thd->fatal_error)
return 1; // Wrong field
all_fields.push_front(*order->item); // Add new field to field list
order->item=(Item**) all_fields.head_ref();
return 0;
}
/*
** Change order to point at item in select list. If item isn't a number
** and doesn't exits in the select list, add it the the field list.
*/
static int
setup_order(THD *thd,TABLE_LIST *tables,List<Item> &fields,
List<Item> &all_fields, ORDER *order)
{
thd->where="order clause";
for (; order; order=order->next)
{
if (find_order_in_list(thd,tables,order,fields,all_fields))
return 1;
}
return 0;
}
static int
setup_group(THD *thd,TABLE_LIST *tables,List<Item> &fields,
List<Item> &all_fields, ORDER *order, bool *hidden_group_fields)
{
*hidden_group_fields=0;
if (!order)
return 0; /* Everything is ok */
#ifdef RESTRICTED_GROUP
Item *item;
List_iterator<Item> li(fields);
while ((item=li++))
item->marker=0; /* Marker that field is not used */
#endif
uint org_fields=all_fields.elements;
thd->where="group statement";
for ( ; order; order=order->next)
{
if (find_order_in_list(thd,tables,order,fields,all_fields))
return 1;
(*order->item)->marker=1; /* Mark found */
if ((*order->item)->with_sum_func)
{
my_printf_error(ER_WRONG_GROUP_FIELD, ER(ER_WRONG_GROUP_FIELD),MYF(0),
(*order->item)->full_name());
return 1;
}
}
#ifdef RESTRICTED_GROUP
li.rewind();
while ((item=li++))
{
if (item->type() != Item::SUM_FUNC_ITEM && !item->marker)
{
my_printf_error(ER_WRONG_FIELD_WITH_GROUP,ER(ER_WRONG_FIELD_WITH_GROUP),
MYF(0),item->full_name());
return 1;
}
}
#endif
if (org_fields != all_fields.elements)
*hidden_group_fields=1; // group fields is not used
return 0;
}
/*
** Add fields with aren't used at start of field list. Return FALSE if ok
*/
static bool
setup_new_fields(THD *thd,TABLE_LIST *tables,List<Item> &fields,
List<Item> &all_fields, ORDER *new_field)
{
Item **item;
DBUG_ENTER("setup_new_fields");
thd->set_query_id=1; // Not really needed, but...
thd->where=0; // Don't give error
for ( ; new_field ; new_field=new_field->next)
{
if ((item=find_item_in_list(*new_field->item,fields)))
new_field->item=item; /* Change to shared Item */
else
{
thd->where="procedure list";
if ((*new_field->item)->fix_fields(thd,tables))
DBUG_RETURN(1); /* purecov: inspected */
new_field->free_me=TRUE;
all_fields.push_front(*new_field->item);
}
}
DBUG_RETURN(0);
}
/*
** Add all fields non const fields with dosen't exist in order to order
** If all fields are const return 0
*/
static ORDER *
add_all_fields_to_order(JOIN *join,ORDER *order_list,List<Item> &fields)
{
List_iterator<Item> li(fields);
Item *item;
ORDER *order,**prev;
bool found_item=0;
while ((item=li++))
item->marker=0; /* Marker that field is not used */
prev= &order_list;
for (order=order_list ; order; order=order->next)
{
(*order->item)->marker=1;
prev= &order->next;
}
li.rewind();
while ((item=li++))
{
if (item->const_item() || item->with_sum_func)
continue;
found_item=1;
if (!item->marker)
{
ORDER *ord=(ORDER*) sql_alloc(sizeof(ORDER));
ord->item=li.ref();
ord->asc=1;
ord->free_me=0;
*prev=ord;
prev= &ord->next;
}
}
*prev=0;
return found_item ? order_list : 0;
}
/*****************************************************************************
** Update join with count of the different type of fields
*****************************************************************************/
static void
count_field_types(JOIN *join,List<Item> &fields)
{
List_iterator<Item> li(fields);
Item *field;
join->field_count=join->sum_func_count=join->func_count=0;
join->quick_group=1;
while ((field=li++))
{
if (field->type() == Item::FIELD_ITEM)
join->field_count++;
else if (field->type() == Item::FUNC_ITEM ||
field->type() == Item::COND_ITEM ||
field->type() == Item::FIELD_AVG_ITEM ||
field->type() == Item::FIELD_STD_ITEM)
join->func_count++;
else if (field->type() == Item::SUM_FUNC_ITEM && ! field->const_item())
{
Item_sum *sum_item=(Item_sum*) field;
if (!sum_item->quick_group)
join->quick_group=0;
join->sum_func_count++;
if (sum_item->item->type() == Item::FUNC_ITEM)
join->func_count++;
}
}
}
/*
Return 1 if second is a subpart of first argument
If first parts has different direction, change it to second part
(group is sorted like order)
*/
static bool
test_if_subpart(ORDER *a,ORDER *b)
{
for (; a && b; a=a->next,b=b->next)
{
if (a->item == b->item)
a->asc=b->asc;
else
return 0;
}
return test(!b);
}
/*
Return table number if there is only one table in sort order
and group and order is compatible
else return ~0;
*/
static TABLE *
get_sort_by_table(ORDER *a,ORDER *b,TABLE_LIST *tables)
{
table_map map=0L;
DBUG_ENTER("get_sort_by_table");
if (!a)
a=b; // Only one need to be given
else if (!b)
b=a;
for (; a && b; a=a->next,b=b->next)
{
if (a->item != b->item)
DBUG_RETURN(0);
map|=a->item[0]->used_tables();
}
if (!map)
DBUG_RETURN(0);
for ( ; !(map & 1) ; map>>=1,tables=tables->next) ;
if (map != (table_map) 1)
DBUG_RETURN(0); // More than one table
DBUG_PRINT("exit",("sort by table: %d",tables->table->tablenr));
DBUG_RETURN(tables->table);
}
/* calc how big buffer we need for comparing group entries */
static void
calc_group_buffer(JOIN *join,ORDER *group)
{
uint key_length=0,parts=0;
for (; group ; group=group->next)
{
Field *field=(*group->item)->tmp_table_field();
if (field)
key_length+=field->pack_length();
else if ((*group->item)->result_type() == REAL_RESULT)
key_length+=sizeof(double);
else if ((*group->item)->result_type() == INT_RESULT)
key_length+=sizeof(longlong);
else
key_length+=(*group->item)->max_length;
parts++;
if ((*group->item)->maybe_null)
key_length++;
}
join->group_length=key_length;
join->group_parts=parts;
}
/*
** Get a list of buffers for saveing last group
** Groups are saved in reverse order for easyer check loop
*/
static void
alloc_group_fields(JOIN *join,ORDER *group)
{
if (group)
{
for (; group ; group=group->next)
join->group_fields.push_front(new_Item_buff(*group->item));
}
join->sort_and_group=1; /* Mark for do_select */
}
static int
test_if_group_changed(List<Item_buff> &list)
{
List_iterator<Item_buff> li(list);
int index= -1,i;
Item_buff *buff;
for (i=(int) list.elements-1 ; (buff=li++) ; i--)
{
if (buff->cmp())
index=i;
}
return index;
}
/*
** Setup copy_fields to save fields at start of new group
** Only FIELD_ITEM:s and FUNC_ITEM:s needs to be saved between groups.
** Change old item_field to use a new field with points at saved fieldvalue
** This function is only called before use of send_fields
*/
static void
setup_copy_fields(JOIN *join,List<Item> &fields)
{
Item *pos;
uint field_count;
List_iterator<Item> li(fields);
Copy_field *copy;
field_count=join->field_count;
copy=join->copy_field= new Copy_field[field_count];
join->copy_funcs.empty();
while ((pos=li++))
{
if (pos->type() == Item::FIELD_ITEM)
{
Item_field *item=(Item_field*) pos;
if (item->field->flags & BLOB_FLAG)
{
pos=new Item_copy_string(pos);
VOID(li.replace(pos));
join->copy_funcs.push_back(pos);
continue;
}
/* set up save buffer and change result_field to point at saved value */
Field *field= item->field;
item->result_field=field->new_field(field->table);
copy->set((char*) sql_alloc(field->pack_length()+1),
item->result_field);
item->result_field->move_field(copy->to_ptr,copy->to_null_ptr,1);
copy++;
}
else if ((pos->type() == Item::FUNC_ITEM ||
pos->type() == Item::COND_ITEM) &&
!pos->with_sum_func)
{ // Save for send fields
pos=new Item_copy_string(pos);
VOID(li.replace(pos));
join->copy_funcs.push_back(pos);
}
}
join->copy_field_count=(uint) (copy - join->copy_field);
}
/*
** Copy fields and null values between two tables
*/
static void
copy_fields(JOIN *join)
{
Copy_field *ptr=join->copy_field,*end=ptr+join->copy_field_count;
for ( ; ptr != end; ptr++)
(*ptr->do_copy)(ptr);
List_iterator<Item> it(join->copy_funcs);
Item_copy_string *item;
while ((item = (Item_copy_string*) it++))
{
item->copy();
}
}
/*****************************************************************************
** Make an array of pointer to sum_functions to speed up sum_func calculation
*****************************************************************************/
static void
make_sum_func_list(JOIN *join,List<Item> &fields)
{
Item_sum **func = (Item_sum**) sql_alloc(sizeof(Item_sum*)*
(join->sum_func_count+1));
List_iterator<Item> it(fields);
join->sum_funcs=func;
Item *field;
while ((field=it++))
{
if (field->type() == Item::SUM_FUNC_ITEM && !field->const_item())
{
*func++=(Item_sum*) field;
}
}
*func=0; // End marker
}
/*
** Change all funcs and sum_funcs to fields in tmp table
*/
static void
change_to_use_tmp_fields(List<Item> &items)
{
List_iterator<Item> it(items);
Item *item_field,*item;
while ((item=it++))
{
Field *field;
if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
continue;
if (item->type() == Item::FIELD_ITEM)
{
((Item_field*) item)->field=
((Item_field*) item)->result_field;
}
else if ((field=item->tmp_table_field()))
{
if (item->type() == Item::SUM_FUNC_ITEM && field->table->group)
{
if (((Item_sum*) item)->sum_func() == Item_sum::AVG_FUNC)
item_field=(Item*) new Item_avg_field((Item_sum_avg*) item);
else if (((Item_sum*) item)->sum_func() == Item_sum::STD_FUNC)
item_field=(Item*) new Item_std_field((Item_sum_std*) item);
else
item_field=(Item*) new Item_field(field);
}
else
item_field=(Item*) new Item_field(field);
item_field->name=item->name; /*lint -e613 */
#ifdef DELETE_ITEMS
delete it.replace(item_field); /*lint -e613 */
#else
(void) it.replace(item_field); /*lint -e613 */
#endif
}
}
}
/*
** Change all sum_func refs to fields to point at fields in tmp table
** Change all funcs to be fields in tmp table
*/
static void
change_refs_to_tmp_fields(List<Item> &items)
{
List_iterator<Item> it(items);
Item *item;
while ((item= it++))
{
if (item->type() == Item::SUM_FUNC_ITEM)
{
if (!item->const_item())
{
Item_sum *sum_item= (Item_sum*) item;
if (sum_item->result_field) // If not a const sum func
{
#ifdef DELETE_ITEMS
delete sum_item->item; // This is a nop
#endif
sum_item->item= new Item_field(sum_item->result_field);
}
}
}
else if (item->with_sum_func)
continue;
else if (item->type() == Item::FUNC_ITEM || item->type() == Item::COND_ITEM)
{ /* All funcs are stored */
#ifdef DELETE_ITEMS
delete it.replace(new Item_field(((Item_func*) item)->result_field));
#else
(void) it.replace(new Item_field(((Item_func*) item)->result_field));
#endif
}
else if (item->type() == Item::FIELD_ITEM) /* Change refs */
{
((Item_field*)item)->field=((Item_field*) item)->result_field;
}
}
}
/******************************************************************************
** code for calculating functions
******************************************************************************/
static void
init_tmptable_sum_functions(Item_sum **func_ptr)
{
Item_sum *func;
while ((func= *(func_ptr++)))
func->reset_field();
}
/* Update record 0 in tmp_table from record 1 */
static void
update_tmptable_sum_func(Item_sum **func_ptr,
TABLE *tmp_table __attribute__((unused)))
{
Item_sum *func;
while ((func= *(func_ptr++)))
func->update_field(0);
}
/* Copy result of sum functions to record in tmp_table */
static void
copy_sum_funcs(Item_sum **func_ptr)
{
Item_sum *func;
for (; (func = *func_ptr) ; func_ptr++)
(void) func->save_in_field(func->result_field);
return;
}
static void
init_sum_functions(Item_sum **func_ptr)
{
Item_sum *func;
for (; (func= (Item_sum*) *func_ptr) ; func_ptr++)
func->reset();
}
static void
update_sum_func(Item_sum **func_ptr)
{
Item_sum *func;
for (; (func= (Item_sum*) *func_ptr) ; func_ptr++)
func->add();
}
/* Copy result of functions to record in tmp_table */
static void
copy_funcs(Item_result_field **func_ptr)
{
Item_result_field *func;
for (; (func = *func_ptr) ; func_ptr++)
(void) func->save_in_field(func->result_field);
return;
}
/*****************************************************************************
** Create a condition for a const reference and add this to the
** currenct select for the table
*****************************************************************************/
static void add_ref_to_table_cond(JOIN_TAB *join_tab)
{
Item_cond_and *cond=new Item_cond_and();
TABLE *table=join_tab->table;
int error;
DBUG_ENTER("add_ref_to_table_cond");
for (uint i=0 ; i < table->reginfo.ref_fields ; i++)
{
Item *value;
LINT_INIT(value);
Field *field=table->key_info[table->reginfo.ref_key].key_part[i].field;
field->set_image(table->reginfo.ref_field[i].ptr,field->pack_length());
switch (field->cmp_type()) {
case REAL_RESULT:
value=new Item_real(field->val_real());
break;
case INT_RESULT:
value=new Item_int(field->val_int());
break;
case STRING_RESULT:
{
char buff[MAX_FIELD_WIDTH];
String str(buff,sizeof(buff));
field->val_str(&str,&str);
value=new Item_string(sql_strmake(str.ptr(),str.length()),
str.length());
break;
}
}
cond->add(new Item_func_eq(new Item_field(field),value));
}
cond->fix_fields((THD *) 0,(TABLE_LIST *) 0);
if (join_tab->select)
{
cond->add(join_tab->select->cond);
join_tab->select->cond=cond;
}
else
join_tab->select=make_select(&join_tab->table,1,0,0,cond,&error);
DBUG_VOID_RETURN;
}
/****************************************************************************
** Send a description about what how the select will be done to stdout
****************************************************************************/
static void select_describe(JOIN *join)
{
DBUG_ENTER("select_describe");
List<Item> field_list;
Item *item;
field_list.push_back(new Item_empty_string("table",NAME_LEN));
field_list.push_back(new Item_empty_string("type",10));
field_list.push_back(item=new Item_empty_string("possible_keys",
NAME_LEN*MAX_KEY));
item->maybe_null=1;
field_list.push_back(item=new Item_empty_string("key",NAME_LEN));
item->maybe_null=1;
field_list.push_back(item=new Item_int("key_len",0,3));
item->maybe_null=1;
field_list.push_back(item=new Item_empty_string("ref",
NAME_LEN*MAX_REF_PARTS));
item->maybe_null=1;
field_list.push_back(new Item_real("rows",0.0,0,10));
field_list.push_back(new Item_empty_string("Extra",80));
if (send_fields(join->thd,field_list,1))
return; /* purecov: inspected */
char buff[141];
String tmp(buff,sizeof(buff)),*packet= &join->thd->packet;
for (uint i=0 ; i < join->tables ; i++)
{
JOIN_TAB *tab=join->join_tab+i;
TABLE *table=tab->table;
if (tab->type == JT_ALL && tab->select && tab->select->quick)
tab->type= JT_RANGE;
packet->length(0);
net_store_data(packet,table->table_name);
net_store_data(packet,join_type_str[tab->type]);
tmp.length(0);
key_map bits;
uint j;
for (j=0,bits=tab->keys ; bits ; j++,bits>>=1)
{
if (bits & 1)
{
if (tmp.length())
tmp.append(',');
tmp.append(table->key_info[j].name);
}
}
if (tmp.length())
net_store_data(packet,tmp.ptr(),tmp.length());
else
net_store_null(packet);
if (table->reginfo.ref_fields)
{
net_store_data(packet,table->key_info[table->reginfo.ref_key].name);
net_store_data(packet,(long) table->reginfo.ref_length);
tmp.length(0);
for (uint ref=0 ; ref < table->reginfo.ref_fields ; ref++)
{
Field *field=table->reginfo.ref_field[ref].field;
if (tmp.length())
tmp.append(',');
if (field->table_name)
{
tmp.append(field->table_name);
tmp.append('.');
}
tmp.append(field->field_name);
}
net_store_data(packet,tmp.ptr(),tmp.length());
}
else if (tab->type == JT_NEXT)
{
net_store_data(packet,table->key_info[tab->index].name);
net_store_data(packet,(long) table->key_info[tab->index].key_length);
net_store_null(packet);
}
else if (tab->select && tab->select->quick)
{
net_store_data(packet,table->key_info[tab->select->quick->index].name);;
net_store_null(packet);
net_store_null(packet);
}
else
{
net_store_null(packet);
net_store_null(packet);
net_store_null(packet);
}
sprintf(buff,"%.0f",join->best_positions[i].records_read);
net_store_data(packet,buff);
my_bool key_read=table->key_read;
if (tab->type == JT_NEXT &&
((table->used_keys & ((key_map) 1 << tab->index))))
key_read=1;
if (tab->info)
net_store_data(packet,tab->info);
else if (tab->select)
{
buff[0]=0;
if (tab->use_quick == 2)
sprintf(buff,"range checked for each record (index map: %lu)",
tab->keys);
else if (!tab->select->quick)
strmov(buff,"where used");
if (key_read)
strmov(strend(buff),"; Using index");
net_store_data(packet,buff);
}
else if (key_read)
net_store_data(packet,"Using index");
else
net_store_data(packet,"",0);
if (my_net_write(&join->thd->net,(char*) packet->ptr(),packet->length()))
DBUG_VOID_RETURN; /* purecov: inspected */
}
send_eof(&join->thd->net);
DBUG_VOID_RETURN;
}
static void describe_info(const char *info)
{
List<Item> field_list;
THD *thd=current_thd;
String *packet= &thd->packet;
field_list.push_back(new Item_empty_string("Comment",80));
if (send_fields(thd,field_list,1))
return; /* purecov: inspected */
packet->length(0);
net_store_data(packet,info);
if (!my_net_write(&thd->net,(char*) packet->ptr(),packet->length()))
send_eof(&thd->net);
}