From 44ef1bb7d985a7716625a6c186ec217b161d0ee5 Mon Sep 17 00:00:00 2001
From: Andrey Borodin <amborodin@acm.org>
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;
