Fixed bug mdev-4063 (bug #56927).

This bug could result in returning 0 for the expressions of the form 
<aggregate_function>(distinct field) when the system variable  
max_heap_table_size was set to a small enough number.
It happened because the method Unique::walk() did not support
the case when more than one pass was needed to merge the trees
of distinct values saved in an external file.

Backported a fix in grant_lowercase.test from mariadb 5.5.
This commit is contained in:
Igor Babaev 2013-01-21 11:47:45 -08:00
parent 2e11ca36f2
commit fade3647ec
6 changed files with 146 additions and 69 deletions

View File

@ -103,5 +103,20 @@ sm
10323810 10323810
10325070 10325070
10326330 10326330
#
# Bug mdev-4063: SUM(DISTINCT...) with small'max_heap_table_size
# (bug #56927)
#
SET max_heap_table_size=default;
INSERT INTO t1 SELECT id+16384 FROM t1;
DELETE FROM t2;
INSERT INTO t2 SELECT id FROM t1 ORDER BY id*rand();
SELECT SUM(DISTINCT id) sm FROM t2;
sm
536887296
SET max_heap_table_size=16384;
SELECT SUM(DISTINCT id) sm FROM t2;
sm
536887296
DROP TABLE t1; DROP TABLE t1;
DROP TABLE t2; DROP TABLE t2;

View File

@ -1,4 +1,5 @@
# test cases for strmov(tmp_db, db) -> strnmov replacement in sql_acl.cc # test cases for strmov(tmp_db, db) -> strnmov replacement in sql_acl.cc
--source include/not_embedded.inc
# #
# http://seclists.org/fulldisclosure/2012/Dec/4 # http://seclists.org/fulldisclosure/2012/Dec/4

View File

@ -63,5 +63,22 @@ SELECT SUM(DISTINCT id) sm FROM t1;
SELECT SUM(DISTINCT id) sm FROM t2; SELECT SUM(DISTINCT id) sm FROM t2;
SELECT SUM(DISTINCT id) sm FROM t1 GROUP BY id % 13; SELECT SUM(DISTINCT id) sm FROM t1 GROUP BY id % 13;
--echo #
--echo # Bug mdev-4063: SUM(DISTINCT...) with small'max_heap_table_size
--echo # (bug #56927)
--echo #
SET max_heap_table_size=default;
INSERT INTO t1 SELECT id+16384 FROM t1;
DELETE FROM t2;
INSERT INTO t2 SELECT id FROM t1 ORDER BY id*rand();
SELECT SUM(DISTINCT id) sm FROM t2;
SET max_heap_table_size=16384;
SELECT SUM(DISTINCT id) sm FROM t2;
DROP TABLE t1; DROP TABLE t1;
DROP TABLE t2; DROP TABLE t2;

View File

@ -1084,7 +1084,7 @@ void Item_sum_distinct::calculate_val_and_count()
if (tree) if (tree)
{ {
table->field[0]->set_notnull(); table->field[0]->set_notnull();
tree->walk(item_sum_distinct_walk, (void*) this); tree->walk(table, item_sum_distinct_walk, (void*) this);
} }
is_evaluated= TRUE; is_evaluated= TRUE;
} }
@ -2583,7 +2583,7 @@ longlong Item_sum_count_distinct::val_int()
if (tree->elements == 0) if (tree->elements == 0)
return (longlong) tree->elements_in_tree(); // everything fits in memory return (longlong) tree->elements_in_tree(); // everything fits in memory
count= 0; count= 0;
tree->walk(count_distinct_walk, (void*) &count); tree->walk(table, count_distinct_walk, (void*) &count);
is_evaluated= TRUE; is_evaluated= TRUE;
return (longlong) count; return (longlong) count;
} }

View File

@ -3008,6 +3008,8 @@ class Unique :public Sql_alloc
bool flush(); bool flush();
uint size; uint size;
bool merge(TABLE *table, uchar *buff, bool without_last_merge);
public: public:
ulong elements; ulong elements;
Unique(qsort_cmp2 comp_func, void *comp_func_fixed_arg, Unique(qsort_cmp2 comp_func, void *comp_func_fixed_arg,
@ -3035,7 +3037,7 @@ public:
} }
void reset(); void reset();
bool walk(tree_walk_action action, void *walk_action_arg); bool walk(TABLE *table, tree_walk_action action, void *walk_action_arg);
uint get_size() const { return size; } uint get_size() const { return size; }
ulonglong get_max_in_memory_size() const { return max_in_memory_size; } ulonglong get_max_in_memory_size() const { return max_in_memory_size; }

