]> granicus.if.org Git - postgresql/blob - src/backend/access/gin/ginentrypage.c
Update copyright for 2016
[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);
278
279         Assert(!GinPageIsLeaf(page));
280         Assert(!GinPageIsData(page));
281
282         if (btree->fullScan)
283         {
284                 stack->off = FirstOffsetNumber;
285                 stack->predictNumber *= PageGetMaxOffsetNumber(page);
286                 return btree->getLeftMostChild(btree, page);
287         }
288
289         low = FirstOffsetNumber;
290         maxoff = high = PageGetMaxOffsetNumber(page);
291         Assert(high >= low);
292
293         high++;
294
295         while (high > low)
296         {
297                 OffsetNumber mid = low + ((high - low) / 2);
298
299                 if (mid == maxoff && GinPageRightMost(page))
300                 {
301                         /* Right infinity */
302                         result = -1;
303                 }
304                 else
305                 {
306                         OffsetNumber attnum;
307                         Datum           key;
308                         GinNullCategory category;
309
310                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
311                         attnum = gintuple_get_attrnum(btree->ginstate, itup);
312                         key = gintuple_get_key(btree->ginstate, itup, &category);
313                         result = ginCompareAttEntries(btree->ginstate,
314                                                                                   btree->entryAttnum,
315                                                                                   btree->entryKey,
316                                                                                   btree->entryCategory,
317                                                                                   attnum, key, category);
318                 }
319
320                 if (result == 0)
321                 {
322                         stack->off = mid;
323                         Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
324                         return GinGetDownlink(itup);
325                 }
326                 else if (result > 0)
327                         low = mid + 1;
328                 else
329                         high = mid;
330         }
331
332         Assert(high >= FirstOffsetNumber && high <= maxoff);
333
334         stack->off = high;
335         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high));
336         Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
337         return GinGetDownlink(itup);
338 }
339
340 /*
341  * Searches correct position for value on leaf page.
342  * Page should be correctly chosen.
343  * Returns true if value found on page.
344  */
345 static bool
346 entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack)
347 {
348         Page            page = BufferGetPage(stack->buffer);
349         OffsetNumber low,
350                                 high;
351
352         Assert(GinPageIsLeaf(page));
353         Assert(!GinPageIsData(page));
354
355         if (btree->fullScan)
356         {
357                 stack->off = FirstOffsetNumber;
358                 return TRUE;
359         }
360
361         low = FirstOffsetNumber;
362         high = PageGetMaxOffsetNumber(page);
363
364         if (high < low)
365         {
366                 stack->off = FirstOffsetNumber;
367                 return false;
368         }
369
370         high++;
371
372         while (high > low)
373         {
374                 OffsetNumber mid = low + ((high - low) / 2);
375                 IndexTuple      itup;
376                 OffsetNumber attnum;
377                 Datum           key;
378                 GinNullCategory category;
379                 int                     result;
380
381                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
382                 attnum = gintuple_get_attrnum(btree->ginstate, itup);
383                 key = gintuple_get_key(btree->ginstate, itup, &category);
384                 result = ginCompareAttEntries(btree->ginstate,
385                                                                           btree->entryAttnum,
386                                                                           btree->entryKey,
387                                                                           btree->entryCategory,
388                                                                           attnum, key, category);
389                 if (result == 0)
390                 {
391                         stack->off = mid;
392                         return true;
393                 }
394                 else if (result > 0)
395                         low = mid + 1;
396                 else
397                         high = mid;
398         }
399
400         stack->off = high;
401         return false;
402 }
403
404 static OffsetNumber
405 entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
406 {
407         OffsetNumber i,
408                                 maxoff = PageGetMaxOffsetNumber(page);
409         IndexTuple      itup;
410
411         Assert(!GinPageIsLeaf(page));
412         Assert(!GinPageIsData(page));
413
414         /* if page isn't changed, we returns storedOff */
415         if (storedOff >= FirstOffsetNumber && storedOff <= maxoff)
416         {
417                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff));
418                 if (GinGetDownlink(itup) == blkno)
419                         return storedOff;
420
421                 /*
422                  * we hope, that needed pointer goes to right. It's true if there
423                  * wasn't a deletion
424                  */
425                 for (i = storedOff + 1; i <= maxoff; i++)
426                 {
427                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
428                         if (GinGetDownlink(itup) == blkno)
429                                 return i;
430                 }
431                 maxoff = storedOff - 1;
432         }
433
434         /* last chance */
435         for (i = FirstOffsetNumber; i <= maxoff; i++)
436         {
437                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
438                 if (GinGetDownlink(itup) == blkno)
439                         return i;
440         }
441
442         return InvalidOffsetNumber;
443 }
444
445 static BlockNumber
446 entryGetLeftMostPage(GinBtree btree, Page page)
447 {
448         IndexTuple      itup;
449
450         Assert(!GinPageIsLeaf(page));
451         Assert(!GinPageIsData(page));
452         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
453
454         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
455         return GinGetDownlink(itup);
456 }
457
458 static bool
459 entryIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off,
460                                    GinBtreeEntryInsertData *insertData)
461 {
462         Size            releasedsz = 0;
463         Size            addedsz;
464         Page            page = BufferGetPage(buf);
465
466         Assert(insertData->entry);
467         Assert(!GinPageIsData(page));
468
469         if (insertData->isDelete)
470         {
471                 IndexTuple      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
472
473                 releasedsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
474         }
475
476         addedsz = MAXALIGN(IndexTupleSize(insertData->entry)) + sizeof(ItemIdData);
477
478         if (PageGetFreeSpace(page) + releasedsz >= addedsz)
479                 return true;
480
481         return false;
482 }
483
484 /*
485  * Delete tuple on leaf page if tuples existed and we
486  * should update it, update old child blkno to new right page
487  * if child split occurred
488  */
489 static void
490 entryPreparePage(GinBtree btree, Page page, OffsetNumber off,
491                                  GinBtreeEntryInsertData *insertData, BlockNumber updateblkno)
492 {
493         Assert(insertData->entry);
494         Assert(!GinPageIsData(page));
495
496         if (insertData->isDelete)
497         {
498                 Assert(GinPageIsLeaf(page));
499                 PageIndexTupleDelete(page, off);
500         }
501
502         if (!GinPageIsLeaf(page) && updateblkno != InvalidBlockNumber)
503         {
504                 IndexTuple      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
505
506                 GinSetDownlink(itup, updateblkno);
507         }
508 }
509
510 /*
511  * Place tuple on page and fills WAL record
512  *
513  * If the tuple doesn't fit, returns false without modifying the page.
514  *
515  * On insertion to an internal node, in addition to inserting the given item,
516  * the downlink of the existing item at 'off' is updated to point to
517  * 'updateblkno'.
518  *
519  * On INSERTED, registers the buffer as buffer ID 0, with data.
520  * On SPLIT, returns rdata that represents the split pages in *prdata.
521  */
522 static GinPlaceToPageRC
523 entryPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
524                                  void *insertPayload, BlockNumber updateblkno,
525                                  Page *newlpage, Page *newrpage)
526 {
527         GinBtreeEntryInsertData *insertData = insertPayload;
528         Page            page = BufferGetPage(buf);
529         OffsetNumber off = stack->off;
530         OffsetNumber placed;
531
532         /* this must be static so it can be returned to caller. */
533         static ginxlogInsertEntry data;
534
535         /* quick exit if it doesn't fit */
536         if (!entryIsEnoughSpace(btree, buf, off, insertData))
537         {
538                 entrySplitPage(btree, buf, stack, insertPayload, updateblkno,
539                                            newlpage, newrpage);
540                 return SPLIT;
541         }
542
543         START_CRIT_SECTION();
544
545         entryPreparePage(btree, page, off, insertData, updateblkno);
546
547         placed = PageAddItem(page,
548                                                  (Item) insertData->entry,
549                                                  IndexTupleSize(insertData->entry),
550                                                  off, false, false);
551         if (placed != off)
552                 elog(ERROR, "failed to add item to index page in \"%s\"",
553                          RelationGetRelationName(btree->index));
554
555         if (RelationNeedsWAL(btree->index))
556         {
557                 data.isDelete = insertData->isDelete;
558                 data.offset = off;
559
560                 XLogBeginInsert();
561                 XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
562                 XLogRegisterBufData(0, (char *) &data,
563                                                         offsetof(ginxlogInsertEntry, tuple));
564                 XLogRegisterBufData(0, (char *) insertData->entry,
565                                                         IndexTupleSize(insertData->entry));
566         }
567
568         return INSERTED;
569 }
570
571 /*
572  * Place tuple and split page, original buffer(lbuf) leaves untouched,
573  * returns shadow pages filled with new data.
574  * Tuples are distributed between pages by equal size on its, not
575  * an equal number!
576  */
577 static void
578 entrySplitPage(GinBtree btree, Buffer origbuf,
579                            GinBtreeStack *stack,
580                            void *insertPayload,
581                            BlockNumber updateblkno,
582                            Page *newlpage, Page *newrpage)
583 {
584         GinBtreeEntryInsertData *insertData = insertPayload;
585         OffsetNumber off = stack->off;
586         OffsetNumber i,
587                                 maxoff,
588                                 separator = InvalidOffsetNumber;
589         Size            totalsize = 0;
590         Size            lsize = 0,
591                                 size;
592         char       *ptr;
593         IndexTuple      itup;
594         Page            page;
595         Page            lpage = PageGetTempPageCopy(BufferGetPage(origbuf));
596         Page            rpage = PageGetTempPageCopy(BufferGetPage(origbuf));
597         Size            pageSize = PageGetPageSize(lpage);
598         char            tupstore[2 * BLCKSZ];
599
600         entryPreparePage(btree, lpage, off, insertData, updateblkno);
601
602         /*
603          * First, append all the existing tuples and the new tuple we're inserting
604          * one after another in a temporary workspace.
605          */
606         maxoff = PageGetMaxOffsetNumber(lpage);
607         ptr = tupstore;
608         for (i = FirstOffsetNumber; i <= maxoff; i++)
609         {
610                 if (i == off)
611                 {
612                         size = MAXALIGN(IndexTupleSize(insertData->entry));
613                         memcpy(ptr, insertData->entry, size);
614                         ptr += size;
615                         totalsize += size + sizeof(ItemIdData);
616                 }
617
618                 itup = (IndexTuple) PageGetItem(lpage, PageGetItemId(lpage, i));
619                 size = MAXALIGN(IndexTupleSize(itup));
620                 memcpy(ptr, itup, size);
621                 ptr += size;
622                 totalsize += size + sizeof(ItemIdData);
623         }
624
625         if (off == maxoff + 1)
626         {
627                 size = MAXALIGN(IndexTupleSize(insertData->entry));
628                 memcpy(ptr, insertData->entry, size);
629                 ptr += size;
630                 totalsize += size + sizeof(ItemIdData);
631         }
632
633         /*
634          * Initialize the left and right pages, and copy all the tuples back to
635          * them.
636          */
637         GinInitPage(rpage, GinPageGetOpaque(lpage)->flags, pageSize);
638         GinInitPage(lpage, GinPageGetOpaque(rpage)->flags, pageSize);
639
640         ptr = tupstore;
641         maxoff++;
642         lsize = 0;
643
644         page = lpage;
645         for (i = FirstOffsetNumber; i <= maxoff; i++)
646         {
647                 itup = (IndexTuple) ptr;
648
649                 if (lsize > totalsize / 2)
650                 {
651                         if (separator == InvalidOffsetNumber)
652                                 separator = i - 1;
653                         page = rpage;
654                 }
655                 else
656                 {
657                         lsize += MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
658                 }
659
660                 if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
661                         elog(ERROR, "failed to add item to index page in \"%s\"",
662                                  RelationGetRelationName(btree->index));
663                 ptr += MAXALIGN(IndexTupleSize(itup));
664         }
665
666         *newlpage = lpage;
667         *newrpage = rpage;
668 }
669
670 /*
671  * Construct insertion payload for inserting the downlink for given buffer.
672  */
673 static void *
674 entryPrepareDownlink(GinBtree btree, Buffer lbuf)
675 {
676         GinBtreeEntryInsertData *insertData;
677         Page            lpage = BufferGetPage(lbuf);
678         BlockNumber lblkno = BufferGetBlockNumber(lbuf);
679         IndexTuple      itup;
680
681         itup = getRightMostTuple(lpage);
682
683         insertData = palloc(sizeof(GinBtreeEntryInsertData));
684         insertData->entry = GinFormInteriorTuple(itup, lpage, lblkno);
685         insertData->isDelete = false;
686
687         return insertData;
688 }
689
690 /*
691  * Fills new root by rightest values from child.
692  * Also called from ginxlog, should not use btree
693  */
694 void
695 ginEntryFillRoot(GinBtree btree, Page root,
696                                  BlockNumber lblkno, Page lpage,
697                                  BlockNumber rblkno, Page rpage)
698 {
699         IndexTuple      itup;
700
701         itup = GinFormInteriorTuple(getRightMostTuple(lpage), lpage, lblkno);
702         if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
703                 elog(ERROR, "failed to add item to index root page");
704         pfree(itup);
705
706         itup = GinFormInteriorTuple(getRightMostTuple(rpage), rpage, rblkno);
707         if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
708                 elog(ERROR, "failed to add item to index root page");
709         pfree(itup);
710 }
711
712 /*
713  * Set up GinBtree for entry page access
714  *
715  * Note: during WAL recovery, there may be no valid data in ginstate
716  * other than a faked-up Relation pointer; the key datum is bogus too.
717  */
718 void
719 ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum,
720                                         Datum key, GinNullCategory category,
721                                         GinState *ginstate)
722 {
723         memset(btree, 0, sizeof(GinBtreeData));
724
725         btree->index = ginstate->index;
726         btree->rootBlkno = GIN_ROOT_BLKNO;
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->fillRoot = ginEntryFillRoot;
736         btree->prepareDownlink = entryPrepareDownlink;
737
738         btree->isData = FALSE;
739         btree->fullScan = FALSE;
740         btree->isBuild = FALSE;
741
742         btree->entryAttnum = attnum;
743         btree->entryKey = key;
744         btree->entryCategory = category;
745 }