Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mysql-test/main/func_gconcat.result
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ group_concat(distinct a, c)
00,01,10,11,31
select group_concat(distinct a, c order by a) from t1;
group_concat(distinct a, c order by a)
00,01,11,10,31
01,00,11,10,31
select group_concat(distinct a, c) from t1;
group_concat(distinct a, c)
00,01,10,11,31
Expand Down
132 changes: 132 additions & 0 deletions mysql-test/main/gconcat_distinct_spill.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#
# Each block records the answer computed with memory to spare, then
# recomputes it with the duplicate filter starved, and compares.
#
CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, a VARCHAR(100) NOT NULL);
INSERT INTO t1 (a) SELECT LPAD(seq, 4, '0') FROM seq_1_to_50;
INSERT INTO t1 (a) SELECT a FROM t1 ORDER BY pk;
SELECT COUNT(*) AS rows_in_table, COUNT(DISTINCT a) AS distinct_values FROM t1;
rows_in_table distinct_values
100 50
SELECT GROUP_CONCAT(DISTINCT a) INTO @gc FROM t1;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc_order FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a) INTO @ja FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) INTO @ja_order FROM t1;
SET @@tmp_memory_table_size=0;
SELECT GROUP_CONCAT(DISTINCT a) = @gc AS gc_unchanged FROM t1;
gc_unchanged
1
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) = @gc_order AS gc_order_unchanged FROM t1;
gc_order_unchanged
1
SELECT JSON_ARRAYAGG(DISTINCT a) = @ja AS ja_unchanged FROM t1;
ja_unchanged
1
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) = @ja_order AS ja_order_unchanged FROM t1;
ja_order_unchanged
1
SET @@tmp_memory_table_size=DEFAULT;
DROP TABLE t1;
#
# Values wide enough that the filter flushes on nearly every row.
# Here the ORDER BY case used to return a single value out of 30.
#
CREATE TABLE t2 (a VARCHAR(2000)) AS
SELECT CONCAT(seq, REPEAT('.', 1990)) AS a FROM seq_1_to_30;
SELECT COUNT(*) AS rows_in_table, COUNT(DISTINCT a) AS distinct_values FROM t2;
rows_in_table distinct_values
30 30
SELECT GROUP_CONCAT(DISTINCT a) INTO @gc FROM t2;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc_order FROM t2;
SET @@tmp_memory_table_size=1000, @@max_heap_table_size=1000;
Warnings:
Warning 1292 Truncated incorrect tmp_memory_table_size value: '1000'
Warning 1292 Truncated incorrect max_heap_table_size value: '1000'
SELECT GROUP_CONCAT(DISTINCT a) = @gc AS gc_unchanged FROM t2;
gc_unchanged
1
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) = @gc_order AS gc_order_unchanged FROM t2;
gc_order_unchanged
1
SET @@tmp_memory_table_size=DEFAULT, @@max_heap_table_size=DEFAULT;
DROP TABLE t2;
#
# ORDER BY does not order the rows that tie on the ordering
# expression. Which of them comes first is not specified, but it
# must not depend on the memory available or on the order the rows
# are read in.
#
CREATE TABLE t3 (a BIT(2), b VARCHAR(10), c BIT);
INSERT INTO t3 VALUES (1, 'a', 0), (0, 'b', 1), (0, 'c', 0), (3, 'd', 1),
(1, 'e', 1), (3, 'f', 1), (0, 'g', 1);
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS at_default FROM t3;
at_default
01,00,11,10,31
SET @@tmp_memory_table_size=0;
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS at_zero FROM t3;
at_zero
01,00,11,10,31
SET @@tmp_memory_table_size=DEFAULT;
DELETE FROM t3;
INSERT INTO t3 VALUES (0, 'c', 0), (0, 'b', 1), (1, 'a', 0), (3, 'd', 1),
(1, 'e', 1), (3, 'f', 1), (0, 'g', 1);
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS reversed_scan_order FROM t3;
reversed_scan_order
01,00,11,10,31
DROP TABLE t3;
#
# The sort tree can overflow too. Starving it makes repack_tree()
# cut rows out of the group, which is expected, but the rows that
# do come back must still be deduplicated and still be in order.
#
CREATE TABLE t4 (pk INT AUTO_INCREMENT PRIMARY KEY, a VARCHAR(20));
INSERT INTO t4 (a) SELECT LPAD(seq, 6, '0') FROM seq_1_to_200;
INSERT INTO t4 (a) SELECT a FROM t4 ORDER BY pk;
SET @@tmp_memory_table_size=0, @@group_concat_max_len=4000;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc FROM t4;
Warnings:
Warning 1260 Row 32 was cut by group_concat()
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) INTO @ja FROM t4;
Warnings:
Warning 1260 Row 32 was cut by json_arrayagg()
SET @@tmp_memory_table_size=DEFAULT, @@group_concat_max_len=DEFAULT;
SELECT COUNT(*) = COUNT(DISTINCT val) AS gc_no_duplicates,
GROUP_CONCAT(val ORDER BY val) = @gc AS gc_ascending
FROM (SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(@gc, ',', seq), ',', -1) AS val
FROM seq_1_to_500
WHERE seq <= 1 + LENGTH(@gc) - LENGTH(REPLACE(@gc, ',', ''))) split;
gc_no_duplicates gc_ascending
1 1
SELECT JSON_VALID(@ja) AS ja_valid,
COUNT(*) = COUNT(DISTINCT val) AS ja_no_duplicates,
JSON_ARRAYAGG(val ORDER BY val) = @ja AS ja_ascending
FROM (SELECT JSON_UNQUOTE(JSON_EXTRACT(@ja, CONCAT('$[', seq - 1, ']'))) AS val
FROM seq_1_to_500 WHERE seq <= JSON_LENGTH(@ja)) split;
ja_valid ja_no_duplicates ja_ascending
1 1 1
DROP TABLE t4;
#
# Running out of LIMIT stops the walk of the duplicate filter early,
# but nothing is lost by it. It must not be reported as a cut value.
#
CREATE TABLE t5 (a VARCHAR(100));
INSERT INTO t5 SELECT LPAD(seq MOD 200, 100, '0') FROM seq_1_to_600;
SELECT COUNT(DISTINCT a) AS distinct_values FROM t5;
distinct_values
200
SELECT LENGTH(GROUP_CONCAT(DISTINCT a LIMIT 5)) AS gc_len FROM t5;
gc_len
504
SELECT LENGTH(GROUP_CONCAT(DISTINCT a ORDER BY a LIMIT 5)) AS gc_order_len FROM t5;
gc_order_len
504
SET @@tmp_memory_table_size=0;
SELECT LENGTH(GROUP_CONCAT(DISTINCT a LIMIT 5)) AS gc_len_spilled FROM t5;
gc_len_spilled
504
SELECT LENGTH(GROUP_CONCAT(DISTINCT a ORDER BY a LIMIT 5)) AS gc_order_len_spilled
FROM t5;
gc_order_len_spilled
504
SET @@tmp_memory_table_size=DEFAULT;
DROP TABLE t5;
111 changes: 111 additions & 0 deletions mysql-test/main/gconcat_distinct_spill.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#
# GROUP_CONCAT(DISTINCT ...) and JSON_ARRAYAGG(DISTINCT ...) filter
# duplicates with a Unique object, which flushes to disk when it runs out
# of memory. The answer must not depend on whether that flush happened.
#
--source include/have_sequence.inc

