From 935719795667e7c193d13728acf810e5f43a8f47 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Wed, 12 Nov 2025 20:30:47 +0900
Subject: [PATCH] Add tests for maximum tuple and index entry sizes.

Add regression tests that verify size limit enforcement for heap
tuples and index entries at their boundaries.  These tests ensure
that the limits are correctly applied and appropriate error messages
are shown when exceeded.

For heap tuples, test the maximum size of 8160 bytes by inserting
8128-byte and 8129-byte text values.  The former succeeds while the
latter fails because MAXALIGN rounds the 8161-byte tuple to 8168
bytes, exceeding the limit.

For B-tree indexes, test the maximum entry size of about 2704 bytes
(1/3 of page size to ensure proper page splits) by indexing 2692-byte
and 2693-byte text values.

For GiST indexes, test the maximum siglen parameter value of 2024 by
creating indexes with siglen=2024 and siglen=2025.  The limit comes
from the page size constraints for GiST index tuples.

These boundary tests use values that differ by only 1 byte or unit
to clearly demonstrate the exact limits, making it easier to catch
regressions if these limits change in the future.
---
 src/test/regress/expected/btree_index.out | 13 +++++++++++++
 src/test/regress/expected/insert.out      | 11 +++++++++++
 src/test/regress/expected/tsearch.out     |  8 ++++++--
 src/test/regress/sql/btree_index.sql      | 10 ++++++++++
 src/test/regress/sql/insert.sql           | 14 ++++++++++++++
 src/test/regress/sql/tsearch.sql          |  7 +++++--
 6 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 21dc9b5783a..5a042306049 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -736,3 +736,16 @@ ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 ERROR:  ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_part_idx"
 DETAIL:  This operation is not supported for partitioned indexes.
 DROP TABLE btree_part;
+-- Test B-tree index maximum entry size boundary
+CREATE TABLE btree_max_entry_test (i INT PRIMARY KEY, t TEXT);
+CREATE INDEX btree_max_entry_idx ON btree_max_entry_test(t);
+ALTER TABLE btree_max_entry_test ALTER COLUMN t SET STORAGE EXTERNAL;
+-- should succeed
+INSERT INTO btree_max_entry_test VALUES (1, repeat('a', 2692));
+-- should fail
+INSERT INTO btree_max_entry_test VALUES (2, repeat('a', 2693));
+ERROR:  index row size 2712 exceeds btree version 4 maximum 2704 for index "btree_max_entry_idx"
+DETAIL:  Index row references tuple (0,2) in relation "btree_max_entry_test".
+HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
+Consider a function index of an MD5 hash of the value, or use full text indexing.
+DROP TABLE btree_max_entry_test;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index cf4b5221a8d..d9702ad42eb 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -103,6 +103,17 @@ INSERT INTO large_tuple_test (select 3, NULL);
 INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
 DROP TABLE large_tuple_test;
 --
+-- test maximum tuple size
+--
+CREATE TABLE max_tuple_test (a int, b text);
+ALTER TABLE max_tuple_test ALTER COLUMN b SET STORAGE plain;
+-- should succeed
+INSERT INTO max_tuple_test VALUES (1, repeat('x', 8128));
+-- should fail
+INSERT INTO max_tuple_test VALUES (2, repeat('x', 8129));
+ERROR:  row is too big: size 8168, maximum size 8160
+DROP TABLE max_tuple_test;
+--
 -- check indirection (field/array assignment), cf bug #14265
 --
 -- these tests are aware that transformInsertStmt has 3 separate code paths
diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out
index 9fad6c8b04b..79d75f77a91 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -528,9 +528,13 @@ ERROR:  unrecognized parameter "foo"
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
 ERROR:  value 0 out of bounds for option "siglen"
 DETAIL:  Valid values are between "1" and "2024".
-CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2048));
-ERROR:  value 2048 out of bounds for option "siglen"
+-- should fail
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2025));
+ERROR:  value 2025 out of bounds for option "siglen"
 DETAIL:  Valid values are between "1" and "2024".
+-- should succeed
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=2024));
+DROP INDEX wowidx2;
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar'));
 ERROR:  unrecognized parameter "foo"
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200));
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 6aaaa386abc..b736db8dbe6 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -438,3 +438,13 @@ CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id);
 CREATE INDEX btree_part_idx ON btree_part(id);
 ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 DROP TABLE btree_part;
+
+-- Test B-tree index maximum entry size boundary
+CREATE TABLE btree_max_entry_test (i INT PRIMARY KEY, t TEXT);
+CREATE INDEX btree_max_entry_idx ON btree_max_entry_test(t);
+ALTER TABLE btree_max_entry_test ALTER COLUMN t SET STORAGE EXTERNAL;
+-- should succeed
+INSERT INTO btree_max_entry_test VALUES (1, repeat('a', 2692));
+-- should fail
+INSERT INTO btree_max_entry_test VALUES (2, repeat('a', 2693));
+DROP TABLE btree_max_entry_test;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 2b086eeb6d7..521f30a5e3c 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -59,6 +59,20 @@ INSERT INTO large_tuple_test (select 4, repeat('a', 8126));
 
 DROP TABLE large_tuple_test;
 
+--
+-- test maximum tuple size
+--
+CREATE TABLE max_tuple_test (a int, b text);
+ALTER TABLE max_tuple_test ALTER COLUMN b SET STORAGE plain;
+
+-- should succeed
+INSERT INTO max_tuple_test VALUES (1, repeat('x', 8128));
+
+-- should fail
+INSERT INTO max_tuple_test VALUES (2, repeat('x', 8129));
+
+DROP TABLE max_tuple_test;
+
 --
 -- check indirection (field/array assignment), cf bug #14265
 --
diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql
index fbd26cdba45..158ccf9922f 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -146,10 +146,13 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!wd:D';
 -- Test siglen parameter of GiST tsvector_ops
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
-CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2048));
+-- should fail
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=2025));
+-- should succeed
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=2024));
+DROP INDEX wowidx2;
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar'));
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200));
-
 CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1));
 
 \d test_tsvector
-- 
2.50.1 (Apple Git-155)

