From 9c6a676c4cedab50e4015f49c871dbcdfc4efe07 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Thu, 19 Jul 2018 21:04:17 +0300
Subject: [PATCH] Fix handling of empty uncompressed posting list pages in GIN

PostgreSQL 9.4 introduces posting list compression in GIN.  This feature
supports online upgrade, so that after pg_upgrade uncompressed posting
lists are compressed on-the-fly.  Underlying code appears to always
expect at least one item on uncompressed posting list page.  But there
could be completely empty pages, because VACUUM never deletes leftmost
and rightmost pages from posting trees.  This commit fixes that.

Reported-by: Sivasubramanian Ramasubramanian
Discussion: https://postgr.es/m/1531867212836.63354%40amazon.com
Author: Sivasubramanian Ramasubramanian, Alexander Korotkov
Backpatch-through: 9.4
---
 src/backend/access/gin/gindatapage.c | 20 ++++++++++++--------
 src/backend/access/gin/ginxlog.c     | 27 +++++++++++++++++++++------
 2 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 77725acf9c4..25c36a2985a 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -1470,7 +1470,8 @@ disassembleLeaf(Page page)
 	{
 		/*
 		 * A pre-9.4 format uncompressed page is represented by a single
-		 * segment, with an array of items.
+		 * segment, with an array of items.  The corner case is uncompressed
+		 * page containing no items, which is represented as no segments.
 		 */
 		ItemPointer uncompressed;
 		int			nuncompressed;
@@ -1478,15 +1479,18 @@ disassembleLeaf(Page page)
 
 		uncompressed = dataLeafPageGetUncompressed(page, &nuncompressed);
 
-		seginfo = palloc(sizeof(leafSegmentInfo));
+		if (nuncompressed > 0)
+		{
+			seginfo = palloc(sizeof(leafSegmentInfo));
 
-		seginfo->action = GIN_SEGMENT_REPLACE;
-		seginfo->seg = NULL;
-		seginfo->items = palloc(nuncompressed * sizeof(ItemPointerData));
-		memcpy(seginfo->items, uncompressed, nuncompressed * sizeof(ItemPointerData));
-		seginfo->nitems = nuncompressed;
+			seginfo->action = GIN_SEGMENT_REPLACE;
+			seginfo->seg = NULL;
+			seginfo->items = palloc(nuncompressed * sizeof(ItemPointerData));
+			memcpy(seginfo->items, uncompressed, nuncompressed * sizeof(ItemPointerData));
+			seginfo->nitems = nuncompressed;
 
-		dlist_push_tail(&leaf->segments, &seginfo->node);
+			dlist_push_tail(&leaf->segments, &seginfo->node);
+		}
 
 		leaf->oldformat = true;
 	}
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index 7f93ce6526f..130ee7d5b2c 100644
--- a/src/backend/access/gin/ginxlog.c
+++ b/src/backend/access/gin/ginxlog.c
@@ -160,15 +160,30 @@ ginRedoRecompress(Page page, ginxlogRecompressDataLeaf *data)
 		ItemPointer uncompressed = (ItemPointer) GinDataPageGetData(page);
 		int			nuncompressed = GinPageGetOpaque(page)->maxoff;
 		int			npacked;
-		GinPostingList *plist;
 
-		plist = ginCompressPostingList(uncompressed, nuncompressed,
-									   BLCKSZ, &npacked);
-		Assert(npacked == nuncompressed);
+		/*
+		 * Empty leaf pages are deleted as part of vacuum, but leftmost and
+		 * rightmost pages are never deleted.  So, pg_upgrade'd from pre-9.4
+		 * instances might contain empty leaf pages, and we need to handle
+		 * them correctly.
+		 */
+		if (nuncompressed > 0)
+		{
+			GinPostingList *plist;
+
+			plist = ginCompressPostingList(uncompressed, nuncompressed,
+										   BLCKSZ, &npacked);
+			totalsize = SizeOfGinPostingList(plist);
+
+			Assert(npacked == nuncompressed);
 
-		totalsize = SizeOfGinPostingList(plist);
+			memcpy(GinDataLeafPageGetPostingList(page), plist, totalsize);
+		}
+		else
+		{
+			totalsize = 0;
+		}
 
-		memcpy(GinDataLeafPageGetPostingList(page), plist, totalsize);
 		GinDataPageSetDataSize(page, totalsize);
 		GinPageSetCompressed(page);
 		GinPageGetOpaque(page)->maxoff = InvalidOffsetNumber;
-- 
GitLab