Coverage Report

Created: 2025-06-13 06:06

/src/postgres/src/backend/access/gin/ginentrypage.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * ginentrypage.c
4
 *    routines for handling GIN entry tree pages.
5
 *
6
 *
7
 * Portions Copyright (c) 1996-2025, 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/ginxlog.h"
19
#include "access/xloginsert.h"
20
#include "utils/rel.h"
21
22
static void entrySplitPage(GinBtree btree, Buffer origbuf,
23
               GinBtreeStack *stack,
24
               GinBtreeEntryInsertData *insertData,
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
0
{
49
0
  Datum   datums[2];
50
0
  bool    isnull[2];
51
0
  IndexTuple  itup;
52
0
  uint32    newsize;
53
54
  /* Build the basic tuple: optional column number, plus key datum */
55
0
  if (ginstate->oneCol)
56
0
  {
57
0
    datums[0] = key;
58
0
    isnull[0] = (category != GIN_CAT_NORM_KEY);
59
0
  }
60
0
  else
61
0
  {
62
0
    datums[0] = UInt16GetDatum(attnum);
63
0
    isnull[0] = false;
64
0
    datums[1] = key;
65
0
    isnull[1] = (category != GIN_CAT_NORM_KEY);
66
0
  }
67
68
0
  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
0
  newsize = IndexTupleSize(itup);
81
82
0
  if (IndexTupleHasNulls(itup))
83
0
  {
84
0
    uint32    minsize;
85
86
0
    Assert(category != GIN_CAT_NORM_KEY);
87
0
    minsize = GinCategoryOffset(itup, ginstate) + sizeof(GinNullCategory);
88
0
    newsize = Max(newsize, minsize);
89
0
  }
90
91
0
  newsize = SHORTALIGN(newsize);
92
93
0
  GinSetPostingOffset(itup, newsize);
94
0
  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
0
  newsize += dataSize;
101
102
0
  newsize = MAXALIGN(newsize);
103
104
0
  if (newsize > GinMaxItemSize)
105
0
  {
106
0
    if (errorTooBig)
107
0
      ereport(ERROR,
108
0
          (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
109
0
           errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
110
0
              (Size) newsize, (Size) GinMaxItemSize,
111
0
              RelationGetRelationName(ginstate->index))));
112
0
    pfree(itup);
113
0
    return NULL;
114
0
  }
115
116
  /*
117
   * Resize tuple if needed
118
   */
119
0
  if (newsize != IndexTupleSize(itup))
120
0
  {
121
0
    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
0
    memset((char *) itup + IndexTupleSize(itup),
128
0
         0, newsize - IndexTupleSize(itup));
129
    /* set new size in tuple header */
130
0
    itup->t_info &= ~INDEX_SIZE_MASK;
131
0
    itup->t_info |= newsize;
132
0
  }
133
134
  /*
135
   * Copy in the posting list, if provided
136
   */
137
0
  if (data)
138
0
  {
139
0
    char     *ptr = GinGetPosting(itup);
140
141
0
    memcpy(ptr, data, dataSize);
142
0
  }
143
144
  /*
145
   * Insert category byte, if needed
146
   */
147
0
  if (category != GIN_CAT_NORM_KEY)
148
0
  {
149
0
    Assert(IndexTupleHasNulls(itup));
150
0
    GinSetNullCategory(itup, ginstate, category);
151
0
  }
152
0
  return itup;
153
0
}
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
0
{
165
0
  Pointer   ptr = GinGetPosting(itup);
166
0
  int     nipd = GinGetNPosting(itup);
167
0
  ItemPointer ipd;
168
0
  int     ndecoded;
169
170
0
  if (GinItupIsCompressed(itup))
171
0
  {
172
0
    if (nipd > 0)
173
0
    {
174
0
      ipd = ginPostingListDecode((GinPostingList *) ptr, &ndecoded);
175
0
      if (nipd != ndecoded)
176
0
        elog(ERROR, "number of items mismatch in GIN entry tuple, %d in tuple header, %d decoded",
177
0
           nipd, ndecoded);
178
0
    }
179
0
    else
180
0
    {
181
0
      ipd = palloc(0);
182
0
    }
183
0
  }
184
0
  else
185
0
  {
186
0
    ipd = (ItemPointer) palloc(sizeof(ItemPointerData) * nipd);
187
0
    memcpy(ipd, ptr, sizeof(ItemPointerData) * nipd);
188
0
  }
189
0
  *nitems = nipd;
190
0
  return ipd;
191
0
}
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
0
{
203
0
  IndexTuple  nitup;
204
205
0
  if (GinPageIsLeaf(page) && !GinIsPostingTree(itup))
206
0
  {
207
    /* Tuple contains a posting list, just copy stuff before that */
208
0
    uint32    origsize = GinGetPostingOffset(itup);
209
210
0
    origsize = MAXALIGN(origsize);
211
0
    nitup = (IndexTuple) palloc(origsize);
212
0
    memcpy(nitup, itup, origsize);
213
    /* ... be sure to fix the size header field ... */
214
0
    nitup->t_info &= ~INDEX_SIZE_MASK;
215
0
    nitup->t_info |= origsize;
216
0
  }
217
0
  else
218
0
  {
219
    /* Copy the tuple as-is */
220
0
    nitup = (IndexTuple) palloc(IndexTupleSize(itup));
221
0
    memcpy(nitup, itup, IndexTupleSize(itup));
222
0
  }
223
224
  /* Now insert the correct downlink */
225
0
  GinSetDownlink(nitup, childblk);
226
227
0
  return nitup;
228
0
}
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
0
{
237
0
  OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
238
239
0
  return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff));
