From 18925af4dd7115a15bdf8b8ea9604b51e45081f6 Mon Sep 17 00:00:00 2001 From: Ewan Young Date: Thu, 9 Jul 2026 00:52:01 +0800 Subject: [PATCH v2] Fix parallel GIN index build with keys larger than 65535 (fail-fast variant) bytes During a parallel GIN build each key is serialized into a GinTuple, a transient representation used only while sorting. _gin_build_tuple() lays out the whole tuple -- the palloc size, the key memcpy, and the offset to the posting list -- from a local "int keylen". But the stored GinTuple.keylen field was uint16, so a key wider than 65535 bytes had its stored length silently truncated. On read-back, GinTupleGetFirst() and _gin_parse_tuple_items() recompute the posting-list offset from the truncated keylen and land inside the key data. ginPostingListDecodeAllSegments() then decodes garbage, tripping an assertion (or reading past the allocation in a non-assert build). Only parallel builds are affected, because only the parallel path materializes a GinTuple. Widen keylen so the stored length matches the length the rest of _gin_build_tuple() already computes, and make the type consistent -- it was uint16 in GinTuple, Size in GinBuffer, and int in the _gin_build_tuple() local variable -- by using int throughout. In addition, reject in _gin_build_tuple() any key whose (uncompressed) length exceeds GinMaxItemSize, so a parallel build fails fast rather than serializing a GinTuple that GinFormTuple() would later be unable to store. Bug: #19545 Reported-by: Yuelin Wang <1217816127@qq.com> Discussion: https://postgr.es/m/19545-DUMMY-REPLACE-WITH-REAL-MSGID@postgresql.org --- src/backend/access/gin/gininsert.c | 19 ++++++++++++++++++- src/include/access/gin_tuple.h | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index cb9ed3b563c..c2365afd1fd 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -1190,7 +1190,7 @@ typedef struct GinBuffer OffsetNumber attnum; GinNullCategory category; Datum key; /* 0 if no key (and keylen == 0) */ - Size keylen; /* number of bytes (not typlen) */ + int keylen; /* number of bytes (not typlen) */ /* type info */ int16 typlen; @@ -2281,6 +2281,23 @@ _gin_build_tuple(OffsetNumber attrnum, unsigned char category, else elog(ERROR, "unexpected typlen value (%d)", typlen); + /* + * Reject a key that cannot possibly be stored in the index, so that a + * parallel build fails fast instead of serializing a GinTuple that the + * leader will be unable to write out. This mirrors the GinMaxItemSize + * limit enforced by GinFormTuple() when the merged tuple is finally + * written. + * + * Note the key here is uncompressed; GinFormTuple() applies inline + * compression (via index_form_tuple) before its own check, so this is + * more conservative and the GinFormTuple() check is still required. + */ + if (keylen > GinMaxItemSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("index row size %d exceeds maximum %zu for a GIN index", + keylen, (Size) GinMaxItemSize))); + /* compress the item pointers */ ncompressed = 0; compresslen = 0; diff --git a/src/include/access/gin_tuple.h b/src/include/access/gin_tuple.h index 7bde05e2de4..9ca578652e7 100644 --- a/src/include/access/gin_tuple.h +++ b/src/include/access/gin_tuple.h @@ -23,7 +23,7 @@ typedef struct GinTuple { int tuplen; /* length of the whole tuple */ OffsetNumber attrnum; /* attnum of index key */ - uint16 keylen; /* bytes in data for key value */ + int keylen; /* bytes in data for key value */ int16 typlen; /* typlen for key */ bool typbyval; /* typbyval for key */ signed char category; /* category: normal or NULL? */ -- 2.47.3