From 2e2d5c5cec2cefd04ae64486a8333e4ed4191112 Mon Sep 17 00:00:00 2001 From: Ewan Young Date: Thu, 9 Jul 2026 18:19:51 +0800 Subject: [PATCH v3] Fix parallel GIN index build with keys larger than 65535 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 keylen holding VARSIZE_ANY() of the key. 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 GinTuple.keylen to Size so the stored length matches what _gin_build_tuple() computes. Size is the natural type here: it's what VARSIZE_ANY() returns and what GinBuffer.keylen already uses. The _gin_build_tuple() local was int, which truncated VARSIZE_ANY() the same way; make it Size too. Widening keylen also moves GinTuple.data, which happened to land at an 8-byte boundary before. While the Size layout keeps data MAXALIGNed, GinBufferKeyEquals() should not depend on that: it read an unaligned Datum via "*(Datum *) tup->data" for byval keys. Use _gin_parse_tuple_key() there instead, which copies the key out with memcpy() and so makes no assumption about the alignment of the data array, matching how the key is read everywhere else. Bug: #19545 Reported-by: Yuelin Wang <1217816127@qq.com> Discussion: https://postgr.es/m/19545-0f25b7e47351e8fc@postgresql.org --- src/backend/access/gin/gininsert.c | 10 ++++++---- src/include/access/gin_tuple.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index cb9ed3b563c..53d2a3f9f70 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -1376,10 +1376,12 @@ GinBufferKeyEquals(GinBuffer *buffer, GinTuple *tup) return true; /* - * For the tuple, get either the first sizeof(Datum) bytes for byval - * types, or a pointer to the beginning of the data array. + * Get the key from the tuple. We use _gin_parse_tuple_key() rather than + * reading tup->data directly, because for byval types the data is only + * stored/read with memcpy() (the data array is not guaranteed to be + * aligned enough to dereference as a Datum). */ - tupkey = (buffer->typbyval) ? *(Datum *) tup->data : PointerGetDatum(tup->data); + tupkey = _gin_parse_tuple_key(tup); r = ApplySortComparator(buffer->key, false, tupkey, false, @@ -2248,7 +2250,7 @@ _gin_build_tuple(OffsetNumber attrnum, unsigned char category, char *ptr; Size tuplen; - int keylen; + Size keylen; dlist_mutable_iter iter; dlist_head segments; diff --git a/src/include/access/gin_tuple.h b/src/include/access/gin_tuple.h index 7bde05e2de4..99a31578ac1 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 */ + Size 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