Fix handling of empty uncompressed posting list pages in GIN
authorAlexander Korotkov
Thu, 19 Jul 2018 18:04:17 +0000 (21:04 +0300)
committerAlexander Korotkov
Thu, 19 Jul 2018 18:19:19 +0000 (21:19 +0300)
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
src/backend/access/gin/ginxlog.c

index 276376a6f1de0e7c8c9f7dce59f047833ed2441c..cd3b9dfb784b084dd27a37146a4909fa1109ee81 100644 (file)
@@ -1392,7 +1392,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;
@@ -1400,15 +1401,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;
    }
index a40f1683dd80e4620ba6ccd93ae0e178e9616cf7..7515f8bc167c2eafceced5d6ad5d74f7ec09e0a5 100644 (file)
@@ -151,15 +151,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;