]> granicus.if.org Git - postgresql/blob - src/backend/access/gin/ginentrypage.c
8a5d9e1727971a5a4c29cda78d6f501932b8dafb
[postgresql] / src / backend / access / gin / ginentrypage.c
1 /*-------------------------------------------------------------------------
2  *
3  * ginentrypage.c
4  *        routines for handling GIN entry tree pages.
5  *
6  *
7  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  * IDENTIFICATION
11  *                      src/backend/access/gin/ginentrypage.c
12  *-------------------------------------------------------------------------
13  */
14
15 #include "postgres.h"
16
17 #include "access/gin_private.h"
18 #include "access/xloginsert.h"
19 #include "miscadmin.h"
20 #include "utils/rel.h"
21
22 static void entrySplitPage(GinBtree btree, Buffer origbuf,
23                            GinBtreeStack *stack,
24                            void *insertPayload,
25                            BlockNumber updateblkno,
26                            Page *newlpage, Page *newrpage);
27
28 /*
29  * Form a tuple for entry tree.
30  *
31  * If the tuple would be too big to be stored, function throws a suitable
32  * error if errorTooBig is TRUE, or returns NULL if errorTooBig is FALSE.
33  *
34  * See src/backend/access/gin/README for a description of the index tuple
35  * format that is being built here.  We build on the assumption that we
36  * are making a leaf-level key entry containing a posting list of nipd items.
37  * If the caller is actually trying to make a posting-tree entry, non-leaf
38  * entry, or pending-list entry, it should pass dataSize = 0 and then overwrite
39  * the t_tid fields as necessary.  In any case, 'data' can be NULL to skip
40  * filling in the posting list; the caller is responsible for filling it
41  * afterwards if data = NULL and nipd > 0.
42  */
43 IndexTuple
44 GinFormTuple(GinState *ginstate,
45                          OffsetNumber attnum, Datum key, GinNullCategory category,
46                          Pointer data, Size dataSize, int nipd,
47                          bool errorTooBig)
48 {
49         Datum           datums[2];
50         bool            isnull[2];
51         IndexTuple      itup;
52         uint32          newsize;
53
54         /* Build the basic tuple: optional column number, plus key datum */
55         if (ginstate->oneCol)
56         {
57                 datums[0] = key;
58                 isnull[0] = (category != GIN_CAT_NORM_KEY);
59         }
60         else
61         {
62                 datums[0] = UInt16GetDatum(attnum);
63                 isnull[0] = false;
64                 datums[1] = key;
65                 isnull[1] = (category != GIN_CAT_NORM_KEY);
66         }
67
68         itup = index_form_tuple(ginstate->tupdesc[attnum - 1], datums, isnull);
69
70         /*
71          * Determine and store offset to the posting list, making sure there is
72          * room for the category byte if needed.
73          *
74          * Note: because index_form_tuple MAXALIGNs the tuple size, there may well
75          * be some wasted pad space.  Is it worth recomputing the data length to
76          * prevent that?  That would also allow us to Assert that the real data
77          * doesn't overlap the GinNullCategory byte, which this code currently
78          * takes on faith.
79          */
80         newsize = IndexTupleSize(itup);
81
82         if (IndexTupleHasNulls(itup))
83         {
84                 uint32          minsize;
85
86                 Assert(category != GIN_CAT_NORM_KEY);
87                 minsize = GinCategoryOffset(itup, ginstate) + sizeof(GinNullCategory);
88                 newsize = Max(newsize, minsize);
89         }
90
91         newsize = SHORTALIGN(newsize);
92
93         GinSetPostingOffset(itup, newsize);
94         GinSetNPosting(itup, nipd);
95
96         /*
97          * Add space needed for posting list, if any.  Then check that the tuple
98          * won't be too big to store.
99          */
100         newsize += dataSize;
101
102         newsize = MAXALIGN(newsize);
103
104         if (newsize > GinMaxItemSize)
105         {
106                 if (errorTooBig)
107                         ereport(ERROR,
108                                         (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
109                         errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
110                                    (Size) newsize, (Size) GinMaxItemSize,
111                                    RelationGetRelationName(ginstate->index))));
112                 pfree(itup);
113                 return NULL;
114         }
115
116         /*
117          * Resize tuple if needed
118          */
119         if (newsize != IndexTupleSize(itup))
120         {
121                 itup = repalloc(itup, newsize);
122
123                 /*
124                  * PostgreSQL 9.3 and earlier did not clear this new space, so we
125                  * might find uninitialized padding when reading tuples from disk.
126                  */
127                 memset((char *) itup + IndexTupleSize(itup),
128                            0, newsize - IndexTupleSize(itup));
129                 /* set new size in tuple header */
130                 itup->t_info &= ~INDEX_SIZE_MASK;
131                 itup->t_info |= newsize;
132         }
133
134         /*
135          * Copy in the posting list, if provided
136          */
137         if (data)
138         {
139                 char       *ptr = GinGetPosting(itup);
140
141                 memcpy(ptr, data, dataSize);
142         }
143
144         /*
145          * Insert category byte, if needed
146          */
147         if (category != GIN_CAT_NORM_KEY)
148         {
149                 Assert(IndexTupleHasNulls(itup));
150                 GinSetNullCategory(itup, ginstate, category);
151         }
152         return itup;
153 }
154
155 /*
156  * Read item pointers from leaf entry tuple.
157  *
158  * Returns a palloc'd array of ItemPointers. The number of items is returned
159  * in *nitems.
160  */
161 ItemPointer
162 ginReadTuple(GinState *ginstate, OffsetNumber attnum, IndexTuple itup,
163                          int *nitems)
164 {
165         Pointer         ptr = GinGetPosting(itup);
166         int                     nipd = GinGetNPosting(itup);
167         ItemPointer ipd;
168         int                     ndecoded;
169
170         if (GinItupIsCompressed(itup))
171         {
172                 if (nipd > 0)
173                 {
174                         ipd = ginPostingListDecode((GinPostingList *) ptr, &ndecoded);
175                         if (nipd != ndecoded)
176                                 elog(ERROR, "number of items mismatch in GIN entry tuple, %d in tuple header, %d decoded",
177                                          nipd, ndecoded);
178                 }
179                 else
180                 {
181                         ipd = palloc(0);
182                 }
183         }
184         else
185         {
186                 ipd = (ItemPointer) palloc(sizeof(ItemPointerData) * nipd);
187                 memcpy(ipd, ptr, sizeof(ItemPointerData) * nipd);
188         }
189         *nitems = nipd;
190         return ipd;
191 }
192
193 /*
194  * Form a non-leaf entry tuple by copying the key data from the given tuple,
195  * which can be either a leaf or non-leaf entry tuple.
196  *
197  * Any posting list in the source tuple is not copied.  The specified child
198  * block number is inserted into t_tid.
199  */
200 static IndexTuple
201 GinFormInteriorTuple(IndexTuple itup, Page page, BlockNumber childblk)
202 {
203         IndexTuple      nitup;
204
205         if (GinPageIsLeaf(page) && !GinIsPostingTree(itup))
206         {
207                 /* Tuple contains a posting list, just copy stuff before that */
208                 uint32          origsize = GinGetPostingOffset(itup);
209
210                 origsize = MAXALIGN(origsize);
211                 nitup = (IndexTuple) palloc(origsize);
212                 memcpy(nitup, itup, origsize);
213                 /* ... be sure to fix the size header field ... */
214                 nitup->t_info &= ~INDEX_SIZE_MASK;
215                 nitup->t_info |= origsize;
216         }
217         else
218         {
219                 /* Copy the tuple as-is */
220                 nitup = (IndexTuple) palloc(IndexTupleSize(itup));
221                 memcpy(nitup, itup, IndexTupleSize(itup));
222         }
223
224         /* Now insert the correct downlink */
225         GinSetDownlink(nitup, childblk);
226
227         return nitup;
228 }
229
230 /*
231  * Entry tree is a "static", ie tuple never deletes from it,
232  * so we don't use right bound, we use rightmost key instead.
233  */
234 static IndexTuple
235 getRightMostTuple(Page page)
236 {
237         OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
238
239         return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff));
240 }
241
242 static bool
243 entryIsMoveRight(GinBtree btree, Page page)
244 {
245         IndexTuple      itup;
246         OffsetNumber attnum;
247         Datum           key;
248         GinNullCategory category;
249
250         if (GinPageRightMost(page))
251                 return FALSE;
252
253         itup = getRightMostTuple(page);
254         attnum = gintuple_get_attrnum(btree->ginstate, itup);
255         key = gintuple_get_key(btree->ginstate, itup, &category);
256
257         if (ginCompareAttEntries(btree->ginstate,
258                                    btree->entryAttnum, btree->entryKey, btree->entryCategory,
259                                                          attnum, key, category) > 0)
260                 return TRUE;
261
262         return FALSE;
263 }
264
265 /*
266  * Find correct tuple in non-leaf page. It supposed that
267  * page correctly chosen and searching value SHOULD be on page
268  */
269 static BlockNumber
270 entryLocateEntry(GinBtree btree, GinBtreeStack *stack)
271 {
272         OffsetNumber low,
273                                 high,
274                                 maxoff;
275         IndexTuple      itup = NULL;
276         int                     result;
277         Page            page = BufferGetPage(stack->buffer, NULL, NULL,
278                                                                          BGP_NO_SNAPSHOT_TEST);
279
280         Assert(!GinPageIsLeaf(page));
281         Assert(!GinPageIsData(page));
282
283         if (btree->fullScan)
284         {
285                 stack->off = FirstOffsetNumber;
286                 stack->predictNumber *= PageGetMaxOffsetNumber(page);
287                 return btree->getLeftMostChild(btree, page);
288         }
289
290         low = FirstOffsetNumber;
291         maxoff = high = PageGetMaxOffsetNumber(page);
292         Assert(high >= low);
293
294         high++;
295
296         while (high > low)
297         {
298                 OffsetNumber mid = low + ((high - low) / 2);
299
300                 if (mid == maxoff && GinPageRightMost(page))
301                 {
302                         /* Right infinity */
303                         result = -1;
304                 }
305                 else
306                 {
307                         OffsetNumber attnum;
308                         Datum           key;
309                         GinNullCategory category;
310
311                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
312                         attnum = gintuple_get_attrnum(btree->ginstate, itup);
313                         key = gintuple_get_key(btree->ginstate, itup, &category);
314                         result = ginCompareAttEntries(btree->ginstate,
315                                                                                   btree->entryAttnum,
316                                                                                   btree->entryKey,
317                                                                                   btree->entryCategory,
318                                                                                   attnum, key, category);
319                 }
320
321                 if (result == 0)
322                 {
323                         stack->off = mid;
324                         Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
325                         return GinGetDownlink(itup);
326                 }
327                 else if (result > 0)
328                         low = mid + 1;
329                 else
330                         high = mid;
331         }
332
333         Assert(high >= FirstOffsetNumber && high <= maxoff);
334
335         stack->off = high;
336         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high));
337         Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
338         return GinGetDownlink(itup);
339 }
340
341 /*
342  * Searches correct position for value on leaf page.
343  * Page should be correctly chosen.
344  * Returns true if value found on page.
345  */
346 static bool
347 entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack)
348 {
349         Page            page = BufferGetPage(stack->buffer, NULL, NULL,
350                                                                          BGP_NO_SNAPSHOT_TEST);
351         OffsetNumber low,
352                                 high;
353
354         Assert(GinPageIsLeaf(page));
355         Assert(!GinPageIsData(page));
356
357         if (btree->fullScan)
358         {
359                 stack->off = FirstOffsetNumber;
360                 return TRUE;
361         }
362
363         low = FirstOffsetNumber;
364         high = PageGetMaxOffsetNumber(page);
365
366         if (high < low)
367         {
368                 stack->off = FirstOffsetNumber;
369                 return false;
370         }
371
372         high++;
373
374         while (high > low)
375         {
376                 OffsetNumber mid = low + ((high - low) / 2);
377                 IndexTuple      itup;
378                 OffsetNumber attnum;
379                 Datum           key;
380                 GinNullCategory category;
381                 int                     result;
382
383                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
384                 attnum = gintuple_get_attrnum(btree->ginstate, itup);
385                 key = gintuple_get_key(btree->ginstate, itup, &category);
386                 result = ginCompareAttEntries(btree->ginstate,
387                                                                           btree->entryAttnum,
388                                                                           btree->entryKey,
389                                                                           btree->entryCategory,
390                                                                           attnum, key, category);
391                 if (result == 0)
392                 {
393                         stack->off = mid;
394                         return true;
395                 }
396                 else if (result > 0)
397                         low = mid + 1;
398                 else
399                         high = mid;
400         }
401
402         stack->off = high;
403         return false;
404 }
405
406 static OffsetNumber
407 entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
408 {
409         OffsetNumber i,
410                                 maxoff = PageGetMaxOffsetNumber(page);
411         IndexTuple      itup;
412
413         Assert(!GinPageIsLeaf(page));
414         Assert(!GinPageIsData(page));
415
416         /* if page isn't changed, we returns storedOff */
417         if (storedOff >= FirstOffsetNumber && storedOff <= maxoff)
418         {
419                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff));
420                 if (GinGetDownlink(itup) == blkno)
421                         return storedOff;
422
423                 /*
424                  * we hope, that needed pointer goes to right. It's true if there
425                  * wasn't a deletion
426                  */
427                 for (i = storedOff + 1; i <= maxoff; i++)
428                 {
429                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
430                         if (GinGetDownlink(itup) == blkno)
431                                 return i;
432                 }
433                 maxoff = storedOff - 1;
434         }
435
436         /* last chance */
437         for (i = FirstOffsetNumber; i <= maxoff; i++)
438         {
439                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
440                 if (GinGetDownlink(itup) == blkno)
441                         return i;
442         }
443
444         return InvalidOffsetNumber;
445 }
446
447 static BlockNumber
448 entryGetLeftMostPage(GinBtree btree, Page page)
449 {
450         IndexTuple      itup;
451
452         Assert(!GinPageIsLeaf(page));
453         Assert(!GinPageIsData(page));
454         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
455
456         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
457         return GinGetDownlink(itup);
458 }
459
460 static bool
461 entryIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off,
462                                    GinBtreeEntryInsertData *insertData)
463 {
464         Size            releasedsz = 0;
465         Size            addedsz;
466         Page            page = BufferGetPage(buf, NULL, NULL, BGP_NO_SNAPSHOT_TEST);
467
468         Assert(insertData->entry);
469         Assert(!GinPageIsData(page));
470
471         if (insertData->isDelete)
472         {
473                 IndexTuple      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
474
475                 releasedsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
476         }
477
478         addedsz = MAXALIGN(IndexTupleSize(insertData->entry)) + sizeof(ItemIdData);
479
480         if (PageGetFreeSpace(page) + releasedsz >= addedsz)
481                 return true;
482
483         return false;
484 }
485
486 /*
487  * Delete tuple on leaf page if tuples existed and we
488  * should update it, update old child blkno to new right page
489  * if child split occurred
490  */
491 static void
492 entryPreparePage(GinBtree btree, Page page, OffsetNumber off,
493                                  GinBtreeEntryInsertData *insertData, BlockNumber updateblkno)
494 {
495         Assert(insertData->entry);
496         Assert(!GinPageIsData(page));
497
498         if (insertData->isDelete)
499         {
500                 Assert(GinPageIsLeaf(page));
501                 PageIndexTupleDelete(page, off);
502         }
503
504         if (!GinPageIsLeaf(page) && updateblkno != InvalidBlockNumber)
505         {
506                 IndexTuple      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
507
508                 GinSetDownlink(itup, updateblkno);
509         }
510 }
511
512 /*
513  * Place tuple on page and fills WAL record
514  *
515  * If the tuple doesn't fit, returns false without modifying the page.
516  *
517  * On insertion to an internal node, in addition to inserting the given item,
518  * the downlink of the existing item at 'off' is updated to point to
519  * 'updateblkno'.
520  *
521  * On INSERTED, registers the buffer as buffer ID 0, with data.
522  * On SPLIT, returns rdata that represents the split pages in *prdata.
523  */
524 static GinPlaceToPageRC
525 entryPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
526                                  void *insertPayload, BlockNumber updateblkno,
527                                  Page *newlpage, Page *newrpage)
528 {
529         GinBtreeEntryInsertData *insertData = insertPayload;
530         Page            page = BufferGetPage(buf, NULL, NULL, BGP_NO_SNAPSHOT_TEST);
531         OffsetNumber off = stack->off;
532         OffsetNumber placed;
533
534         /* this must be static so it can be returned to caller. */
535         static ginxlogInsertEntry data;
536
537         /* quick exit if it doesn't fit */
538         if (!entryIsEnoughSpace(btree, buf, off, insertData))
539         {
540                 entrySplitPage(btree, buf, stack, insertPayload, updateblkno,
541                                            newlpage, newrpage);
542                 return SPLIT;
543         }
544
545         START_CRIT_SECTION();
546
547         entryPreparePage(btree, page, off, insertData, updateblkno);
548
549         placed = PageAddItem(page,
550                                                  (Item) insertData->entry,
551                                                  IndexTupleSize(insertData->entry),
552                                                  off, false, false);
553         if (placed != off)
554                 elog(ERROR, "failed to add item to index page in \"%s\"",
555                          RelationGetRelationName(btree->index));
556
557         if (RelationNeedsWAL(btree->index))
558         {
559                 data.isDelete = insertData->isDelete;
560                 data.offset = off;
561
562                 XLogBeginInsert();
563                 XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
564                 XLogRegisterBufData(0, (char *) &data,
565                                                         offsetof(ginxlogInsertEntry, tuple));
566                 XLogRegisterBufData(0, (char *) insertData->entry,
567                                                         IndexTupleSize(insertData->entry));
568         }
569
570         return INSERTED;
571 }
572
573 /*
574  * Place tuple and split page, original buffer(lbuf) leaves untouched,
575  * returns shadow pages filled with new data.
576  * Tuples are distributed between pages by equal size on its, not
577  * an equal number!
578  */
579 static void
580 entrySplitPage(GinBtree btree, Buffer origbuf,
581                            GinBtreeStack *stack,
582                            void *insertPayload,
583                            BlockNumber updateblkno,
584                            Page *newlpage, Page *newrpage)
585 {
586         GinBtreeEntryInsertData *insertData = insertPayload;
587         OffsetNumber off = stack->off;
588         OffsetNumber i,
589                                 maxoff,
590                                 separator = InvalidOffsetNumber;
591         Size            totalsize = 0;
592         Size            lsize = 0,
593                                 size;
594         char       *ptr;
595         IndexTuple      itup;
596         Page            page;
597         Page            lpage = PageGetTempPageCopy(BufferGetPage(origbuf, NULL, NULL,
598                                                                                                                   BGP_NO_SNAPSHOT_TEST));
599         Page            rpage = PageGetTempPageCopy(BufferGetPage(origbuf, NULL, NULL,
600                                                                                                                   BGP_NO_SNAPSHOT_TEST));
601         Size            pageSize = PageGetPageSize(lpage);
602         char            tupstore[2 * BLCKSZ];
603
604         entryPreparePage(btree, lpage, off, insertData, updateblkno);
605
606         /*
607          * First, append all the existing tuples and the new tuple we're inserting
608          * one after another in a temporary workspace.
609          */
610         maxoff = PageGetMaxOffsetNumber(lpage);
611         ptr = tupstore;
612         for (i = FirstOffsetNumber; i <= maxoff; i++)
613         {
614                 if (i == off)
615                 {
616                         size = MAXALIGN(IndexTupleSize(insertData->entry));
617                         memcpy(ptr, insertData->entry, size);
618                         ptr += size;
619                         totalsize += size + sizeof(ItemIdData);
620                 }
621
622                 itup = (IndexTuple) PageGetItem(lpage, PageGetItemId(lpage, i));
623                 size = MAXALIGN(IndexTupleSize(itup));
624                 memcpy(ptr, itup, size);
625                 ptr += size;
626                 totalsize += size + sizeof(ItemIdData);
627         }
628
629         if (off == maxoff + 1)
630         {
631                 size = MAXALIGN(IndexTupleSize(insertData->entry));
632                 memcpy(ptr, insertData->entry, size);
633                 ptr += size;
634                 totalsize += size + sizeof(ItemIdData);
635         }
636
637         /*
638          * Initialize the left and right pages, and copy all the tuples back to
639          * them.
640          */
641         GinInitPage(rpage, GinPageGetOpaque(lpage)->flags, pageSize);
642         GinInitPage(lpage, GinPageGetOpaque(rpage)->flags, pageSize);
643
644         ptr = tupstore;
645         maxoff++;
646         lsize = 0;
647
648         page = lpage;
649         for (i = FirstOffsetNumber; i <= maxoff; i++)
650         {
651                 itup = (IndexTuple) ptr;
652
653                 if (lsize > totalsize / 2)
654                 {
655                         if (separator == InvalidOffsetNumber)
656                                 separator = i - 1;
657                         page = rpage;
658                 }
659                 else
660                 {
661                         lsize += MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
662                 }
663
664                 if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
665                         elog(ERROR, "failed to add item to index page in \"%s\"",
666                                  RelationGetRelationName(btree->index));
667                 ptr += MAXALIGN(IndexTupleSize(itup));
668         }
669
670         *newlpage = lpage;
671         *newrpage = rpage;
672 }
673
674 /*
675  * Construct insertion payload for inserting the downlink for given buffer.
676  */
677 static void *
678 entryPrepareDownlink(GinBtree btree, Buffer lbuf)
679 {
680         GinBtreeEntryInsertData *insertData;
681         Page            lpage = BufferGetPage(lbuf, NULL, NULL, BGP_NO_SNAPSHOT_TEST);
682         BlockNumber lblkno = BufferGetBlockNumber(lbuf);
683         IndexTuple      itup;
684
685         itup = getRightMostTuple(lpage);
686
687         insertData = palloc(sizeof(GinBtreeEntryInsertData));
688         insertData->entry = GinFormInteriorTuple(itup, lpage, lblkno);
689         insertData->isDelete = false;
690
691         return insertData;
692 }
693
694 /*
695  * Fills new root by rightest values from child.
696  * Also called from ginxlog, should not use btree
697  */
698 void
699 ginEntryFillRoot(GinBtree btree, Page root,
700                                  BlockNumber lblkno, Page lpage,
701                                  BlockNumber rblkno, Page rpage)
702 {
703         IndexTuple      itup;
704
705         itup = GinFormInteriorTuple(getRightMostTuple(lpage), lpage, lblkno);
706         if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
707                 elog(ERROR, "failed to add item to index root page");
708         pfree(itup);
709
710         itup = GinFormInteriorTuple(getRightMostTuple(rpage), rpage, rblkno);
711         if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
712                 elog(ERROR, "failed to add item to index root page");
713         pfree(itup);
714 }
715
716 /*
717  * Set up GinBtree for entry page access
718  *
719  * Note: during WAL recovery, there may be no valid data in ginstate
720  * other than a faked-up Relation pointer; the key datum is bogus too.
721  */
722 void
723 ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum,
724                                         Datum key, GinNullCategory category,
725                                         GinState *ginstate)
726 {
727         memset(btree, 0, sizeof(GinBtreeData));
728
729         btree->index = ginstate->index;
730         btree->rootBlkno = GIN_ROOT_BLKNO;
731         btree->ginstate = ginstate;
732
733         btree->findChildPage = entryLocateEntry;
734         btree->getLeftMostChild = entryGetLeftMostPage;
735         btree->isMoveRight = entryIsMoveRight;
736         btree->findItem = entryLocateLeafEntry;
737         btree->findChildPtr = entryFindChildPtr;
738         btree->placeToPage = entryPlaceToPage;
739         btree->fillRoot = ginEntryFillRoot;
740         btree->prepareDownlink = entryPrepareDownlink;
741
742         btree->isData = FALSE;
743         btree->fullScan = FALSE;
744         btree->isBuild = FALSE;
745
746         btree->entryAttnum = attnum;
747         btree->entryKey = key;
748         btree->entryCategory = category;
749 }