240
0
}
241
242
static bool
243
entryIsMoveRight(GinBtree btree, Page page)
244
0
{
245
0
  IndexTuple  itup;
246
0
  OffsetNumber attnum;
247
0
  Datum   key;
248
0
  GinNullCategory category;
249
250
0
  if (GinPageRightMost(page))
251
0
    return false;
252
253
0
  itup = getRightMostTuple(page);
254
0
  attnum = gintuple_get_attrnum(btree->ginstate, itup);
255
0
  key = gintuple_get_key(btree->ginstate, itup, &category);
256
257
0
  if (ginCompareAttEntries(btree->ginstate,
258
0
               btree->entryAttnum, btree->entryKey, btree->entryCategory,
259
0
               attnum, key, category) > 0)
260
0
    return true;
261
262
0
  return false;
263
0
}
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
0
{
272
0
  OffsetNumber low,
273
0
        high,
274
0
        maxoff;
275
0
  IndexTuple  itup = NULL;
276
0
  int     result;
277
0
  Page    page = BufferGetPage(stack->buffer);
278
279
0
  Assert(!GinPageIsLeaf(page));
280
0
  Assert(!GinPageIsData(page));
281
282
0
  if (btree->fullScan)
283
0
  {
284
0
    stack->off = FirstOffsetNumber;
285
0
    stack->predictNumber *= PageGetMaxOffsetNumber(page);
286
0
    return btree->getLeftMostChild(btree, page);
287
0
  }
288
289
0
  low = FirstOffsetNumber;
290
0
  maxoff = high = PageGetMaxOffsetNumber(page);
291
0
  Assert(high >= low);
292
293
0
  high++;
294
295
0
  while (high > low)
296
0
  {
297
0
    OffsetNumber mid = low + ((high - low) / 2);
298
299
0
    if (mid == maxoff && GinPageRightMost(page))
300
0
    {
301
      /* Right infinity */
302
0
      result = -1;
303
0
    }
304
0
    else
305
0
    {
306
0
      OffsetNumber attnum;
307
0
      Datum   key;
308
0
      GinNullCategory category;
309
310
0
      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
311
0
      attnum = gintuple_get_attrnum(btree->ginstate, itup);
312
0
      key = gintuple_get_key(btree->ginstate, itup, &category);
313
0
      result = ginCompareAttEntries(btree->ginstate,
314
0
                      btree->entryAttnum,
315
0
                      btree->entryKey,
316
0
                      btree->entryCategory,
317
0
                      attnum, key, category);
318
0
    }
319
320
0
    if (result == 0)
321
0
    {
322
0
      stack->off = mid;
323
0
      Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
324
0
      return GinGetDownlink(itup);
325
0
    }
326
0
    else if (result > 0)
327
0
      low = mid + 1;
328
0
    else
329
0
      high = mid;
330
0
  }
331
332
0
  Assert(high >= FirstOffsetNumber && high <= maxoff);
333
334
0
  stack->off = high;
335
0
  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high));
