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