diff --git a/mysql-test/main/cte_recursive.result b/mysql-test/main/cte_recursive.result index 9380237dce53b..30247c6d08231 100644 --- a/mysql-test/main/cte_recursive.result +++ b/mysql-test/main/cte_recursive.result @@ -6246,3 +6246,230 @@ SELECT 1 > ( WITH cte AS ( SELECT 1 FROM x ) SELECT 1 FROM cte ) ) SELECT 1 FROM x; ERROR HY000: Restrictions imposed on recursive definitions are violated for table 'x' # End of 10.11 tests +# +# Recursive CTE loses a row when the increment table is converted +# into an on-disk temporary table +# +CREATE TABLE t1 (n INT, pad CHAR(255) CHARACTER SET utf8mb4); +INSERT INTO t1 SELECT seq, REPEAT('x',255) FROM seq_1_to_64; +SET @save_tmp_memory_table_size= @@tmp_memory_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +# Each query below is run twice. The first run leaves the increment +# table in memory, the second forces it to be converted to an on-disk +# table while it is being filled. Both runs must return the same rows. +# +# The increment table overflows while the anchor part fills it +# +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +FLUSH STATUS; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, pad FROM t1 +UNION ALL +SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +# 0 = no temporary table was converted +SELECT VARIABLE_VALUE > 0 AS converted FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME = 'Created_tmp_disk_tables'; +converted +0 +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +FLUSH STATUS; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, pad FROM t1 +UNION ALL +SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +# 1 = a temporary table was converted +SELECT VARIABLE_VALUE > 0 AS converted FROM information_schema.SESSION_STATUS +WHERE VARIABLE_NAME = 'Created_tmp_disk_tables'; +converted +1 +# +# The increment table overflows during a recursive step +# +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, pad FROM t1 WHERE n = 1 +UNION ALL +SELECT r.lvl+1, t1.pad FROM r, t1 WHERE r.lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads +1 1 1 +2 64 64 +3 4096 4096 +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, pad FROM t1 WHERE n = 1 +UNION ALL +SELECT r.lvl+1, t1.pad FROM r, t1 WHERE r.lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads +1 1 1 +2 64 64 +3 4096 4096 +# +# The same, for a CTE that eliminates duplicates +# +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, pad FROM t1 +UNION +SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, pad FROM t1 +UNION +SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +# +# The same, when the result table's key does not fit an on-disk table +# and is replaced by a unique constraint. The result table's record +# then carries a trailing hash field that the increment table's record +# does not have, so the two records are of different length. +# +CREATE TABLE t2 (n INT, +p1 CHAR(200) CHARACTER SET utf8mb4, +p2 CHAR(200) CHARACTER SET utf8mb4, +p3 CHAR(200) CHARACTER SET utf8mb4); +INSERT INTO t2 +SELECT seq, REPEAT('a',200), REPEAT('b',200), REPEAT('c',200) FROM seq_1_to_64; +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, p1, p2, p3 FROM t2 +UNION +SELECT lvl+1, n, p1, p2, p3 FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(p3) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, p1, p2, p3 FROM t2 +UNION +SELECT lvl+1, n, p1, p2, p3 FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(p3) AS pads, SUM(n) AS sum_n +FROM r GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +DROP TABLE t2; +# +# A prepared statement re-executed after its increment table was +# converted on an earlier execution +# +PREPARE s FROM " +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, pad FROM t1 + UNION ALL + SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM r GROUP BY lvl ORDER BY lvl"; +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +EXECUTE s; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +EXECUTE s; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +EXECUTE s; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +DROP PREPARE s; +# +# Mutually recursive CTEs +# +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +WITH RECURSIVE +a AS (SELECT 1 AS lvl, n, pad FROM t1 +UNION ALL +SELECT lvl+1, n, pad FROM b WHERE lvl < 4), +b AS (SELECT lvl, n, pad FROM a) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM a GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +4 64 64 2080 +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +WITH RECURSIVE +a AS (SELECT 1 AS lvl, n, pad FROM t1 +UNION ALL +SELECT lvl+1, n, pad FROM b WHERE lvl < 4), +b AS (SELECT lvl, n, pad FROM a) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM a GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +4 64 64 2080 +# +# INSERT ... SELECT, which takes a different warning-handling path +# when the increment table is filled +# +CREATE TABLE t3 (lvl INT, n INT, pad CHAR(255) CHARACTER SET utf8mb4); +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +INSERT INTO t3 +WITH RECURSIVE r AS +(SELECT 1 AS lvl, n, pad FROM t1 +UNION ALL +SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, n, pad FROM r; +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n +FROM t3 GROUP BY lvl ORDER BY lvl; +lvl cnt pads sum_n +1 64 64 2080 +2 64 64 2080 +3 64 64 2080 +DROP TABLE t3; +SET SESSION tmp_memory_table_size= @save_tmp_memory_table_size; +SET SESSION max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1; +# End of 13.1 tests diff --git a/mysql-test/main/cte_recursive.test b/mysql-test/main/cte_recursive.test index fe3e5e8ab03f5..feabe591a8625 100644 --- a/mysql-test/main/cte_recursive.test +++ b/mysql-test/main/cte_recursive.test @@ -4257,3 +4257,180 @@ WITH RECURSIVE x AS ( SELECT 1 FROM x; --echo # End of 10.11 tests + +--echo # +--echo # Recursive CTE loses a row when the increment table is converted +--echo # into an on-disk temporary table +--echo # + +--source include/have_sequence.inc + +CREATE TABLE t1 (n INT, pad CHAR(255) CHARACTER SET utf8mb4); +INSERT INTO t1 SELECT seq, REPEAT('x',255) FROM seq_1_to_64; + +SET @save_tmp_memory_table_size= @@tmp_memory_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; + +--echo # Each query below is run twice. The first run leaves the increment +--echo # table in memory, the second forces it to be converted to an on-disk +--echo # table while it is being filled. Both runs must return the same rows. + +--echo # +--echo # The increment table overflows while the anchor part fills it +--echo # +let $q= +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, pad FROM t1 + UNION ALL + SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM r GROUP BY lvl ORDER BY lvl; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +FLUSH STATUS; +eval $q; +--echo # 0 = no temporary table was converted +SELECT VARIABLE_VALUE > 0 AS converted FROM information_schema.SESSION_STATUS + WHERE VARIABLE_NAME = 'Created_tmp_disk_tables'; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +FLUSH STATUS; +eval $q; +--echo # 1 = a temporary table was converted +SELECT VARIABLE_VALUE > 0 AS converted FROM information_schema.SESSION_STATUS + WHERE VARIABLE_NAME = 'Created_tmp_disk_tables'; + +--echo # +--echo # The increment table overflows during a recursive step +--echo # +let $q= +WITH RECURSIVE r AS + (SELECT 1 AS lvl, pad FROM t1 WHERE n = 1 + UNION ALL + SELECT r.lvl+1, t1.pad FROM r, t1 WHERE r.lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads + FROM r GROUP BY lvl ORDER BY lvl; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +eval $q; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +eval $q; + +--echo # +--echo # The same, for a CTE that eliminates duplicates +--echo # +let $q= +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, pad FROM t1 + UNION + SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM r GROUP BY lvl ORDER BY lvl; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +eval $q; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +eval $q; + +--echo # +--echo # The same, when the result table's key does not fit an on-disk table +--echo # and is replaced by a unique constraint. The result table's record +--echo # then carries a trailing hash field that the increment table's record +--echo # does not have, so the two records are of different length. +--echo # +CREATE TABLE t2 (n INT, + p1 CHAR(200) CHARACTER SET utf8mb4, + p2 CHAR(200) CHARACTER SET utf8mb4, + p3 CHAR(200) CHARACTER SET utf8mb4); +INSERT INTO t2 + SELECT seq, REPEAT('a',200), REPEAT('b',200), REPEAT('c',200) FROM seq_1_to_64; + +let $q= +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, p1, p2, p3 FROM t2 + UNION + SELECT lvl+1, n, p1, p2, p3 FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(p3) AS pads, SUM(n) AS sum_n + FROM r GROUP BY lvl ORDER BY lvl; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +eval $q; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +eval $q; + +DROP TABLE t2; + +--echo # +--echo # A prepared statement re-executed after its increment table was +--echo # converted on an earlier execution +--echo # +PREPARE s FROM " +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, pad FROM t1 + UNION ALL + SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM r GROUP BY lvl ORDER BY lvl"; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +EXECUTE s; +EXECUTE s; +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +EXECUTE s; +DROP PREPARE s; + +--echo # +--echo # Mutually recursive CTEs +--echo # +let $q= +WITH RECURSIVE + a AS (SELECT 1 AS lvl, n, pad FROM t1 + UNION ALL + SELECT lvl+1, n, pad FROM b WHERE lvl < 4), + b AS (SELECT lvl, n, pad FROM a) +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM a GROUP BY lvl ORDER BY lvl; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +eval $q; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +eval $q; + +--echo # +--echo # INSERT ... SELECT, which takes a different warning-handling path +--echo # when the increment table is filled +--echo # +CREATE TABLE t3 (lvl INT, n INT, pad CHAR(255) CHARACTER SET utf8mb4); +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +INSERT INTO t3 +WITH RECURSIVE r AS + (SELECT 1 AS lvl, n, pad FROM t1 + UNION ALL + SELECT lvl+1, n, pad FROM r WHERE lvl < 3) +SELECT lvl, n, pad FROM r; +SELECT lvl, COUNT(*) AS cnt, COUNT(pad) AS pads, SUM(n) AS sum_n + FROM t3 GROUP BY lvl ORDER BY lvl; +DROP TABLE t3; + +SET SESSION tmp_memory_table_size= @save_tmp_memory_table_size; +SET SESSION max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1; + +--echo # End of 13.1 tests diff --git a/mysql-test/main/cte_update_delete.result b/mysql-test/main/cte_update_delete.result index 6cda90fb9e2cd..14fd4b09dc78f 100644 --- a/mysql-test/main/cte_update_delete.result +++ b/mysql-test/main/cte_update_delete.result @@ -1374,5 +1374,6 @@ a 15 20 25 +drop view v1; drop table t1; # End of 12.2 tests diff --git a/mysql-test/main/cte_update_delete.test b/mysql-test/main/cte_update_delete.test index ab6a0b8b1903c..d49c7546e7d63 100644 --- a/mysql-test/main/cte_update_delete.test +++ b/mysql-test/main/cte_update_delete.test @@ -594,6 +594,7 @@ with recursive cte as (select a from t1 union select a from cte where a < 2) delete from v1 using v1, cte where v1.a = cte.a; select * from t1 order by 1; +drop view v1; drop table t1; --echo # End of 12.2 tests diff --git a/mysql-test/main/union.result b/mysql-test/main/union.result index 351beae4b1a19..f551c34cbd6e8 100644 --- a/mysql-test/main/union.result +++ b/mysql-test/main/union.result @@ -2848,3 +2848,38 @@ res 3 4 2 +# +# A result table that is converted to an on-disk table after its index +# has been disabled +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 SELECT seq FROM seq_1_to_5000; +SET @save_tmp_memory_table_size= @@tmp_memory_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +# The index of the result table is dropped once the last distinct part +# has been read, so the trailing UNION ALL part fills a table with no +# index, and that table then outgrows the in-memory limit. +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +SELECT COUNT(*) AS cnt, COUNT(DISTINCT a) AS distinct_a FROM ( +SELECT a FROM t1 WHERE a <= 100 +UNION +SELECT a FROM t1 WHERE a <= 100 +UNION ALL +SELECT a FROM t1) d; +cnt distinct_a +5100 5000 +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +SELECT COUNT(*) AS cnt, COUNT(DISTINCT a) AS distinct_a FROM ( +SELECT a FROM t1 WHERE a <= 100 +UNION +SELECT a FROM t1 WHERE a <= 100 +UNION ALL +SELECT a FROM t1) d; +cnt distinct_a +5100 5000 +SET SESSION tmp_memory_table_size= @save_tmp_memory_table_size; +SET SESSION max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1; +# End of 13.1 tests diff --git a/mysql-test/main/union.test b/mysql-test/main/union.test index 70a3d0fad5913..d10658d2ea9d4 100644 --- a/mysql-test/main/union.test +++ b/mysql-test/main/union.test @@ -2050,3 +2050,39 @@ select 1 as res union select 2 union all select 1 union distinct select 3; select 1 as res union select 2 union all select 1 union distinct select 3 union all select 2; select 1 as res union select 2 union all select 1 union distinct select 3 union all select 2 union distinct select 5; select truncate(seq/2,0)+1 as res from seq_1_to_6 union all select 2 union all select 1 union distinct select 3 union all select 2; + +--echo # +--echo # A result table that is converted to an on-disk table after its index +--echo # has been disabled +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 SELECT seq FROM seq_1_to_5000; + +SET @save_tmp_memory_table_size= @@tmp_memory_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; + +--echo # The index of the result table is dropped once the last distinct part +--echo # has been read, so the trailing UNION ALL part fills a table with no +--echo # index, and that table then outgrows the in-memory limit. +let $q= +SELECT COUNT(*) AS cnt, COUNT(DISTINCT a) AS distinct_a FROM ( + SELECT a FROM t1 WHERE a <= 100 + UNION + SELECT a FROM t1 WHERE a <= 100 + UNION ALL + SELECT a FROM t1) d; + +SET SESSION tmp_memory_table_size= 16384; +SET SESSION max_heap_table_size= 16384; +eval $q; + +SET SESSION tmp_memory_table_size= 16777216; +SET SESSION max_heap_table_size= 16777216; +eval $q; + +SET SESSION tmp_memory_table_size= @save_tmp_memory_table_size; +SET SESSION max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1; + +--echo # End of 13.1 tests diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 8bf48bc4487ba..6e3b878cf9f0f 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -287,6 +287,16 @@ int select_union_recursive::send_data(List &values) if ((err= incr_table->file->ha_write_tmp_row(table->record[0]))) { bool is_duplicate; + /* + create_internal_tmp_table_from_heap() appends the row that did not fit + from the record[0] of the table it converts, so the row has to be in + the increment table's own record buffer. Only reclength bytes of the + increment table are copied: if the union result table uses a unique + constraint its record has a trailing hash field, which is not part of + the increment table's record (see the assert above). + */ + memcpy(incr_table->record[0], table->record[0], + incr_table->s->reclength); rc= create_internal_tmp_table_from_heap(thd, incr_table, tmp_table_param.start_recinfo, &tmp_table_param.recinfo,