View File

@ -538,6 +538,7 @@ end:
SYNOPSIS SYNOPSIS
Unique:walk() Unique:walk()
All params are 'IN': All params are 'IN':
table parameter for the call of the merge method
action function-visitor, typed in include/my_tree.h action function-visitor, typed in include/my_tree.h
function is called for each unique element function is called for each unique element
arg argument for visitor, which is passed to it on each call arg argument for visitor, which is passed to it on each call
@ -546,30 +547,117 @@ end:
<> 0 error <> 0 error
*/ */
bool Unique::walk(tree_walk_action action, void *walk_action_arg) bool Unique::walk(TABLE *table, tree_walk_action action, void *walk_action_arg)
{ {
int res; int res= 0;
uchar *merge_buffer; uchar *merge_buffer;
if (elements == 0) /* the whole tree is in memory */ if (elements == 0) /* the whole tree is in memory */
return tree_walk(&tree, action, walk_action_arg, left_root_right); return tree_walk(&tree, action, walk_action_arg, left_root_right);
table->sort.found_records=elements+tree.elements_in_tree;
/* flush current tree to the file to have some memory for merge buffer */ /* flush current tree to the file to have some memory for merge buffer */
if (flush()) if (flush())
return 1; return 1;
if (flush_io_cache(&file) || reinit_io_cache(&file, READ_CACHE, 0L, 0, 0)) if (flush_io_cache(&file) || reinit_io_cache(&file, READ_CACHE, 0L, 0, 0))
return 1; return 1;
if (!(merge_buffer= (uchar *) my_malloc((ulong) max_in_memory_size, MYF(0)))) ulong buff_sz= (max_in_memory_size / size + 1) * size;
if (!(merge_buffer= (uchar *) my_malloc((ulong) buff_sz, MYF(0))))
return 1; return 1;
if (buff_sz < (ulong) (size * (file_ptrs.elements + 1)))
res= merge(table, merge_buffer, buff_sz >= size * MERGEBUFF2) ;
if (!res)
{
res= merge_walk(merge_buffer, (ulong) max_in_memory_size, size, res= merge_walk(merge_buffer, (ulong) max_in_memory_size, size,
(BUFFPEK *) file_ptrs.buffer, (BUFFPEK *) file_ptrs.buffer,
(BUFFPEK *) file_ptrs.buffer + file_ptrs.elements, (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements,
action, walk_action_arg, action, walk_action_arg,
tree.compare, tree.custom_arg, &file); tree.compare, tree.custom_arg, &file);
}
my_free((char*) merge_buffer, MYF(0)); my_free((char*) merge_buffer, MYF(0));
return res; return res;
} }
/*
DESCRIPTION
Perform multi-pass sort merge of the elements accessed through table->sort,
using the buffer buff as the merge buffer. The last pass is not performed
if without_last_merge is TRUE.
SYNOPSIS
Unique:merge()
All params are 'IN':
table the parameter to access sort context
buff merge buffer
without_last_merge TRUE <=> do not perform the last merge
RETURN VALUE
0 OK
<> 0 error
*/
bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge)
{
SORTPARAM sort_param;
IO_CACHE *outfile= table->sort.io_cache;
BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer;
uint maxbuffer= file_ptrs.elements - 1;
my_off_t save_pos;
bool error= 1;
/* Open cached file if it isn't open */
if (!outfile)
outfile= table->sort.io_cache= (IO_CACHE*) my_malloc(sizeof(IO_CACHE),
MYF(MY_ZEROFILL));
if (!outfile ||
(! my_b_inited(outfile) &&
open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER,
MYF(MY_WME))))
return 1;
reinit_io_cache(outfile,WRITE_CACHE,0L,0,0);
bzero((char*) &sort_param,sizeof(sort_param));
sort_param.max_rows= elements;
sort_param.sort_form= table;
sort_param.rec_length= sort_param.sort_length= sort_param.ref_length=
size;
sort_param.keys= (uint) (max_in_memory_size / sort_param.sort_length);
sort_param.not_killable= 1;
sort_param.unique_buff= buff + (sort_param.keys * sort_param.sort_length);
sort_param.compare= (qsort2_cmp) buffpek_compare;
sort_param.cmp_context.key_compare= tree.compare;
sort_param.cmp_context.key_compare_arg= tree.custom_arg;
/* Merge the buffers to one file, removing duplicates */
if (merge_many_buff(&sort_param,buff,file_ptr,&maxbuffer,&file))
goto err;
if (flush_io_cache(&file) ||
reinit_io_cache(&file,READ_CACHE,0L,0,0))
goto err;
if (without_last_merge)
{
file_ptrs.elements= maxbuffer+1;
return 0;
}
if (merge_buffers(&sort_param, &file, outfile, buff, file_ptr,
file_ptr, file_ptr+maxbuffer,0))
goto err;
error= 0;
err:
if (flush_io_cache(outfile))
error= 1;
/* Setup io_cache for reading */
save_pos= outfile->pos_in_file;
if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
error= 1;
outfile->end_of_file=save_pos;
return error;
}
/* /*
Modify the TABLE element so that when one calls init_records() Modify the TABLE element so that when one calls init_records()
the rows will be read in priority order. the rows will be read in priority order.
@ -577,7 +665,8 @@ bool Unique::walk(tree_walk_action action, void *walk_action_arg)
bool Unique::get(TABLE *table) bool Unique::get(TABLE *table)
{ {
SORTPARAM sort_param; bool rc= 1;
uchar *sort_buffer= NULL;
table->sort.found_records= elements+tree.elements_in_tree; table->sort.found_records= elements+tree.elements_in_tree;
if (my_b_tell(&file) == 0) if (my_b_tell(&file) == 0)
@ -595,62 +684,15 @@ bool Unique::get(TABLE *table)
if (flush()) if (flush())
return 1; return 1;
IO_CACHE *outfile=table->sort.io_cache; ulong buff_sz= (max_in_memory_size / size + 1) * size;
BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer; if (!(sort_buffer= (uchar*) my_malloc(buff_sz, MYF(0))))
uint maxbuffer= file_ptrs.elements - 1;
uchar *sort_buffer;
my_off_t save_pos;
bool error=1;
/* Open cached file if it isn't open */
outfile=table->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE),
MYF(MY_ZEROFILL));
if (!outfile ||
(! my_b_inited(outfile) &&
open_cached_file(outfile,mysql_tmpdir,TEMP_PREFIX,READ_RECORD_BUFFER,
MYF(MY_WME))))
return 1; return 1;
reinit_io_cache(outfile,WRITE_CACHE,0L,0,0);
bzero((char*) &sort_param,sizeof(sort_param)); if (merge(table, sort_buffer, FALSE))
sort_param.max_rows= elements;
sort_param.sort_form=table;
sort_param.rec_length= sort_param.sort_length= sort_param.ref_length=
size;
sort_param.keys= (uint) (max_in_memory_size / sort_param.sort_length);
sort_param.not_killable=1;
if (!(sort_buffer=(uchar*) my_malloc((sort_param.keys+1) *
sort_param.sort_length,
MYF(0))))
return 1;
sort_param.unique_buff= sort_buffer+(sort_param.keys*
sort_param.sort_length);
sort_param.compare= (qsort2_cmp) buffpek_compare;
sort_param.cmp_context.key_compare= tree.compare;
sort_param.cmp_context.key_compare_arg= tree.custom_arg;
/* Merge the buffers to one file, removing duplicates */
if (merge_many_buff(&sort_param,sort_buffer,file_ptr,&maxbuffer,&file))
goto err; goto err;
if (flush_io_cache(&file) || rc= 0;
reinit_io_cache(&file,READ_CACHE,0L,0,0))
goto err;
if (merge_buffers(&sort_param, &file, outfile, sort_buffer, file_ptr,
file_ptr, file_ptr+maxbuffer,0))
goto err;
error=0;
err: err:
x_free(sort_buffer); x_free(sort_buffer);
if (flush_io_cache(outfile)) return rc;
error=1;
/* Setup io_cache for reading */
save_pos=outfile->pos_in_file;
if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
error=1;
outfile->end_of_file=save_pos;
return error;
} }