336
0
  Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO);
337
0
  return GinGetDownlink(itup);
338
0
}
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
0
{
348
0
  Page    page = BufferGetPage(stack->buffer);
349
0
  OffsetNumber low,
350
0
        high;
351
352
0
  Assert(GinPageIsLeaf(page));
353
0
  Assert(!GinPageIsData(page));
354
355
0
  if (btree->fullScan)
356
0
  {
357
0
    stack->off = FirstOffsetNumber;
358
0
    return true;
359
0
  }
360
361
0
  low = FirstOffsetNumber;
362
0
  high = PageGetMaxOffsetNumber(page);
363
364
0
  if (high < low)
365
0
  {
366
0
    stack->off = FirstOffsetNumber;
367
0
    return false;
368
0
  }
369
370
0
  high++;
371
372
0
  while (high > low)
373
0
  {
374
0
    OffsetNumber mid = low + ((high - low) / 2);
375
0
    IndexTuple  itup;
376
0
    OffsetNumber attnum;
377
0
    Datum   key;
378
0
    GinNullCategory category;
379
0
    int     result;
380
381
0
    itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
382
0
    attnum = gintuple_get_attrnum(btree->ginstate, itup);
383
0
    key = gintuple_get_key(btree->ginstate, itup, &category);
384
0
    result = ginCompareAttEntries(btree->ginstate,
385
0
                    btree->entryAttnum,
386
0
                    btree->entryKey,
387
0
                    btree->entryCategory,
388
0
                    attnum, key, category);
389
0
    if (result == 0)
390
0
    {
391
0
      stack->off = mid;
392
0
      return true;
393
0
    }
394
0
    else if (result > 0)
395
0
      low = mid + 1;
396
0
    else
397
0
      high = mid;
398
0
  }
399
400
0
  stack->off = high;
401
0
  return false;
402
0
}
403
404
static OffsetNumber
405
entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
406
0
{
407
0
  OffsetNumber i,
408
0
        maxoff = PageGetMaxOffsetNumber(page);
409
0
  IndexTuple  itup;
410
411
0
  Assert(!GinPageIsLeaf(page));
412
0
  Assert(!GinPageIsData(page));
413
414
  /* if page isn't changed, we returns storedOff */
415
0
  if (storedOff >= FirstOffsetNumber && storedOff <= maxoff)
416
0
  {
417
0
    itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff));
418
0
    if (GinGetDownlink(itup) == blkno)
419
0
      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
0
    for (i = storedOff + 1; i <= maxoff; i++)
426
0
    {
427
0
      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
428
0
      if (GinGetDownlink(itup) == blkno)
429
0
        return i;
430
0
    }
431
0
    maxoff = storedOff - 1;
432
0
  }
433
434
  /* last chance */
435
0
  for (i = FirstOffsetNumber; i <= maxoff; i++)
436
0
  {
437
0
    itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
438
0
    if (GinGetDownlink(itup) == blkno)
439
0
      return i;
440
0
  }
441
442
0
  return InvalidOffsetNumber;
443
0
}
444
445
static BlockNumber
446
entryGetLeftMostPage(GinBtree btree, Page page)
447
0
{
448
0
  IndexTuple  itup;
449
450
0
  Assert(!GinPageIsLeaf(page));
451
0
  Assert(!GinPageIsData(page));
452
0
  Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
453
454
0
  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
455
0
  return GinGetDownlink(itup);
456
0
}
457
458
static bool
459
entryIsEnoughSpace(GinBtree btree, Buffer buf, OffsetNumber off,
460
           GinBtreeEntryInsertData *insertData)
