summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Babaev <igor@askmonty.org>2013-01-21 11:47:45 -0800
committerIgor Babaev <igor@askmonty.org>2013-01-21 11:47:45 -0800
commitfade3647ecb18a90d9c89a924a076d714ec45888 (patch)
tree1cdd64f9b996aa56df1993d3ea319075124ec5c4
parent2e11ca36f28133c18b72351d176ee2fd7fcbc465 (diff)
downloadmariadb-git-fade3647ecb18a90d9c89a924a076d714ec45888.tar.gz
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.
-rw-r--r--mysql-test/r/sum_distinct-big.result15
-rw-r--r--mysql-test/t/grant_lowercase.test1
-rw-r--r--mysql-test/t/sum_distinct-big.test17
-rw-r--r--sql/item_sum.cc4
-rw-r--r--sql/sql_class.h4
-rw-r--r--sql/uniques.cc144
6 files changed, 131 insertions, 54 deletions
diff --git a/mysql-test/r/sum_distinct-big.result b/mysql-test/r/sum_distinct-big.result
index 9b55d59ab91..d4933b31f80 100644
--- a/mysql-test/r/sum_distinct-big.result
+++ b/mysql-test/r/sum_distinct-big.result
@@ -103,5 +103,20 @@ sm
10323810
10325070
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 t2;
diff --git a/mysql-test/t/grant_lowercase.test b/mysql-test/t/grant_lowercase.test
index 157e13449c2..b07cb88afd6 100644
--- a/mysql-test/t/grant_lowercase.test
+++ b/mysql-test/t/grant_lowercase.test
@@ -1,4 +1,5 @@
# 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
diff --git a/mysql-test/t/sum_distinct-big.test b/mysql-test/t/sum_distinct-big.test
index 0859f4b3d89..d3710056c9a 100644
--- a/mysql-test/t/sum_distinct-big.test
+++ b/mysql-test/t/sum_distinct-big.test
@@ -63,5 +63,22 @@ SELECT SUM(DISTINCT id) sm FROM t1;
SELECT SUM(DISTINCT id) sm FROM t2;
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 t2;
diff --git a/sql/item_sum.cc b/sql/item_sum.cc
index 92c2ba83f23..debba23438d 100644
--- a/sql/item_sum.cc
+++ b/sql/item_sum.cc
@@ -1084,7 +1084,7 @@ void Item_sum_distinct::calculate_val_and_count()
if (tree)
{
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;
}
@@ -2583,7 +2583,7 @@ longlong Item_sum_count_distinct::val_int()
if (tree->elements == 0)
return (longlong) tree->elements_in_tree(); // everything fits in memory
count= 0;
- tree->walk(count_distinct_walk, (void*) &count);
+ tree->walk(table, count_distinct_walk, (void*) &count);
is_evaluated= TRUE;
return (longlong) count;
}
diff --git a/sql/sql_class.h b/sql/sql_class.h
index fb4e13ad9c6..2fd8e8cd04b 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -3008,6 +3008,8 @@ class Unique :public Sql_alloc
bool flush();
uint size;
+ bool merge(TABLE *table, uchar *buff, bool without_last_merge);
+
public:
ulong elements;
Unique(qsort_cmp2 comp_func, void *comp_func_fixed_arg,
@@ -3035,7 +3037,7 @@ public:
}
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; }
ulonglong get_max_in_memory_size() const { return max_in_memory_size; }
diff --git a/sql/uniques.cc b/sql/uniques.cc
index fd6056ae8a6..5788edc673e 100644
--- a/sql/uniques.cc
+++ b/sql/uniques.cc
@@ -538,6 +538,7 @@ end:
SYNOPSIS
Unique:walk()
All params are 'IN':
+ table parameter for the call of the merge method
action function-visitor, typed in include/my_tree.h
function is called for each unique element
arg argument for visitor, which is passed to it on each call
@@ -546,66 +547,68 @@ end:
<> 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;
if (elements == 0) /* the whole tree is in memory */
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 */
if (flush())
return 1;
if (flush_io_cache(&file) || reinit_io_cache(&file, READ_CACHE, 0L, 0, 0))
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;
- res= merge_walk(merge_buffer, (ulong) max_in_memory_size, size,
- (BUFFPEK *) file_ptrs.buffer,
- (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements,
- action, walk_action_arg,
- tree.compare, tree.custom_arg, &file);
+ 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,
+ (BUFFPEK *) file_ptrs.buffer,
+ (BUFFPEK *) file_ptrs.buffer + file_ptrs.elements,
+ action, walk_action_arg,
+ tree.compare, tree.custom_arg, &file);
+ }
my_free((char*) merge_buffer, MYF(0));
return res;
}
+
/*
- Modify the TABLE element so that when one calls init_records()
- the rows will be read in priority order.
-*/
+ 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::get(TABLE *table)
+bool Unique::merge(TABLE *table, uchar *buff, bool without_last_merge)
{
SORTPARAM sort_param;
- table->sort.found_records=elements+tree.elements_in_tree;
-
- if (my_b_tell(&file) == 0)
- {
- /* Whole tree is in memory; Don't use disk if you don't need to */
- if ((record_pointers=table->sort.record_pointers= (uchar*)
- my_malloc(size * tree.elements_in_tree, MYF(0))))
- {
- (void) tree_walk(&tree, (tree_walk_action) unique_write_to_ptrs,
- this, left_root_right);
- return 0;
- }
- }
- /* Not enough memory; Save the result to file && free memory used by tree */
- if (flush())
- return 1;
-
- IO_CACHE *outfile=table->sort.io_cache;
+ IO_CACHE *outfile= table->sort.io_cache;
BUFFPEK *file_ptr= (BUFFPEK*) file_ptrs.buffer;
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));
+ 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,
@@ -615,42 +618,81 @@ bool Unique::get(TABLE *table)
bzero((char*) &sort_param,sizeof(sort_param));
sort_param.max_rows= elements;
- sort_param.sort_form=table;
+ 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.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.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,sort_buffer,file_ptr,&maxbuffer,&file))
+ 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 (merge_buffers(&sort_param, &file, outfile, sort_buffer, file_ptr,
+ 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;
+ error= 0;
err:
- x_free(sort_buffer);
if (flush_io_cache(outfile))
- error=1;
+ error= 1;
/* Setup io_cache for reading */
- save_pos=outfile->pos_in_file;
+ save_pos= outfile->pos_in_file;
if (reinit_io_cache(outfile,READ_CACHE,0L,0,0))
- error=1;
+ error= 1;
outfile->end_of_file=save_pos;
return error;
}
+
+
+/*
+ Modify the TABLE element so that when one calls init_records()
+ the rows will be read in priority order.
+*/
+
+bool Unique::get(TABLE *table)
+{
+ bool rc= 1;
+ uchar *sort_buffer= NULL;
+ table->sort.found_records= elements+tree.elements_in_tree;
+
+ if (my_b_tell(&file) == 0)
+ {
+ /* Whole tree is in memory; Don't use disk if you don't need to */
+ if ((record_pointers=table->sort.record_pointers= (uchar*)
+ my_malloc(size * tree.elements_in_tree, MYF(0))))
+ {
+ (void) tree_walk(&tree, (tree_walk_action) unique_write_to_ptrs,
+ this, left_root_right);
+ return 0;
+ }
+ }
+ /* Not enough memory; Save the result to file && free memory used by tree */
+ if (flush())
+ return 1;
+
+ ulong buff_sz= (max_in_memory_size / size + 1) * size;
+ if (!(sort_buffer= (uchar*) my_malloc(buff_sz, MYF(0))))
+ return 1;
+
+ if (merge(table, sort_buffer, FALSE))
+ goto err;
+ rc= 0;
+
+err:
+ x_free(sort_buffer);
+ return rc;
+}