--echo #
--echo # Each block records the answer computed with memory to spare, then
--echo # recomputes it with the duplicate filter starved, and compares.
--echo #

CREATE TABLE t1 (pk INT AUTO_INCREMENT PRIMARY KEY, a VARCHAR(100) NOT NULL);
INSERT INTO t1 (a) SELECT LPAD(seq, 4, '0') FROM seq_1_to_50;
INSERT INTO t1 (a) SELECT a FROM t1 ORDER BY pk;
SELECT COUNT(*) AS rows_in_table, COUNT(DISTINCT a) AS distinct_values FROM t1;

SELECT GROUP_CONCAT(DISTINCT a) INTO @gc FROM t1;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc_order FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a) INTO @ja FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) INTO @ja_order FROM t1;

SET @@tmp_memory_table_size=0;
SELECT GROUP_CONCAT(DISTINCT a) = @gc AS gc_unchanged FROM t1;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) = @gc_order AS gc_order_unchanged FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a) = @ja AS ja_unchanged FROM t1;
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) = @ja_order AS ja_order_unchanged FROM t1;
SET @@tmp_memory_table_size=DEFAULT;
DROP TABLE t1;

--echo #
--echo # Values wide enough that the filter flushes on nearly every row.
--echo # Here the ORDER BY case used to return a single value out of 30.
--echo #
CREATE TABLE t2 (a VARCHAR(2000)) AS
SELECT CONCAT(seq, REPEAT('.', 1990)) AS a FROM seq_1_to_30;
SELECT COUNT(*) AS rows_in_table, COUNT(DISTINCT a) AS distinct_values FROM t2;