461
0
{
462
0
  Size    releasedsz = 0;
463
0
  Size    addedsz;
464
0
  Page    page = BufferGetPage(buf);
465
466
0
  Assert(insertData->entry);
467
0
  Assert(!GinPageIsData(page));
468
469
0
  if (insertData->isDelete)
470
0
  {
471
0
    IndexTuple  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
472
473
0
    releasedsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
474
0
  }
475
476
0
  addedsz = MAXALIGN(IndexTupleSize(insertData->entry)) + sizeof(ItemIdData);
477
478
0
  if (PageGetFreeSpace(page) + releasedsz >= addedsz)
479
0
    return true;
480
481
0
  return false;
482
0
}
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
0
{
493
0
  Assert(insertData->entry);
494
0
  Assert(!GinPageIsData(page));
495
496
0
  if (insertData->isDelete)
497
0
  {
498
0
    Assert(GinPageIsLeaf(page));
499
0
    PageIndexTupleDelete(page, off);
500
0
  }
501
502
0
  if (!GinPageIsLeaf(page) && updateblkno != InvalidBlockNumber)
503
0
  {
504
0
    IndexTuple  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
505
506
0
    GinSetDownlink(itup, updateblkno);
507
0
  }
508
0
}
509
510
/*
511
 * Prepare to insert data on an entry page.
512
 *
513
 * If it will fit, return GPTP_INSERT after doing whatever setup is needed
514
 * before we enter the insertion critical section.  *ptp_workspace can be
515
 * set to pass information along to the execPlaceToPage function.
516
 *
517
 * If it won't fit, perform a page split and return two temporary page
518
 * images into *newlpage and *newrpage, with result GPTP_SPLIT.
519
 *
520
 * In neither case should the given page buffer be modified here.
521
 *
522
 * Note: on insertion to an internal node, in addition to inserting the given
523
 * item, the downlink of the existing item at stack->off will be updated to
524
 * point to updateblkno.
525
 */
526
static GinPlaceToPageRC
527
entryBeginPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
528
            void *insertPayload, BlockNumber updateblkno,
529
            void **ptp_workspace,
530
            Page *newlpage, Page *newrpage)
531
0
{
532
0
  GinBtreeEntryInsertData *insertData = insertPayload;
533
0
  OffsetNumber off = stack->off;
534
535
  /* If it doesn't fit, deal with split case */
536
0
  if (!entryIsEnoughSpace(btree, buf, off, insertData))
537
0
  {
538
0
    entrySplitPage(btree, buf, stack, insertData, updateblkno,
539
0
             newlpage, newrpage);
540
0
    return GPTP_SPLIT;
541
0
  }
542
543
  /* Else, we're ready to proceed with insertion */
544
0
  return GPTP_INSERT;
545
0
}
546
547
/*
548
 * Perform data insertion after beginPlaceToPage has decided it will fit.
549
 *
550
 * This is invoked within a critical section, and XLOG record creation (if
551
 * needed) is already started.  The target buffer is registered in slot 0.
552
 */
553
static void
554
entryExecPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
555
           void *insertPayload, BlockNumber updateblkno,
556
           void *ptp_workspace)
