From 44ef1bb7d985a7716625a6c186ec217b161d0ee5 Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Sun, 9 Aug 2026 11:10:45 +0500 Subject: [PATCH 1/2] Add regression coverage for cube GiST page splits Cube GiST's picksplit method can produce extremely unbalanced pages for sorted input. Existing tests do not exercise incremental page splits or mixed-dimensional keys. Check index size after sorted insertion and searches after splits of cubes with varying dimensionality. Make an existing coordinate-order test deterministic when two cubes have the same coordinate. --- contrib/cube/expected/cube.out | 43 ++++++++++++++++++++++++++++++---- contrib/cube/sql/cube.sql | 31 ++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out index 47787c50bd9..aac1c6a0dc6 100644 --- a/contrib/cube/expected/cube.out +++ b/contrib/cube/expected/cube.out @@ -1637,7 +1637,7 @@ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound 240 | (337, 455),(240, 359) (15 rows) -SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2, c LIMIT 15; -- ascending by right bound ?column? | c ----------+--------------------------- 0 | (0, 100000) @@ -1651,8 +1651,8 @@ SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right boun 167 | (167, 17214),(92, 17184) 207 | (207, 40886),(179, 40879) 259 | (259, 1850),(175, 1820) - 270 | (270, 29508),(264, 29440) 270 | (270, 32616),(226, 32607) + 270 | (270, 29508),(264, 29440) 288 | (288, 49588),(204, 49571) 318 | (318, 31489),(235, 31404) (15 rows) @@ -1832,7 +1832,7 @@ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound 240 | (337, 455),(240, 359) (15 rows) -SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2, c LIMIT 15; -- ascending by right bound ?column? | c ----------+--------------------------- 0 | (0, 100000) @@ -1846,8 +1846,8 @@ SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right boun 167 | (167, 17214),(92, 17184) 207 | (207, 40886),(179, 40879) 259 | (259, 1850),(175, 1820) - 270 | (270, 29508),(264, 29440) 270 | (270, 32616),(226, 32607) + 270 | (270, 29508),(264, 29440) 288 | (288, 49588),(204, 49571) 318 | (318, 31489),(235, 31404) (15 rows) @@ -1973,3 +1973,38 @@ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upp (15 rows) RESET enable_indexscan; +-- Check that sorted input does not produce pathologically unbalanced splits. +CREATE TABLE cube_picksplit_test (id int, c cube); +CREATE INDEX cube_picksplit_test_idx ON cube_picksplit_test USING gist (c); +INSERT INTO cube_picksplit_test +SELECT g, cube(ARRAY[g::float8 / 400, g::float8 / 400 + 1]) +FROM generate_series(1, 400) g; +SELECT pg_relation_size('cube_picksplit_test_idx') < + 20 * current_setting('block_size')::int AS balanced; + balanced +---------- + t +(1 row) + +-- Exercise splits containing cubes with different dimensionalities. +TRUNCATE cube_picksplit_test; +INSERT INTO cube_picksplit_test +SELECT g, cube(ARRAY(SELECT g::float8 / 20 + d + FROM generate_series(1, dim) d), + ARRAY(SELECT g::float8 / 20 + d + 1 + FROM generate_series(1, dim) d)) +FROM unnest(ARRAY[1, 2, 3, 10, 100]) dim +CROSS JOIN generate_series(1, 20) g; +SET enable_seqscan = false; +SELECT count(*) FROM cube_picksplit_test +WHERE c <@ cube(ARRAY(SELECT -1000::float8 + FROM generate_series(1, 100)), + ARRAY(SELECT 1000::float8 + FROM generate_series(1, 100))); + count +------- + 100 +(1 row) + +RESET enable_seqscan; +DROP TABLE cube_picksplit_test; diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql index eec90d21ee3..90113a2d328 100644 --- a/contrib/cube/sql/cube.sql +++ b/contrib/cube/sql/cube.sql @@ -411,7 +411,7 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c -- Test sorting by coordinates SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound -SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2, c LIMIT 15; -- ascending by right bound SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound @@ -428,7 +428,7 @@ RESET extra_float_digits; SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound -SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2, c LIMIT 15; -- ascending by right bound SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound @@ -436,3 +436,30 @@ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by rig SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound RESET enable_indexscan; + +-- Check that sorted input does not produce pathologically unbalanced splits. +CREATE TABLE cube_picksplit_test (id int, c cube); +CREATE INDEX cube_picksplit_test_idx ON cube_picksplit_test USING gist (c); +INSERT INTO cube_picksplit_test +SELECT g, cube(ARRAY[g::float8 / 400, g::float8 / 400 + 1]) +FROM generate_series(1, 400) g; +SELECT pg_relation_size('cube_picksplit_test_idx') < + 20 * current_setting('block_size')::int AS balanced; + +-- Exercise splits containing cubes with different dimensionalities. +TRUNCATE cube_picksplit_test; +INSERT INTO cube_picksplit_test +SELECT g, cube(ARRAY(SELECT g::float8 / 20 + d + FROM generate_series(1, dim) d), + ARRAY(SELECT g::float8 / 20 + d + 1 + FROM generate_series(1, dim) d)) +FROM unnest(ARRAY[1, 2, 3, 10, 100]) dim +CROSS JOIN generate_series(1, 20) g; +SET enable_seqscan = false; +SELECT count(*) FROM cube_picksplit_test +WHERE c <@ cube(ARRAY(SELECT -1000::float8 + FROM generate_series(1, 100)), + ARRAY(SELECT 1000::float8 + FROM generate_series(1, 100))); +RESET enable_seqscan; +DROP TABLE cube_picksplit_test;