SELECT GROUP_CONCAT(DISTINCT a) INTO @gc FROM t2;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc_order FROM t2;

SET @@tmp_memory_table_size=1000, @@max_heap_table_size=1000;
SELECT GROUP_CONCAT(DISTINCT a) = @gc AS gc_unchanged FROM t2;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) = @gc_order AS gc_order_unchanged FROM t2;
SET @@tmp_memory_table_size=DEFAULT, @@max_heap_table_size=DEFAULT;
DROP TABLE t2;

--echo #
--echo # ORDER BY does not order the rows that tie on the ordering
--echo # expression. Which of them comes first is not specified, but it
--echo # must not depend on the memory available or on the order the rows
--echo # are read in.
--echo #
CREATE TABLE t3 (a BIT(2), b VARCHAR(10), c BIT);
INSERT INTO t3 VALUES (1, 'a', 0), (0, 'b', 1), (0, 'c', 0), (3, 'd', 1),
(1, 'e', 1), (3, 'f', 1), (0, 'g', 1);
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS at_default FROM t3;
SET @@tmp_memory_table_size=0;
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS at_zero FROM t3;
SET @@tmp_memory_table_size=DEFAULT;

DELETE FROM t3;
INSERT INTO t3 VALUES (0, 'c', 0), (0, 'b', 1), (1, 'a', 0), (3, 'd', 1),
(1, 'e', 1), (3, 'f', 1), (0, 'g', 1);
SELECT GROUP_CONCAT(DISTINCT a, c ORDER BY a) AS reversed_scan_order FROM t3;
DROP TABLE t3;

--echo #
--echo # The sort tree can overflow too. Starving it makes repack_tree()
--echo # cut rows out of the group, which is expected, but the rows that
--echo # do come back must still be deduplicated and still be in order.
--echo #
CREATE TABLE t4 (pk INT AUTO_INCREMENT PRIMARY KEY, a VARCHAR(20));
INSERT INTO t4 (a) SELECT LPAD(seq, 6, '0') FROM seq_1_to_200;
INSERT INTO t4 (a) SELECT a FROM t4 ORDER BY pk;

SET @@tmp_memory_table_size=0, @@group_concat_max_len=4000;
SELECT GROUP_CONCAT(DISTINCT a ORDER BY a) INTO @gc FROM t4;
SELECT JSON_ARRAYAGG(DISTINCT a ORDER BY a) INTO @ja FROM t4;
SET @@tmp_memory_table_size=DEFAULT, @@group_concat_max_len=DEFAULT;

SELECT COUNT(*) = COUNT(DISTINCT val) AS gc_no_duplicates,
GROUP_CONCAT(val ORDER BY val) = @gc AS gc_ascending
FROM (SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(@gc, ',', seq), ',', -1) AS val
FROM seq_1_to_500
WHERE seq <= 1 + LENGTH(@gc) - LENGTH(REPLACE(@gc, ',', ''))) split;

SELECT JSON_VALID(@ja) AS ja_valid,
COUNT(*) = COUNT(DISTINCT val) AS ja_no_duplicates,
JSON_ARRAYAGG(val ORDER BY val) = @ja AS ja_ascending
FROM (SELECT JSON_UNQUOTE(JSON_EXTRACT(@ja, CONCAT('$[', seq - 1, ']'))) AS val
FROM seq_1_to_500 WHERE seq <= JSON_LENGTH(@ja)) split;
DROP TABLE t4;