557
0
{
558
0
  GinBtreeEntryInsertData *insertData = insertPayload;
559
0
  Page    page = BufferGetPage(buf);
560
0
  OffsetNumber off = stack->off;
561
0
  OffsetNumber placed;
562
563
0
  entryPreparePage(btree, page, off, insertData, updateblkno);
564
565
0
  placed = PageAddItem(page,
566
0
             (Item) insertData->entry,
567
0
             IndexTupleSize(insertData->entry),
568
0
             off, false, false);
569
0
  if (placed != off)
570
0
    elog(ERROR, "failed to add item to index page in \"%s\"",
571
0
       RelationGetRelationName(btree->index));
572
573
0
  MarkBufferDirty(buf);
574
575
0
  if (RelationNeedsWAL(btree->index) && !btree->isBuild)
576
0
  {
577
    /*
578
     * This must be static, because it has to survive until XLogInsert,
579
     * and we can't palloc here.  Ugly, but the XLogInsert infrastructure
580
     * isn't reentrant anyway.
581
     */
582
0
    static ginxlogInsertEntry data;
583
584
0
    data.isDelete = insertData->isDelete;
585
0
    data.offset = off;
586
587
0
    XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
588
0
    XLogRegisterBufData(0, &data,
589
0
              offsetof(ginxlogInsertEntry, tuple));
590
0
    XLogRegisterBufData(0, insertData->entry,
591
0
              IndexTupleSize(insertData->entry));
592
0
  }
593
0
}
594
595
/*
596
 * Split entry page and insert new data.
597
 *
598
 * Returns new temp pages to *newlpage and *newrpage.
599
 * The original buffer is left untouched.
600
 */
601
static void
602
entrySplitPage(GinBtree btree, Buffer origbuf,
603
         GinBtreeStack *stack,
604
         GinBtreeEntryInsertData *insertData,
605
         BlockNumber updateblkno,
606
         Page *newlpage, Page *newrpage)
607
0
{
608
0
  OffsetNumber off = stack->off;
609
0
  OffsetNumber i,
610
0
        maxoff,
611
0
        separator = InvalidOffsetNumber;
612
0
  Size    totalsize = 0;
613
0
  Size    lsize = 0,
614
0
        size;
615
0
  char     *ptr;
616
0
  IndexTuple  itup;
617
0
  Page    page;
618
0
  Page    lpage = PageGetTempPageCopy(BufferGetPage(origbuf));
619
0
  Page    rpage = PageGetTempPageCopy(BufferGetPage(origbuf));
620
0
  Size    pageSize = PageGetPageSize(lpage);
621
0
  PGAlignedBlock tupstore[2]; /* could need 2 pages' worth of tuples */
622
623
0
  entryPreparePage(btree, lpage, off, insertData, updateblkno);
624
625
  /*
626
   * First, append all the existing tuples and the new tuple we're inserting
627
   * one after another in a temporary workspace.
628
   */
629
0
  maxoff = PageGetMaxOffsetNumber(lpage);
630
0
  ptr = tupstore[0].data;
631
0
  for (i = FirstOffsetNumber; i <= maxoff; i++)
632
0
  {
633
0
    if (i == off)
634
0
    {
635
0
      size = MAXALIGN(IndexTupleSize(insertData->entry));
636
0
      memcpy(ptr, insertData->entry, size);
637
0
      ptr += size;
638
0
      totalsize += size + sizeof(ItemIdData);
639
0
    }
640
641
0
    itup = (IndexTuple) PageGetItem(lpage, PageGetItemId(lpage, i));
642
0
    size = MAXALIGN(IndexTupleSize(itup));
643
0
    memcpy(ptr, itup, size);
644
0
    ptr += size;
645
0
    totalsize += size + sizeof(ItemIdData);
646
0
  }
647
648
0
  if (off == maxoff + 1)
649
0
  {
650
0
    size = MAXALIGN(IndexTupleSize(insertData->entry));
651
0
    memcpy(ptr, insertData->entry, size);
652
0
    ptr += size;
653
0
    totalsize += size + sizeof(ItemIdData);
654
0
  }
655
656
  /*
657
   * Initialize the left and right pages, and copy all the tuples back to
658
   * them.
659
   */
660
0
  GinInitPage(rpage, GinPageGetOpaque(lpage)->flags, pageSize);
661
0
  GinInitPage(lpage, GinPageGetOpaque(rpage)->flags, pageSize);
662
663
0
  ptr = tupstore[0].data;
664
0
  maxoff++;
665
0
  lsize = 0;
666
667
0
  page = lpage;
668
0
  for (i = FirstOffsetNumber; i <= maxoff; i++)
669
0
  {
670
0
    itup = (IndexTuple) ptr;
671
672
    /*
673
     * Decide where to split.  We try to equalize the pages' total data
674
     * size, not number of tuples.
675
     */
676
0
    if (lsize > totalsize / 2)
677
0
    {
678
0
      if (separator == InvalidOffsetNumber)
679
0
        separator = i - 1;
680
0
      page = rpage;
681
0
    }
682
0
    else
683
0
    {
684
0
      lsize += MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
685
0
    }
686
687
0
    if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
688
0
      elog(ERROR, "failed to add item to index page in \"%s\"",
689
0
         RelationGetRelationName(btree->index));
690
0
    ptr += MAXALIGN(IndexTupleSize(itup));
691
0
  }
692
693
  /* return temp pages to caller */
694
0
  *newlpage = lpage;
695
0
  *newrpage = rpage;
696
0
}
697
698
/*
699
 * Construct insertion payload for inserting the downlink for given buffer.
700
 */
