Hi hackers,
I'd like to propose adding 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.
The patch adds three boundary tests:
1. Heap tuple maximum size (8160 bytes)
Tests 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.
2. B-tree index maximum entry size (approximately 2704 bytes)
Tests indexing 2692-byte and 2693-byte text values. The limit is 1/3 of
page size to ensure proper page splits.
3. GiST index maximum siglen parameter (2024)
Tests 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.
The tests are added to existing test files (insert.sql, btree_index.sql, and
tsearch.sql) with their corresponding expected outputs.
Tested on: REL_14_STABLE through master
Note: Applies cleanly to REL_15_STABLE and later.
REL_14_STABLE requires minor adjustments due to test file differences.
Patch attached.
Best regards,
Henson Choi
From 935719795667e7c193d13728acf810e5f43a8f47 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
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)
From 052c14eee9525d34958525241c1b910f432f14e1 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
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 c7328b1dc4f..5c705902a10 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -341,3 +341,16 @@ CREATE INDEX btree_part_idx ON btree_part(id);
ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
ERROR: "btree_part_idx" is not a table, materialized view, or foreign table
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 4e90fe5686f..16c650791c0 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 629147c5b11..96ed0bae9d4 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -518,9 +518,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 c34502249f3..3541fc9a527 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -183,3 +183,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 b40a1b99cc6..daf424c02dc 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 0a90c1b539d..fc832f9743a 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -132,10 +132,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)