--echo #
--echo # Running out of LIMIT stops the walk of the duplicate filter early,
--echo # but nothing is lost by it. It must not be reported as a cut value.
--echo #
CREATE TABLE t5 (a VARCHAR(100));
INSERT INTO t5 SELECT LPAD(seq MOD 200, 100, '0') FROM seq_1_to_600;
SELECT COUNT(DISTINCT a) AS distinct_values FROM t5;

SELECT LENGTH(GROUP_CONCAT(DISTINCT a LIMIT 5)) AS gc_len FROM t5;
SELECT LENGTH(GROUP_CONCAT(DISTINCT a ORDER BY a LIMIT 5)) AS gc_order_len FROM t5;

SET @@tmp_memory_table_size=0;
SELECT LENGTH(GROUP_CONCAT(DISTINCT a LIMIT 5)) AS gc_len_spilled FROM t5;
SELECT LENGTH(GROUP_CONCAT(DISTINCT a ORDER BY a LIMIT 5)) AS gc_order_len_spilled
FROM t5;
SET @@tmp_memory_table_size=DEFAULT;
DROP TABLE t5;
30 changes: 30 additions & 0 deletions mysql-test/main/gconcat_distinct_walk_fail.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE t1 (a VARCHAR(100));
INSERT INTO t1 SELECT LPAD(seq MOD 200, 100, '0') FROM seq_1_to_600;
#
# Starve the duplicate filter so that it spills and the walk has to
# merge, then make the merging walk fail.
#
SET @@tmp_memory_table_size=0;
SET SESSION debug_dbug='+d,unique_walk_merge_fail';
SELECT LENGTH(GROUP_CONCAT(DISTINCT a)) AS gc_len FROM t1;
gc_len
0
Warnings:
Warning 1260 Row 0 was cut by group_concat()
SELECT JSON_LENGTH(JSON_ARRAYAGG(DISTINCT a)) AS ja_len FROM t1;
ja_len
0
Warnings:
Warning 1260 Row 0 was cut by json_arrayagg()
SET SESSION debug_dbug=DEFAULT;
SET @@tmp_memory_table_size=DEFAULT;
#
# Without the injected failure the same queries are complete and quiet.
#
SELECT LENGTH(GROUP_CONCAT(DISTINCT a)) AS gc_len FROM t1;
gc_len
20199
SELECT JSON_LENGTH(JSON_ARRAYAGG(DISTINCT a)) AS ja_len FROM t1;
ja_len
200
DROP TABLE t1;
30 changes: 30 additions & 0 deletions mysql-test/main/gconcat_distinct_walk_fail.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# GROUP_CONCAT(DISTINCT ...) builds its result by walking the Unique that
# filtered the duplicates. That walk merges back what the Unique spilled to
# disk, so it can fail on its own, without the callback that appends the
# rows getting a chance to say anything. The result is then short and the
# user has to be told about it.
#
--source include/have_debug.inc
--source include/have_sequence.inc

CREATE TABLE t1 (a VARCHAR(100));
INSERT INTO t1 SELECT LPAD(seq MOD 200, 100, '0') FROM seq_1_to_600;

--echo #
--echo # Starve the duplicate filter so that it spills and the walk has to
--echo # merge, then make the merging walk fail.
--echo #
SET @@tmp_memory_table_size=0;
SET SESSION debug_dbug='+d,unique_walk_merge_fail';
SELECT LENGTH(GROUP_CONCAT(DISTINCT a)) AS gc_len FROM t1;
SELECT JSON_LENGTH(JSON_ARRAYAGG(DISTINCT a)) AS ja_len FROM t1;
SET SESSION debug_dbug=DEFAULT;
SET @@tmp_memory_table_size=DEFAULT;

--echo #
--echo # Without the injected failure the same queries are complete and quiet.
--echo #
SELECT LENGTH(GROUP_CONCAT(DISTINCT a)) AS gc_len FROM t1;
SELECT JSON_LENGTH(JSON_ARRAYAGG(DISTINCT a)) AS ja_len FROM t1;
DROP TABLE t1;
Loading
Loading