/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Sum functions (COUNT, MIN...) */ #ifdef __GNUC__ #pragma implementation // gcc: Class implementation #endif #include "mysql_priv.h" Item_sum::Item_sum(List &list) { arg_count=list.elements; if ((args=(Item**) sql_alloc(sizeof(Item*)*arg_count))) { uint i=0; List_iterator li(list); Item *item; while ((item=li++)) { args[i++]= item; } } with_sum_func=1; list.empty(); // Fields are used } void Item_sum::make_field(Send_field *tmp_field) { if (args[0]->type() == Item::FIELD_ITEM && keep_field_type()) ((Item_field*) args[0])->field->make_field(tmp_field); else { tmp_field->flags=0; if (!maybe_null) tmp_field->flags|= NOT_NULL_FLAG; tmp_field->length=max_length; tmp_field->decimals=decimals; tmp_field->type=(result_type() == INT_RESULT ? FIELD_TYPE_LONG : result_type() == REAL_RESULT ? FIELD_TYPE_DOUBLE : FIELD_TYPE_VAR_STRING); } tmp_field->table_name=(char*)""; tmp_field->col_name=name; } void Item_sum::print(String *str) { str->append(func_name()); str->append('('); for (uint i=0 ; i < arg_count ; i++) { if (i) str->append(','); args[i]->print(str); } str->append(')'); } void Item_sum::fix_num_length_and_dec() { decimals=0; for (uint i=0 ; i < arg_count ; i++) set_if_bigger(decimals,args[i]->decimals); max_length=float_length(decimals); } String * Item_sum_num::val_str(String *str) { double nr=val(); if (null_value) return 0; str->set(nr,decimals); return str; } String * Item_sum_int::val_str(String *str) { longlong nr=val_int(); if (null_value) return 0; char buff[21]; uint length= (uint) (longlong10_to_str(nr,buff,-10)-buff); str->copy(buff,length); return str; } bool Item_sum_num::fix_fields(THD *thd,TABLE_LIST *tables) { if (!thd->allow_sum_func) { my_error(ER_INVALID_GROUP_FUNC_USE,MYF(0)); return 1; } thd->allow_sum_func=0; // No included group funcs decimals=0; maybe_null=0; for (uint i=0 ; i < arg_count ; i++) { if (args[i]->fix_fields(thd,tables)) return 1; if (decimals < args[i]->decimals) decimals=args[i]->decimals; maybe_null |= args[i]->maybe_null; } result_field=0; max_length=float_length(decimals); null_value=1; fix_length_and_dec(); thd->allow_sum_func=1; // Allow group functions return 0; } bool Item_sum_hybrid::fix_fields(THD *thd,TABLE_LIST *tables) { Item *item=args[0]; if (!thd->allow_sum_func) { my_error(ER_INVALID_GROUP_FUNC_USE,MYF(0)); return 1; } thd->allow_sum_func=0; // No included group funcs if (item->fix_fields(thd,tables)) return 1; hybrid_type=item->result_type(); if (hybrid_type == INT_RESULT) max_length=21; else if (hybrid_type == REAL_RESULT) max_length=float_length(decimals); else max_length=item->max_length; decimals=item->decimals; maybe_null=item->maybe_null; binary=item->binary; result_field=0; null_value=1; fix_length_and_dec(); thd->allow_sum_func=1; // Allow group functions return 0; } /*********************************************************************** ** reset and add of sum_func ***********************************************************************/ void Item_sum_sum::reset() { null_value=0; sum=0.0; Item_sum_sum::add(); } bool Item_sum_sum::add() { sum+=args[0]->val(); return 0; } double Item_sum_sum::val() { return sum; } void Item_sum_count::reset() { count=0; add(); } bool Item_sum_count::add() { if (!args[0]->maybe_null) count++; else { (void) args[0]->val_int(); if (!args[0]->null_value) count++; } return 0; } longlong Item_sum_count::val_int() { return (longlong) count; } /* ** Avgerage */ void Item_sum_avg::reset() { sum=0.0; count=0; Item_sum_avg::add(); } bool Item_sum_avg::add() { double nr=args[0]->val(); if (!args[0]->null_value) { sum+=nr; count++; } return 0; } double Item_sum_avg::val() { if (!count) { null_value=1; return 0.0; } null_value=0; return sum/ulonglong2double(count); } /* ** Standard deviation */ void Item_sum_std::reset() { sum=sum_sqr=0.0; count=0; (void) Item_sum_std::add(); } bool Item_sum_std::add() { double nr=args[0]->val(); if (!args[0]->null_value) { sum+=nr; sum_sqr+=nr*nr; count++; } return 0; } double Item_sum_std::val() { if (!count) { null_value=1; return 0.0; } null_value=0; /* Avoid problems when the precision isn't good enough */ double tmp=ulonglong2double(count); double tmp2=(sum_sqr - sum*sum/tmp)/tmp; return tmp2 <= 0.0 ? 0.0 : sqrt(tmp2); } void Item_sum_std::reset_field() { double nr=args[0]->val(); char *res=result_field->ptr; if (args[0]->null_value) bzero(res,sizeof(double)*2+sizeof(longlong)); else { float8store(res,nr); nr*=nr; float8store(res+sizeof(double),nr); longlong tmp=1; int8store(res+sizeof(double)*2,tmp); } } void Item_sum_std::update_field(int offset) { double nr,old_nr,old_sqr; longlong field_count; char *res=result_field->ptr; float8get(old_nr,res+offset); float8get(old_sqr,res+offset+sizeof(double)); field_count=sint8korr(res+offset+sizeof(double)*2); nr=args[0]->val(); if (!args[0]->null_value) { old_nr+=nr; old_sqr+=nr*nr; field_count++; } float8store(res,old_nr); float8store(res+sizeof(double),old_sqr); int8store(res+sizeof(double)*2,field_count); } /* min & max */ double Item_sum_hybrid::val() { if (null_value) return 0.0; if (hybrid_type == STRING_RESULT) { String *res; res=val_str(&str_value); return res ? atof(res->c_ptr()) : 0.0; } return sum; } String * Item_sum_hybrid::val_str(String *str) { if (null_value) return 0; if (hybrid_type == STRING_RESULT) return &value; str->set(sum,decimals); return str; } bool Item_sum_min::add() { if (hybrid_type != STRING_RESULT) { double nr=args[0]->val(); if (!args[0]->null_value && (null_value || nr < sum)) { sum=nr; null_value=0; } } else { String *result=args[0]->val_str(&tmp_value); if (!args[0]->null_value && (null_value || (binary ? stringcmp(&value,result) : sortcmp(&value,result)) > 0)) { value.copy(*result); null_value=0; } } return 0; } bool Item_sum_max::add() { if (hybrid_type != STRING_RESULT) { double nr=args[0]->val(); if (!args[0]->null_value && (null_value || nr > sum)) { sum=nr; null_value=0; } } else { String *result=args[0]->val_str(&tmp_value); if (!args[0]->null_value && (null_value || (binary ? stringcmp(&value,result) : sortcmp(&value,result)) < 0)) { value.copy(*result); null_value=0; } } return 0; } /* bit_or and bit_and */ longlong Item_sum_bit::val_int() { return (longlong) bits; } void Item_sum_bit::reset() { bits=reset_bits; add(); } bool Item_sum_or::add() { ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) bits|=value; return 0; } bool Item_sum_and::add() { ulonglong value= (ulonglong) args[0]->val_int(); if (!args[0]->null_value) bits&=value; return 0; } /************************************************************************ ** reset result of a Item_sum with is saved in a tmp_table *************************************************************************/ void Item_sum_num::reset_field() { double nr=args[0]->val(); char *res=result_field->ptr; if (maybe_null) { if (args[0]->null_value) { nr=0.0; result_field->set_null(); } else result_field->set_notnull(); } float8store(res,nr); } void Item_sum_hybrid::reset_field() { if (hybrid_type == STRING_RESULT) { char buff[MAX_FIELD_WIDTH]; String tmp(buff,sizeof(buff)),*res; res=args[0]->val_str(&tmp); if (args[0]->null_value) { result_field->set_null(); result_field->reset(); } else { result_field->set_notnull(); result_field->store(res->ptr(),res->length()); } } else if (hybrid_type == INT_RESULT) { longlong nr=args[0]->val_int(); if (maybe_null) { if (args[0]->null_value) { nr=0; result_field->set_null(); } else result_field->set_notnull(); } result_field->store(nr); } else // REAL_RESULT { double nr=args[0]->val(); if (maybe_null) { if (args[0]->null_value) { nr=0.0; result_field->set_null(); } else result_field->set_notnull(); } result_field->store(nr); } } void Item_sum_sum::reset_field() { double nr=args[0]->val(); // Nulls also return 0 float8store(result_field->ptr,nr); null_value=0; result_field->set_notnull(); } void Item_sum_count::reset_field() { char *res=result_field->ptr; longlong nr=0; if (!args[0]->maybe_null) nr=1; else { (void) args[0]->val_int(); if (!args[0]->null_value) nr=1; } int8store(res,nr); } void Item_sum_avg::reset_field() { double nr=args[0]->val(); char *res=result_field->ptr; if (args[0]->null_value) bzero(res,sizeof(double)+sizeof(longlong)); else { float8store(res,nr); res+=sizeof(double); longlong tmp=1; int8store(res,tmp); } } void Item_sum_bit::reset_field() { char *res=result_field->ptr; ulonglong nr=(ulonglong) args[0]->val_int(); int8store(res,nr); } /* ** calc next value and merge it with field_value */ void Item_sum_sum::update_field(int offset) { double old_nr,nr; char *res=result_field->ptr; float8get(old_nr,res+offset); nr=args[0]->val(); if (!args[0]->null_value) old_nr+=nr; float8store(res,old_nr); } void Item_sum_count::update_field(int offset) { longlong nr; char *res=result_field->ptr; nr=sint8korr(res+offset); if (!args[0]->maybe_null) nr++; else { (void) args[0]->val_int(); if (!args[0]->null_value) nr++; } int8store(res,nr); } void Item_sum_avg::update_field(int offset) { double nr,old_nr; longlong field_count; char *res=result_field->ptr; float8get(old_nr,res+offset); field_count=sint8korr(res+offset+sizeof(double)); nr=args[0]->val(); if (!args[0]->null_value) { old_nr+=nr; field_count++; } float8store(res,old_nr); res+=sizeof(double); int8store(res,field_count); } void Item_sum_hybrid::update_field(int offset) { if (hybrid_type == STRING_RESULT) min_max_update_str_field(offset); else if (hybrid_type == INT_RESULT) min_max_update_int_field(offset); else min_max_update_real_field(offset); } void Item_sum_hybrid::min_max_update_str_field(int offset) { String *res_str=args[0]->val_str(&value); if (args[0]->null_value) result_field->copy_from_tmp(offset); // Use old value else { res_str->strip_sp(); result_field->ptr+=offset; // Get old max/min result_field->val_str(&tmp_value,&tmp_value); result_field->ptr-=offset; if (result_field->is_null() || (cmp_sign * (binary ? stringcmp(res_str,&tmp_value) : sortcmp(res_str,&tmp_value)) < 0)) result_field->store(res_str->ptr(),res_str->length()); else { // Use old value char *res=result_field->ptr; memcpy(res,res+offset,result_field->pack_length()); } result_field->set_notnull(); } } void Item_sum_hybrid::min_max_update_real_field(int offset) { double nr,old_nr; result_field->ptr+=offset; old_nr=result_field->val_real(); nr=args[0]->val(); if (!args[0]->null_value) { if (result_field->is_null(offset) || (cmp_sign > 0 ? old_nr > nr : old_nr < nr)) old_nr=nr; result_field->set_notnull(); } else if (result_field->is_null(offset)) result_field->set_null(); result_field->ptr-=offset; result_field->store(old_nr); } void Item_sum_hybrid::min_max_update_int_field(int offset) { longlong nr,old_nr; result_field->ptr+=offset; old_nr=result_field->val_int(); nr=args[0]->val_int(); if (!args[0]->null_value) { if (result_field->is_null(offset) || (cmp_sign > 0 ? old_nr > nr : old_nr < nr)) old_nr=nr; result_field->set_notnull(); } else if (result_field->is_null(offset)) result_field->set_null(); result_field->ptr-=offset; result_field->store(old_nr); } void Item_sum_or::update_field(int offset) { ulonglong nr; char *res=result_field->ptr; nr=uint8korr(res+offset); nr|= (ulonglong) args[0]->val_int(); int8store(res,nr); } void Item_sum_and::update_field(int offset) { ulonglong nr; char *res=result_field->ptr; nr=uint8korr(res+offset); nr&= (ulonglong) args[0]->val_int(); int8store(res,nr); } Item_avg_field::Item_avg_field(Item_sum_avg *item) { name=item->name; decimals=item->decimals; max_length=item->max_length; field=item->result_field; maybe_null=1; } double Item_avg_field::val() { double nr; longlong count; float8get(nr,field->ptr); char *res=(field->ptr+sizeof(double)); count=sint8korr(res); if (!count) { null_value=1; return 0.0; } null_value=0; return nr/(double) count; } String *Item_avg_field::val_str(String *str) { double nr=Item_avg_field::val(); if (null_value) return 0; str->set(nr,decimals); return str; } Item_std_field::Item_std_field(Item_sum_std *item) { name=item->name; decimals=item->decimals; max_length=item->max_length; field=item->result_field; maybe_null=1; } double Item_std_field::val() { double sum,sum_sqr; longlong count; float8get(sum,field->ptr); float8get(sum_sqr,(field->ptr+sizeof(double))); count=sint8korr(field->ptr+sizeof(double)*2); if (!count) { null_value=1; return 0.0; } null_value=0; double tmp= (double) count; double tmp2=(sum_sqr - sum*sum/tmp)/tmp; return tmp2 <= 0.0 ? 0.0 : sqrt(tmp2); } String *Item_std_field::val_str(String *str) { double nr=val(); if (null_value) return 0; str->set(nr,decimals); return str; } /**************************************************************************** ** COUNT(DISTINCT ...) ****************************************************************************/ #include "sql_select.h" static int simple_raw_key_cmp(void* arg, byte* key1, byte* key2) { return memcmp(key1, key2, (int)arg); } static int simple_str_key_cmp(void* arg, byte* key1, byte* key2) { return my_sortcmp(key1, key2, (int)arg); } // did not make this one static - at least gcc gets confused when // I try to declare a static function as a friend. If you can figure // out the syntax to make a static function a friend, make this one // static int composite_key_cmp(void* arg, byte* key1, byte* key2) { Item_sum_count_distinct* item = (Item_sum_count_distinct*)arg; Field** field = item->table->field, **field_end; field_end = field + item->table->fields; for(; field < field_end; ++field) { int res; Field* f = *field; int len = f->field_length; switch((*field)->type()) { case FIELD_TYPE_STRING: case FIELD_TYPE_VAR_STRING: res = f->key_cmp(key1, key2); break; default: res = memcmp(key1, key2, len); break; } if(res) return res; key1 += len; key2 += len; } return 0; } // helper function for walking the tree when we dump it to MyISAM - // tree_walk will call it for each // leaf int dump_leaf(byte* key, uint32 count __attribute__((unused)), Item_sum_count_distinct* item) { char* buf = item->table->record[0]; int error; memset(buf, 0xff, item->rec_offset); // make up for cheating in the tree memcpy(buf + item->rec_offset, key, item->tree.size_of_element); if ((error = item->table->file->write_row(buf))) { if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) return 1; } return 0; } Item_sum_count_distinct::~Item_sum_count_distinct() { if (table) free_tmp_table(current_thd, table); delete tmp_table_param; if(use_tree) delete_tree(&tree); } bool Item_sum_count_distinct::fix_fields(THD *thd,TABLE_LIST *tables) { if (Item_sum_num::fix_fields(thd,tables) || !(tmp_table_param= new TMP_TABLE_PARAM)) return 1; return 0; } bool Item_sum_count_distinct::setup(THD *thd) { List list; /* Create a table with an unique key over all parameters */ for (uint i=0; i < arg_count ; i++) if (list.push_back(args[i])) return 1; count_field_types(tmp_table_param,list,0); if (table) { free_tmp_table(thd, table); tmp_table_param->cleanup(); } if (!(table=create_tmp_table(thd, tmp_table_param, list, (ORDER*) 0, 1, 0, 0, current_lex->options | thd->options))) return 1; table->file->extra(HA_EXTRA_NO_ROWS); // Don't update rows table->no_rows=1; if(table->db_type == DB_TYPE_HEAP) // no blobs, otherwise it would be // MyISAM { qsort_cmp2 compare_key; void* cmp_arg; int key_len; if(table->fields == 1) // if we have only one field, which is // the most common use of count(distinct), it is much faster // to use a simpler key compare method that can take advantage // of not having to worry about other fields { Field* field = table->field[0]; switch(field->type()) { // if we have a string, we must take care of charsets // and case sensitivity case FIELD_TYPE_STRING: case FIELD_TYPE_VAR_STRING: compare_key = (qsort_cmp2)(field->binary() ? simple_raw_key_cmp: simple_str_key_cmp); break; default: // since at this point we cannot have blobs // anything else can be compared with memcmp compare_key = (qsort_cmp2)simple_raw_key_cmp; break; } cmp_arg = (void*)(key_len = field->field_length); rec_offset = 1; } else // too bad, cannot cheat - there is more than one field { cmp_arg = (void*)this; compare_key = (qsort_cmp2)composite_key_cmp; Field** field, **field_end; field_end = (field = table->field) + table->fields; for(key_len = 0; field < field_end; ++field) { key_len += (*field)->field_length; } rec_offset = table->reclength - key_len; } init_tree(&tree, min(max_heap_table_size, sortbuff_size/16), key_len, compare_key, 0, 0); tree.cmp_arg = cmp_arg; use_tree = 1; // the only time key_len could be 0 is if someone does // count(distinct) on a char(0) field - stupid thing to do, // but this has to be handled - otherwise someone can crash // the server with a DoS attack max_elements_in_tree = (key_len) ? max_heap_table_size/key_len : max_heap_table_size; } return 0; } int Item_sum_count_distinct::tree_to_myisam() { if(create_myisam_from_heap(table, tmp_table_param, HA_ERR_RECORD_FILE_FULL, 1) || tree_walk(&tree, (tree_walk_action)&dump_leaf, (void*)this, left_root_right)) return 1; delete_tree(&tree); use_tree = 0; return 0; } void Item_sum_count_distinct::reset() { if(use_tree) reset_tree(&tree); else { table->file->extra(HA_EXTRA_NO_CACHE); table->file->delete_all_rows(); table->file->extra(HA_EXTRA_WRITE_CACHE); } (void) add(); } bool Item_sum_count_distinct::add() { int error; copy_fields(tmp_table_param); copy_funcs(tmp_table_param->funcs); for (Field **field=table->field ; *field ; field++) if ((*field)->is_real_null(0)) return 0; // Don't count NULL if(use_tree) { // if the tree got too big, convert to MyISAM, otherwise // insert into the tree if((tree.elements_in_tree > max_elements_in_tree && tree_to_myisam()) || !tree_insert(&tree, table->record[0] + rec_offset, 0)) return 1; } else if ((error=table->file->write_row(table->record[0]))) { if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) { if (create_myisam_from_heap(table, tmp_table_param, error,1)) return 1; // Not a table_is_full error } } return 0; } longlong Item_sum_count_distinct::val_int() { if (!table) // Empty query return LL(0); if(use_tree) return tree.elements_in_tree; table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); return table->file->records; } /**************************************************************************** ** Functions to handle dynamic loadable aggregates ** Original source by: Alexis Mikhailov ** Adapted for UDAs by: Andreas F. Bobak . ** Rewritten by: Monty. ****************************************************************************/ #ifdef HAVE_DLOPEN void Item_udf_sum::reset() { DBUG_ENTER("Item_udf_sum::reset"); udf.reset(&null_value); DBUG_VOID_RETURN; } bool Item_udf_sum::add() { DBUG_ENTER("Item_udf_sum::reset"); udf.add(&null_value); DBUG_RETURN(0); } double Item_sum_udf_float::val() { DBUG_ENTER("Item_sum_udf_float::val"); DBUG_PRINT("info",("result_type: %d arg_count: %d", args[0]->result_type(), arg_count)); DBUG_RETURN(udf.val(&null_value)); } String *Item_sum_udf_float::val_str(String *str) { double nr=val(); if (null_value) return 0; /* purecov: inspected */ else str->set(nr,decimals); return str; } longlong Item_sum_udf_int::val_int() { DBUG_ENTER("Item_sum_udf_int::val_int"); DBUG_PRINT("info",("result_type: %d arg_count: %d", args[0]->result_type(), arg_count)); DBUG_RETURN(udf.val_int(&null_value)); } String *Item_sum_udf_int::val_str(String *str) { longlong nr=val_int(); if (null_value) return 0; else str->set(nr); return str; } /* Default max_length is max argument length */ void Item_sum_udf_str::fix_length_and_dec() { DBUG_ENTER("Item_sum_udf_str::fix_length_and_dec"); max_length=0; for (uint i = 0; i < arg_count; i++) set_if_bigger(max_length,args[i]->max_length); DBUG_VOID_RETURN; } String *Item_sum_udf_str::val_str(String *str) { DBUG_ENTER("Item_sum_udf_str::str"); String *res=udf.val_str(str,&str_value); null_value = !res; DBUG_RETURN(res); } #endif /* HAVE_DLOPEN */