701
static void *
702
entryPrepareDownlink(GinBtree btree, Buffer lbuf)
703
0
{
704
0
  GinBtreeEntryInsertData *insertData;
705
0
  Page    lpage = BufferGetPage(lbuf);
706
0
  BlockNumber lblkno = BufferGetBlockNumber(lbuf);
707
0
  IndexTuple  itup;
708
709
0
  itup = getRightMostTuple(lpage);
710
711
0
  insertData = palloc(sizeof(GinBtreeEntryInsertData));
712
0
  insertData->entry = GinFormInteriorTuple(itup, lpage, lblkno);
713
0
  insertData->isDelete = false;
714
715
0
  return insertData;
716
0
}
717
718
/*
719
 * Fills new root by rightest values from child.
720
 * Also called from ginxlog, should not use btree
721
 */
722
void
723
ginEntryFillRoot(GinBtree btree, Page root,
724
         BlockNumber lblkno, Page lpage,
725
         BlockNumber rblkno, Page rpage)
726
0
{
727
0
  IndexTuple  itup;
728
729
0
  itup = GinFormInteriorTuple(getRightMostTuple(lpage), lpage, lblkno);
730
0
  if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
731
0
    elog(ERROR, "failed to add item to index root page");
732
0
  pfree(itup);
733
734
0
  itup = GinFormInteriorTuple(getRightMostTuple(rpage), rpage, rblkno);
735
0
  if (PageAddItem(root, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false, false) == InvalidOffsetNumber)
736
0
    elog(ERROR, "failed to add item to index root page");
737
0
  pfree(itup);
738
0
}
739
740
/*
741
 * Set up GinBtree for entry page access
742
 *
743
 * Note: during WAL recovery, there may be no valid data in ginstate
744
 * other than a faked-up Relation pointer; the key datum is bogus too.
745
 */
746
void
747
ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum,
748
          Datum key, GinNullCategory category,
749
          GinState *ginstate)
750
0
{
751
0
  memset(btree, 0, sizeof(GinBtreeData));
752
753
0
  btree->index = ginstate->index;
754
0
  btree->rootBlkno = GIN_ROOT_BLKNO;
755
0
  btree->ginstate = ginstate;
756
757
0
  btree->findChildPage = entryLocateEntry;
758
0
  btree->getLeftMostChild = entryGetLeftMostPage;
759
0
  btree->isMoveRight = entryIsMoveRight;
760
0
  btree->findItem = entryLocateLeafEntry;
761
0
  btree->findChildPtr = entryFindChildPtr;
762
0
  btree->beginPlaceToPage = entryBeginPlaceToPage;
763
0
  btree->execPlaceToPage = entryExecPlaceToPage;
764
0
  btree->fillRoot = ginEntryFillRoot;
765
0
  btree->prepareDownlink = entryPrepareDownlink;
766
767
0
  btree->isData = false;
768
0
  btree->fullScan = false;
769
0
  btree->isBuild = false;
770
771
0
  btree->entryAttnum = attnum;
772
0
  btree->entryKey = key;
773
0
  btree->entryCategory = category;
774
0
}