diff options
Diffstat (limited to 'sql/item_sum.cc')
-rw-r--r-- | sql/item_sum.cc | 333 |
1 files changed, 275 insertions, 58 deletions
diff --git a/sql/item_sum.cc b/sql/item_sum.cc index a81cfd7ea82..a674cfe8ff9 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -42,8 +42,9 @@ size_t Item_sum::ram_limitation(THD *thd) { - return (size_t)MY_MIN(thd->variables.tmp_memory_table_size, - thd->variables.max_heap_table_size); + return MY_MAX(1024, + (size_t)MY_MIN(thd->variables.tmp_memory_table_size, + thd->variables.max_heap_table_size)); } @@ -1284,21 +1285,22 @@ void Item_sum_min_max::setup_hybrid(THD *thd, Item *item, Item *value_arg) } -Field *Item_sum_min_max::create_tmp_field(bool group, TABLE *table) +Field *Item_sum_min_max::create_tmp_field(MEM_ROOT *root, + bool group, TABLE *table) { DBUG_ENTER("Item_sum_min_max::create_tmp_field"); if (args[0]->type() == Item::FIELD_ITEM) { Field *field= ((Item_field*) args[0])->field; - if ((field= field->create_tmp_field(table->in_use->mem_root, table, true))) + if ((field= field->create_tmp_field(root, table, true))) { DBUG_ASSERT((field->flags & NOT_NULL_FLAG) == 0); field->field_name= name; } DBUG_RETURN(field); } - DBUG_RETURN(tmp_table_field_from_field_type(table)); + DBUG_RETURN(tmp_table_field_from_field_type(root, table)); } /*********************************************************************** @@ -1991,7 +1993,7 @@ Item *Item_sum_avg::copy_or_same(THD* thd) } -Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table) +Field *Item_sum_avg::create_tmp_field(MEM_ROOT *root, bool group, TABLE *table) { if (group) @@ -2001,7 +2003,7 @@ Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table) The easiest way is to do this is to store both value in a string and unpack on access. */ - Field *field= new (table->in_use->mem_root) + Field *field= new (root) Field_string(((result_type() == DECIMAL_RESULT) ? dec_bin_size : sizeof(double)) + sizeof(longlong), 0, &name, &my_charset_bin); @@ -2009,7 +2011,7 @@ Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table) field->init(table); return field; } - return tmp_table_field_from_field_type(table); + return tmp_table_field_from_field_type(root, table); } @@ -2233,7 +2235,8 @@ Item *Item_sum_variance::copy_or_same(THD* thd) If we're grouping, then we need some space to serialize variables into, to pass around. */ -Field *Item_sum_variance::create_tmp_field(bool group, TABLE *table) +Field *Item_sum_variance::create_tmp_field(MEM_ROOT *root, + bool group, TABLE *table) { Field *field; if (group) @@ -2243,11 +2246,12 @@ Field *Item_sum_variance::create_tmp_field(bool group, TABLE *table) The easiest way is to do this is to store both value in a string and unpack on access. */ - field= new Field_string(Stddev::binary_size(), 0, &name, &my_charset_bin); + field= new (root) Field_string(Stddev::binary_size(), 0, + &name, &my_charset_bin); } else - field= new Field_double(max_length, maybe_null, &name, decimals, - TRUE); + field= new (root) Field_double(max_length, maybe_null, &name, decimals, + TRUE); if (field != NULL) field->init(table); @@ -3521,7 +3525,7 @@ String *Item_sum_udf_str::val_str(String *str) */ extern "C" -int group_concat_key_cmp_with_distinct(void* arg, const void* key1, +int group_concat_key_cmp_with_distinct(void* arg, const void* key1, const void* key2) { Item_func_group_concat *item_func= (Item_func_group_concat*)arg; @@ -3555,6 +3559,63 @@ int group_concat_key_cmp_with_distinct(void* arg, const void* key1, } +/* + @brief + Comparator function for DISTINCT clause taking into account NULL values. + + @note + Used for JSON_ARRAYAGG function +*/ + +int group_concat_key_cmp_with_distinct_with_nulls(void* arg, + const void* key1_arg, + const void* key2_arg) +{ + Item_func_group_concat *item_func= (Item_func_group_concat*)arg; + + uchar *key1= (uchar*)key1_arg + item_func->table->s->null_bytes; + uchar *key2= (uchar*)key2_arg + item_func->table->s->null_bytes; + + /* + JSON_ARRAYAGG function only accepts one argument. + */ + + Item *item= item_func->args[0]; + /* + If item is a const item then either get_tmp_table_field returns 0 + or it is an item over a const table. + */ + if (item->const_item()) + return 0; + /* + We have to use get_tmp_table_field() instead of + real_item()->get_tmp_table_field() because we want the field in + the temporary table, not the original field + */ + Field *field= item->get_tmp_table_field(); + + if (!field) + return 0; + + if (field->is_null_in_record((uchar*)key1_arg) && + field->is_null_in_record((uchar*)key2_arg)) + return 0; + + if (field->is_null_in_record((uchar*)key1_arg)) + return -1; + + if (field->is_null_in_record((uchar*)key2_arg)) + return 1; + + uint offset= (field->offset(field->table->record[0]) - + field->table->s->null_bytes); + int res= field->cmp(key1 + offset, key2 + offset); + if (res) + return res; + return 0; +} + + /** function of sort for syntax: GROUP_CONCAT(expr,... ORDER BY col,... ) */ @@ -3611,6 +3672,106 @@ int group_concat_key_cmp_with_order(void* arg, const void* key1, } +/* + @brief + Comparator function for ORDER BY clause taking into account NULL values. + + @note + Used for JSON_ARRAYAGG function +*/ + +int group_concat_key_cmp_with_order_with_nulls(void *arg, const void *key1_arg, + const void *key2_arg) +{ + Item_func_group_concat* grp_item= (Item_func_group_concat*) arg; + ORDER **order_item, **end; + + uchar *key1= (uchar*)key1_arg + grp_item->table->s->null_bytes; + uchar *key2= (uchar*)key2_arg + grp_item->table->s->null_bytes; + + for (order_item= grp_item->order, end=order_item+ grp_item->arg_count_order; + order_item < end; + order_item++) + { + Item *item= *(*order_item)->item; + /* + If field_item is a const item then either get_tmp_table_field returns 0 + or it is an item over a const table. + */ + if (item->const_item()) + continue; + /* + We have to use get_tmp_table_field() instead of + real_item()->get_tmp_table_field() because we want the field in + the temporary table, not the original field + + Note that for the case of ROLLUP, field may point to another table + tham grp_item->table. This is however ok as the table definitions are + the same. + */ + Field *field= item->get_tmp_table_field(); + if (!field) + continue; + + if (field->is_null_in_record((uchar*)key1_arg) && + field->is_null_in_record((uchar*)key2_arg)) + continue; + + if (field->is_null_in_record((uchar*)key1_arg)) + return ((*order_item)->direction == ORDER::ORDER_ASC) ? -1 : 1; + + if (field->is_null_in_record((uchar*)key2_arg)) + return ((*order_item)->direction == ORDER::ORDER_ASC) ? 1 : -1; + + uint offset= (field->offset(field->table->record[0]) - + field->table->s->null_bytes); + int res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset); + if (res) + return ((*order_item)->direction == ORDER::ORDER_ASC) ? res : -res; + } + /* + We can't return 0 because in that case the tree class would remove this + item as double value. This would cause problems for case-changes and + if the returned values are not the same we do the sort on. + */ + return 1; +} + + +static void report_cut_value_error(THD *thd, uint row_count, const char *fname) +{ + size_t fn_len= strlen(fname); + char *fname_upper= (char *) my_alloca(fn_len + 1); + if (!fname_upper) + fname_upper= (char*) fname; // Out of memory + else + memcpy(fname_upper, fname, fn_len+1); + my_caseup_str(&my_charset_latin1, fname_upper); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_CUT_VALUE_GROUP_CONCAT, + ER_THD(thd, ER_CUT_VALUE_GROUP_CONCAT), + row_count, fname_upper); + my_afree(fname_upper); +} + + +void Item_func_group_concat::cut_max_length(String *result, + uint old_length, uint max_length) const +{ + const char *ptr= result->ptr(); + /* + It's ok to use item->result.length() as the fourth argument + as this is never used to limit the length of the data. + Cut is done with the third argument. + */ + size_t add_length= Well_formed_prefix(collation.collation, + ptr + old_length, + ptr + max_length, + result->length()).length(); + result->length(old_length + add_length); +} + + /** Append data from current leaf to item->result. */ @@ -3663,7 +3824,7 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), because it contains both order and arg list fields. */ if ((*arg)->const_item()) - res= (*arg)->val_str(&tmp); + res= item->get_str_from_item(*arg, &tmp); else { Field *field= (*arg)->get_tmp_table_field(); @@ -3672,11 +3833,13 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), uint offset= (field->offset(field->table->record[0]) - table->s->null_bytes); DBUG_ASSERT(offset < table->s->reclength); - res= field->val_str(&tmp, key + offset); + res= item->get_str_from_field(*arg, field, &tmp, key, + offset + item->get_null_bytes()); } else - res= (*arg)->val_str(&tmp); + res= item->get_str_from_item(*arg, &tmp); } + if (res) result->append(*res); } @@ -3688,24 +3851,10 @@ int dump_leaf_key(void* key_arg, element_count count __attribute__((unused)), /* stop if length of result more than max_length */ if (result->length() > max_length) { - CHARSET_INFO *cs= item->collation.collation; - const char *ptr= result->ptr(); THD *thd= current_thd; - /* - It's ok to use item->result.length() as the fourth argument - as this is never used to limit the length of the data. - Cut is done with the third argument. - */ - size_t add_length= Well_formed_prefix(cs, - ptr + old_length, - ptr + max_length, - result->length()).length(); - result->length(old_length + add_length); + item->cut_max_length(result, old_length, max_length); item->warning_for_row= TRUE; - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_CUT_VALUE_GROUP_CONCAT, - ER_THD(thd, ER_CUT_VALUE_GROUP_CONCAT), - item->row_count); + report_cut_value_error(thd, item->row_count, item->func_name()); /** To avoid duplicated warnings in Item_func_group_concat::val_str() @@ -3907,7 +4056,7 @@ void Item_func_group_concat::clear() result.copy(); null_value= TRUE; warning_for_row= FALSE; - result_finalized= FALSE; + result_finalized= false; if (offset_limit) copy_offset_limit= offset_limit->val_int(); if (row_limit) @@ -3957,7 +4106,7 @@ bool Item_func_group_concat::repack_tree(THD *thd) init_tree(&st.tree, (size_t) MY_MIN(thd->variables.max_heap_table_size, thd->variables.sortbuff_size/16), 0, - size, group_concat_key_cmp_with_order, NULL, + size, get_comparator_function_for_order_by(), NULL, (void*) this, MYF(MY_THREAD_SPECIFIC)); DBUG_ASSERT(tree->size_of_element == st.tree.size_of_element); st.table= table; @@ -3975,6 +4124,7 @@ bool Item_func_group_concat::repack_tree(THD *thd) return 0; } + /* Repacking the tree is expensive. But it keeps the tree small, and inserting into an unnecessary large tree is also waste of time. @@ -3985,9 +4135,9 @@ bool Item_func_group_concat::repack_tree(THD *thd) */ #define GCONCAT_REPACK_FACTOR 10 -bool Item_func_group_concat::add() +bool Item_func_group_concat::add(bool exclude_nulls) { - if (always_null) + if (always_null && exclude_nulls) return 0; copy_fields(tmp_table_param); if (copy_funcs(tmp_table_param->items_to_copy, table->in_use)) @@ -4005,11 +4155,20 @@ bool Item_func_group_concat::add() Field *field= show_item->get_tmp_table_field(); if (field) { - if (field->is_null_in_record((const uchar*) table->record[0])) + if (field->is_null_in_record((const uchar*) table->record[0]) && + exclude_nulls) return 0; // Skip row if it contains null if (tree && (res= field->val_str(&buf))) row_str_len+= res->length(); } + else + { + /* + should not reach here, we create temp table for all the arguments of + the group_concat function + */ + DBUG_ASSERT(0); + } } null_value= FALSE; @@ -4019,7 +4178,7 @@ bool Item_func_group_concat::add() { /* Filter out duplicate rows. */ uint count= unique_filter->elements_in_tree(); - unique_filter->unique_add(table->record[0] + table->s->null_bytes); + unique_filter->unique_add(get_record_pointer()); if (count == unique_filter->elements_in_tree()) row_eligible= FALSE; } @@ -4033,8 +4192,7 @@ bool Item_func_group_concat::add() && tree->elements_in_tree > 1) if (repack_tree(thd)) return 1; - el= tree_insert(tree, table->record[0] + table->s->null_bytes, 0, - tree->custom_arg); + el= tree_insert(tree, get_record_pointer(), 0, tree->custom_arg); /* check if there was enough memory to insert the row */ if (!el) return 1; @@ -4046,7 +4204,7 @@ bool Item_func_group_concat::add() row to the output buffer here. That will be done in val_str. */ if (row_eligible && !warning_for_row && (!tree && !distinct)) - dump_leaf_key(table->record[0] + table->s->null_bytes, 1, this); + dump_leaf_key(get_record_pointer(), 1, this); return 0; } @@ -4141,13 +4299,10 @@ bool Item_func_group_concat::setup(THD *thd) Item *item= args[i]; if (list.push_back(item, thd->mem_root)) DBUG_RETURN(TRUE); - if (item->const_item()) + if (item->const_item() && item->is_null() && skip_nulls()) { - if (item->is_null()) - { - always_null= 1; - DBUG_RETURN(FALSE); - } + always_null= 1; + DBUG_RETURN(FALSE); } } @@ -4242,16 +4397,16 @@ bool Item_func_group_concat::setup(THD *thd) */ init_tree(tree, (size_t)MY_MIN(thd->variables.max_heap_table_size, thd->variables.sortbuff_size/16), 0, - tree_key_length, - group_concat_key_cmp_with_order, NULL, (void*) this, + tree_key_length + get_null_bytes(), + get_comparator_function_for_order_by(), NULL, (void*) this, MYF(MY_THREAD_SPECIFIC)); tree_len= 0; } if (distinct) - unique_filter= new Unique(group_concat_key_cmp_with_distinct, + unique_filter= new Unique(get_comparator_function_for_distinct(), (void*)this, - tree_key_length, + tree_key_length + get_null_bytes(), ram_limitation(thd)); if ((row_limit && row_limit->cmp_type() != INT_RESULT) || (offset_limit && offset_limit->cmp_type() != INT_RESULT)) @@ -4298,18 +4453,76 @@ String* Item_func_group_concat::val_str(String* str) table->blob_storage->is_truncated_value()) { warning_for_row= true; - push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, - ER_CUT_VALUE_GROUP_CONCAT, ER(ER_CUT_VALUE_GROUP_CONCAT), - row_count); + report_cut_value_error(current_thd, row_count, func_name()); } return &result; } +/* + @brief + Get the comparator function for DISTINT clause +*/ + +qsort_cmp2 Item_func_group_concat::get_comparator_function_for_distinct() +{ + return skip_nulls() ? + group_concat_key_cmp_with_distinct : + group_concat_key_cmp_with_distinct_with_nulls; +} + + +/* + @brief + Get the comparator function for ORDER BY clause +*/ + +qsort_cmp2 Item_func_group_concat::get_comparator_function_for_order_by() +{ + return skip_nulls() ? + group_concat_key_cmp_with_order : + group_concat_key_cmp_with_order_with_nulls; +} + + +/* + + @brief + Get the record pointer of the current row of the table + + @details + look at the comments for Item_func_group_concat::get_null_bytes +*/ + +uchar* Item_func_group_concat::get_record_pointer() +{ + return skip_nulls() ? + table->record[0] + table->s->null_bytes : + table->record[0]; +} + + +/* + @brief + Get the null bytes for the table if required. + + @details + This function is used for GROUP_CONCAT (or JSON_ARRAYAGG) implementation + where the Unique tree or the ORDER BY tree may store the null values, + in such case we also store the null bytes inside each node of the tree. + +*/ + +uint Item_func_group_concat::get_null_bytes() +{ + return skip_nulls() ? 0 : table->s->null_bytes; +} + + void Item_func_group_concat::print(String *str, enum_query_type query_type) { - str->append(STRING_WITH_LEN("group_concat(")); + str->append(func_name()); if (distinct) str->append(STRING_WITH_LEN("distinct ")); for (uint i= 0; i < arg_count_field; i++) @@ -4332,9 +4545,13 @@ void Item_func_group_concat::print(String *str, enum_query_type query_type) str->append(STRING_WITH_LEN(" DESC")); } } - str->append(STRING_WITH_LEN(" separator \'")); - str->append_for_single_quote(separator->ptr(), separator->length()); - str->append(STRING_WITH_LEN("\'")); + + if (sum_func() == GROUP_CONCAT_FUNC) + { + str->append(STRING_WITH_LEN(" separator \'")); + str->append_for_single_quote(separator->ptr(), separator->length()); + str->append(STRING_WITH_LEN("\'")); + } if (limit_clause) { |