Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/storage/page/bufpage.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * bufpage.c
4
 *    POSTGRES standard buffer page code.
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/storage/page/bufpage.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/htup_details.h"
18
#include "access/itup.h"
19
#include "access/xlog.h"
20
#include "pgstat.h"
21
#include "storage/checksum.h"
22
#include "utils/memdebug.h"
23
#include "utils/memutils.h"
24
25
26
/* GUC variable */
27
bool    ignore_checksum_failure = false;
28
29
30
/* ----------------------------------------------------------------
31
 *            Page support functions
32
 * ----------------------------------------------------------------
33
 */
34
35
/*
36
 * PageInit
37
 *    Initializes the contents of a page.
38
 *    Note that we don't calculate an initial checksum here; that's not done
39
 *    until it's time to write.
40
 */
41
void
42
PageInit(Page page, Size pageSize, Size specialSize)
43
0
{
44
0
  PageHeader  p = (PageHeader) page;
45
46
0
  specialSize = MAXALIGN(specialSize);
47
48
0
  Assert(pageSize == BLCKSZ);
49
0
  Assert(pageSize > specialSize + SizeOfPageHeaderData);
50
51
  /* Make sure all fields of page are zero, as well as unused space */
52
0
  MemSet(p, 0, pageSize);
53
54
0
  p->pd_flags = 0;
55
0
  p->pd_lower = SizeOfPageHeaderData;
56
0
  p->pd_upper = pageSize - specialSize;
57
0
  p->pd_special = pageSize - specialSize;
58
0
  PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
59
  /* p->pd_prune_xid = InvalidTransactionId;    done by above MemSet */
60
0
}
61
62
63
/*
64
 * PageIsVerified
65
 *    Check that the page header and checksum (if any) appear valid.
66
 *
67
 * This is called when a page has just been read in from disk.  The idea is
68
 * to cheaply detect trashed pages before we go nuts following bogus line
69
 * pointers, testing invalid transaction identifiers, etc.
70
 *
71
 * It turns out to be necessary to allow zeroed pages here too.  Even though
72
 * this routine is *not* called when deliberately adding a page to a relation,
73
 * there are scenarios in which a zeroed page might be found in a table.
74
 * (Example: a backend extends a relation, then crashes before it can write
75
 * any WAL entry about the new page.  The kernel will already have the
76
 * zeroed page in the file, and it will stay that way after restart.)  So we
77
 * allow zeroed pages here, and are careful that the page access macros
78
 * treat such a page as empty and without free space.  Eventually, VACUUM
79
 * will clean up such a page and make it usable.
80
 *
81
 * If flag PIV_LOG_WARNING/PIV_LOG_LOG is set, a WARNING/LOG message is logged
82
 * in the event of a checksum failure.
83
 *
84
 * If flag PIV_IGNORE_CHECKSUM_FAILURE is set, checksum failures will cause a
85
 * message about the failure to be emitted, but will not cause
86
 * PageIsVerified() to return false.
87
 *
88
 * To allow the caller to report statistics about checksum failures,
89
 * *checksum_failure_p can be passed in. Note that there may be checksum
90
 * failures even if this function returns true, due to
91
 * PIV_IGNORE_CHECKSUM_FAILURE.
92
 */
93
bool
94
PageIsVerified(PageData *page, BlockNumber blkno, int flags, bool *checksum_failure_p)
95
0
{
96
0
  const PageHeaderData *p = (const PageHeaderData *) page;
97
0
  size_t     *pagebytes;
98
0
  bool    checksum_failure = false;
99
0
  bool    header_sane = false;
100
0
  uint16    checksum = 0;
101
102
0
  if (checksum_failure_p)
103
0
    *checksum_failure_p = false;
104
105
  /*
106
   * Don't verify page data unless the page passes basic non-zero test
107
   */
108
0
  if (!PageIsNew(page))
109
0
  {
110
    /*
111
     * There shouldn't be any check for interrupt calls happening in this
112
     * codepath, but just to be on the safe side we hold interrupts since
113
     * if they did happen the data checksum state could change during
114
     * verifying checksums, which could lead to incorrect verification
115
     * results.
116
     */
117
0
    HOLD_INTERRUPTS();
118
0
    if (DataChecksumsNeedVerify())
119
0
    {
120
0
      checksum = pg_checksum_page(page, blkno);
121
122
0
      if (checksum != p->pd_checksum)
123
0
      {
124
0
        checksum_failure = true;
125
0
        if (checksum_failure_p)
126
0
          *checksum_failure_p = true;
127
0
      }
128
0
    }
129
0
    RESUME_INTERRUPTS();
130
131
    /*
132
     * The following checks don't prove the header is correct, only that
133
     * it looks sane enough to allow into the buffer pool. Later usage of
134
     * the block can still reveal problems, which is why we offer the
135
     * checksum option.
136
     */
137
0
    if ((p->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
138
0
      p->pd_lower <= p->pd_upper &&
139
0
      p->pd_upper <= p->pd_special &&
140
0
      p->pd_special <= BLCKSZ &&
141
0
      p->pd_special == MAXALIGN(p->pd_special))
142
0
      header_sane = true;
143
144
0
    if (header_sane && !checksum_failure)
145
0
      return true;
146
0
  }
147
148
  /* Check all-zeroes case */
149
0
  pagebytes = (size_t *) page;
150
151
0
  if (pg_memory_is_all_zeros(pagebytes, BLCKSZ))
152
0
    return true;
153
154
  /*
155
   * Throw a WARNING/LOG, as instructed by PIV_LOG_*, if the checksum fails,
156
   * but only after we've checked for the all-zeroes case.
157
   */
158
0
  if (checksum_failure)
159
0
  {
160
0
    if ((flags & (PIV_LOG_WARNING | PIV_LOG_LOG)) != 0)
161
0
      ereport(flags & PIV_LOG_WARNING ? WARNING : LOG,
162
0
          (errcode(ERRCODE_DATA_CORRUPTED),
163
0
           ((flags & PIV_ZERO_BUFFERS_ON_ERROR) && !(flags & PIV_IGNORE_CHECKSUM_FAILURE)) ?
164
0
           errmsg("page verification failed, calculated checksum %u but expected %u, buffer will be zeroed",
165
0
              checksum, p->pd_checksum) :
166
0
           errmsg("page verification failed, calculated checksum %u but expected %u",
167
0
              checksum, p->pd_checksum)));
168
169
0
    if (header_sane && (flags & PIV_IGNORE_CHECKSUM_FAILURE))
170
0
      return true;
171
0
  }
172
173
0
  return false;
174
0
}
175
176
177
/*
178
 *  PageAddItemExtended
179
 *
180
 *  Add an item to a page.  Return value is the offset at which it was
181
 *  inserted, or InvalidOffsetNumber if the item is not inserted for any
182
 *  reason.  A WARNING is issued indicating the reason for the refusal.
183
 *
184
 *  offsetNumber must be either InvalidOffsetNumber to specify finding a
185
 *  free line pointer, or a value between FirstOffsetNumber and one past
186
 *  the last existing item, to specify using that particular line pointer.
187
 *
188
 *  If offsetNumber is valid and flag PAI_OVERWRITE is set, we just store
189
 *  the item at the specified offsetNumber, which must be either a
190
 *  currently-unused line pointer, or one past the last existing item.
191
 *
192
 *  If offsetNumber is valid and flag PAI_OVERWRITE is not set, insert
193
 *  the item at the specified offsetNumber, moving existing items later
194
 *  in the array to make room.
195
 *
196
 *  If offsetNumber is not valid, then assign a slot by finding the first
197
 *  one that is both unused and deallocated.
198
 *
199
 *  If flag PAI_IS_HEAP is set, we enforce that there can't be more than
200
 *  MaxHeapTuplesPerPage line pointers on the page.
201
 *
202
 *  !!! EREPORT(ERROR) IS DISALLOWED HERE !!!
203
 */
204
OffsetNumber
205
PageAddItemExtended(Page page,
206
          const void *item,
207
          Size size,
208
          OffsetNumber offsetNumber,
209
          int flags)
210
0
{
211
0
  PageHeader  phdr = (PageHeader) page;
212
0
  Size    alignedSize;
213
0
  int     lower;
214
0
  int     upper;
215
0
  ItemId    itemId;
216
0
  OffsetNumber limit;
217
0
  bool    needshuffle = false;
218
219
  /*
220
   * Be wary about corrupted page pointers
221
   */
222
0
  if (phdr->pd_lower < SizeOfPageHeaderData ||
223
0
    phdr->pd_lower > phdr->pd_upper ||
224
0
    phdr->pd_upper > phdr->pd_special ||
225
0
    phdr->pd_special > BLCKSZ)
226
0
    ereport(PANIC,
227
0
        (errcode(ERRCODE_DATA_CORRUPTED),
228
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
229
0
            phdr->pd_lower, phdr->pd_upper, phdr->pd_special)));
230
231
  /*
232
   * Select offsetNumber to place the new item at
233
   */
234
0
  limit = OffsetNumberNext(PageGetMaxOffsetNumber(page));
235
236
  /* was offsetNumber passed in? */
237
0
  if (OffsetNumberIsValid(offsetNumber))
238
0
  {
239
    /* yes, check it */
240
0
    if ((flags & PAI_OVERWRITE) != 0)
241
0
    {
242
0
      if (offsetNumber < limit)
243
0
      {
244
0
        itemId = PageGetItemId(page, offsetNumber);
245
0
        if (ItemIdIsUsed(itemId) || ItemIdHasStorage(itemId))
246
0
        {
247
0
          elog(WARNING, "will not overwrite a used ItemId");
248
0
          return InvalidOffsetNumber;
249
0
        }
250
0
      }
251
0
    }
252
0
    else
253
0
    {
254
0
      if (offsetNumber < limit)
255
0
        needshuffle = true; /* need to move existing linp's */
256
0
    }
257
0
  }
258
0
  else
259
0
  {
260
    /* offsetNumber was not passed in, so find a free slot */
261
    /* if no free slot, we'll put it at limit (1st open slot) */
262
0
    if (PageHasFreeLinePointers(page))
263
0
    {
264
      /*
265
       * Scan line pointer array to locate a "recyclable" (unused)
266
       * ItemId.
267
       *
268
       * Always use earlier items first.  PageTruncateLinePointerArray
269
       * can only truncate unused items when they appear as a contiguous
270
       * group at the end of the line pointer array.
271
       */
272
0
      for (offsetNumber = FirstOffsetNumber;
273
0
         offsetNumber < limit; /* limit is maxoff+1 */
274
0
         offsetNumber++)
275
0
      {
276
0
        itemId = PageGetItemId(page, offsetNumber);
277
278
        /*
279
         * We check for no storage as well, just to be paranoid;
280
         * unused items should never have storage.  Assert() that the
281
         * invariant is respected too.
282
         */
283
0
        Assert(ItemIdIsUsed(itemId) || !ItemIdHasStorage(itemId));
284
285
0
        if (!ItemIdIsUsed(itemId) && !ItemIdHasStorage(itemId))
286
0
          break;
287
0
      }
288
0
      if (offsetNumber >= limit)
289
0
      {
290
        /* the hint is wrong, so reset it */
291
0
        PageClearHasFreeLinePointers(page);
292
0
      }
293
0
    }
294
0
    else
295
0
    {
296
      /* don't bother searching if hint says there's no free slot */
297
0
      offsetNumber = limit;
298
0
    }
299
0
  }
300
301
  /* Reject placing items beyond the first unused line pointer */
302
0
  if (offsetNumber > limit)
303
0
  {
304
0
    elog(WARNING, "specified item offset is too large");
305
0
    return InvalidOffsetNumber;
306
0
  }
307
308
  /* Reject placing items beyond heap boundary, if heap */
309
0
  if ((flags & PAI_IS_HEAP) != 0 && offsetNumber > MaxHeapTuplesPerPage)
310
0
  {
311
0
    elog(WARNING, "can't put more than MaxHeapTuplesPerPage items in a heap page");
312
0
    return InvalidOffsetNumber;
313
0
  }
314
315
  /*
316
   * Compute new lower and upper pointers for page, see if it'll fit.
317
   *
318
   * Note: do arithmetic as signed ints, to avoid mistakes if, say,
319
   * alignedSize > pd_upper.
320
   */
321
0
  if (offsetNumber == limit || needshuffle)
322
0
    lower = phdr->pd_lower + sizeof(ItemIdData);
323
0
  else
324
0
    lower = phdr->pd_lower;
325
326
0
  alignedSize = MAXALIGN(size);
327
328
0
  upper = (int) phdr->pd_upper - (int) alignedSize;
329
330
0
  if (lower > upper)
331
0
    return InvalidOffsetNumber;
332
333
  /*
334
   * OK to insert the item.  First, shuffle the existing pointers if needed.
335
   */
336
0
  itemId = PageGetItemId(page, offsetNumber);
337
338
0
  if (needshuffle)
339
0
    memmove(itemId + 1, itemId,
340
0
        (limit - offsetNumber) * sizeof(ItemIdData));
341
342
  /* set the line pointer */
343
0
  ItemIdSetNormal(itemId, upper, size);
344
345
  /*
346
   * Items normally contain no uninitialized bytes.  Core bufpage consumers
347
   * conform, but this is not a necessary coding rule; a new index AM could
348
   * opt to depart from it.  However, data type input functions and other
349
   * C-language functions that synthesize datums should initialize all
350
   * bytes; datumIsEqual() relies on this.  Testing here, along with the
351
   * similar check in printtup(), helps to catch such mistakes.
352
   *
353
   * Values of the "name" type retrieved via index-only scans may contain
354
   * uninitialized bytes; see comment in btrescan().  Valgrind will report
355
   * this as an error, but it is safe to ignore.
356
   */
357
0
  VALGRIND_CHECK_MEM_IS_DEFINED(item, size);
358
359
  /* copy the item's data onto the page */
360
0
  memcpy((char *) page + upper, item, size);
361
362
  /* adjust page header */
363
0
  phdr->pd_lower = (LocationIndex) lower;
364
0
  phdr->pd_upper = (LocationIndex) upper;
365
366
0
  return offsetNumber;
367
0
}
368
369
370
/*
371
 * PageGetTempPage
372
 *    Get a temporary page in local memory for special processing.
373
 *    The returned page is not initialized at all; caller must do that.
374
 */
375
Page
376
PageGetTempPage(const PageData *page)
377
0
{
378
0
  Size    pageSize;
379
0
  Page    temp;
380
381
0
  pageSize = PageGetPageSize(page);
382
0
  temp = (Page) palloc(pageSize);
383
384
0
  return temp;
385
0
}
386
387
/*
388
 * PageGetTempPageCopy
389
 *    Get a temporary page in local memory for special processing.
390
 *    The page is initialized by copying the contents of the given page.
391
 */
392
Page
393
PageGetTempPageCopy(const PageData *page)
394
0
{
395
0
  Size    pageSize;
396
0
  Page    temp;
397
398
0
  pageSize = PageGetPageSize(page);
399
0
  temp = (Page) palloc(pageSize);
400
401
0
  memcpy(temp, page, pageSize);
402
403
0
  return temp;
404
0
}
405
406
/*
407
 * PageGetTempPageCopySpecial
408
 *    Get a temporary page in local memory for special processing.
409
 *    The page is PageInit'd with the same special-space size as the
410
 *    given page, and the special space is copied from the given page.
411
 */
412
Page
413
PageGetTempPageCopySpecial(const PageData *page)
414
0
{
415
0
  Size    pageSize;
416
0
  Page    temp;
417
418
0
  pageSize = PageGetPageSize(page);
419
0
  temp = (Page) palloc(pageSize);
420
421
0
  PageInit(temp, pageSize, PageGetSpecialSize(page));
422
0
  memcpy(PageGetSpecialPointer(temp),
423
0
       PageGetSpecialPointer(page),
424
0
       PageGetSpecialSize(page));
425
426
0
  return temp;
427
0
}
428
429
/*
430
 * PageRestoreTempPage
431
 *    Copy temporary page back to permanent page after special processing
432
 *    and release the temporary page.
433
 */
434
void
435
PageRestoreTempPage(Page tempPage, Page oldPage)
436
0
{
437
0
  Size    pageSize;
438
439
0
  pageSize = PageGetPageSize(tempPage);
440
0
  memcpy(oldPage, tempPage, pageSize);
441
442
0
  pfree(tempPage);
443
0
}
444
445
/*
446
 * Tuple defrag support for PageRepairFragmentation and PageIndexMultiDelete
447
 */
448
typedef struct itemIdCompactData
449
{
450
  uint16    offsetindex;  /* linp array index */
451
  int16   itemoff;    /* page offset of item data */
452
  uint16    alignedlen;   /* MAXALIGN(item data len) */
453
} itemIdCompactData;
454
typedef itemIdCompactData *itemIdCompact;
455
456
/*
457
 * After removing or marking some line pointers unused, move the tuples to
458
 * remove the gaps caused by the removed items and reorder them back into
459
 * reverse line pointer order in the page.
460
 *
461
 * This function can often be fairly hot, so it pays to take some measures to
462
 * make it as optimal as possible.
463
 *
464
 * Callers may pass 'presorted' as true if the 'itemidbase' array is sorted in
465
 * descending order of itemoff.  When this is true we can just memmove()
466
 * tuples towards the end of the page.  This is quite a common case as it's
467
 * the order that tuples are initially inserted into pages.  When we call this
468
 * function to defragment the tuples in the page then any new line pointers
469
 * added to the page will keep that presorted order, so hitting this case is
470
 * still very common for tables that are commonly updated.
471
 *
472
 * When the 'itemidbase' array is not presorted then we're unable to just
473
 * memmove() tuples around freely.  Doing so could cause us to overwrite the
474
 * memory belonging to a tuple we've not moved yet.  In this case, we copy all
475
 * the tuples that need to be moved into a temporary buffer.  We can then
476
 * simply memcpy() out of that temp buffer back into the page at the correct
477
 * location.  Tuples are copied back into the page in the same order as the
478
 * 'itemidbase' array, so we end up reordering the tuples back into reverse
479
 * line pointer order.  This will increase the chances of hitting the
480
 * presorted case the next time around.
481
 *
482
 * Callers must ensure that nitems is > 0
483
 */
484
static void
485
compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorted)
486
0
{
487
0
  PageHeader  phdr = (PageHeader) page;
488
0
  Offset    upper;
489
0
  Offset    copy_tail;
490
0
  Offset    copy_head;
491
0
  itemIdCompact itemidptr;
492
0
  int     i;
493
494
  /* Code within will not work correctly if nitems == 0 */
495
0
  Assert(nitems > 0);
496
497
0
  if (presorted)
498
0
  {
499
500
#ifdef USE_ASSERT_CHECKING
501
    {
502
      /*
503
       * Verify we've not gotten any new callers that are incorrectly
504
       * passing a true presorted value.
505
       */
506
      Offset    lastoff = phdr->pd_special;
507
508
      for (i = 0; i < nitems; i++)
509
      {
510
        itemidptr = &itemidbase[i];
511
512
        Assert(lastoff > itemidptr->itemoff);
513
514
        lastoff = itemidptr->itemoff;
515
      }
516
    }
517
#endif              /* USE_ASSERT_CHECKING */
518
519
    /*
520
     * 'itemidbase' is already in the optimal order, i.e, lower item
521
     * pointers have a higher offset.  This allows us to memmove() the
522
     * tuples up to the end of the page without having to worry about
523
     * overwriting other tuples that have not been moved yet.
524
     *
525
     * There's a good chance that there are tuples already right at the
526
     * end of the page that we can simply skip over because they're
527
     * already in the correct location within the page.  We'll do that
528
     * first...
529
     */
530
0
    upper = phdr->pd_special;
531
0
    i = 0;
532
0
    do
533
0
    {
534
0
      itemidptr = &itemidbase[i];
535
0
      if (upper != itemidptr->itemoff + itemidptr->alignedlen)
536
0
        break;
537
0
      upper -= itemidptr->alignedlen;
538
539
0
      i++;
540
0
    } while (i < nitems);
541
542
    /*
543
     * Now that we've found the first tuple that needs to be moved, we can
544
     * do the tuple compactification.  We try and make the least number of
545
     * memmove() calls and only call memmove() when there's a gap.  When
546
     * we see a gap we just move all tuples after the gap up until the
547
     * point of the last move operation.
548
     */
549
0
    copy_tail = copy_head = itemidptr->itemoff + itemidptr->alignedlen;
550
0
    for (; i < nitems; i++)
551
0
    {
552
0
      ItemId    lp;
553
554
0
      itemidptr = &itemidbase[i];
555
0
      lp = PageGetItemId(page, itemidptr->offsetindex + 1);
556
557
0
      if (copy_head != itemidptr->itemoff + itemidptr->alignedlen)
558
0
      {
559
0
        memmove((char *) page + upper,
560
0
            page + copy_head,
561
0
            copy_tail - copy_head);
562
563
        /*
564
         * We've now moved all tuples already seen, but not the
565
         * current tuple, so we set the copy_tail to the end of this
566
         * tuple so it can be moved in another iteration of the loop.
567
         */
568
0
        copy_tail = itemidptr->itemoff + itemidptr->alignedlen;
569
0
      }
570
      /* shift the target offset down by the length of this tuple */
571
0
      upper -= itemidptr->alignedlen;
572
      /* point the copy_head to the start of this tuple */
573
0
      copy_head = itemidptr->itemoff;
574
575
      /* update the line pointer to reference the new offset */
576
0
      lp->lp_off = upper;
577
0
    }
578
579
    /* move the remaining tuples. */
580
0
    memmove((char *) page + upper,
581
0
        page + copy_head,
582
0
        copy_tail - copy_head);
583
0
  }
584
0
  else
585
0
  {
586
0
    PGAlignedBlock scratch;
587
0
    char     *scratchptr = scratch.data;
588
589
    /*
590
     * Non-presorted case:  The tuples in the itemidbase array may be in
591
     * any order.  So, in order to move these to the end of the page we
592
     * must make a temp copy of each tuple that needs to be moved before
593
     * we copy them back into the page at the new offset.
594
     *
595
     * If a large percentage of tuples have been pruned (>75%) then we'll
596
     * copy these into the temp buffer tuple-by-tuple, otherwise, we'll
597
     * just do a single memcpy() for all tuples that need to be moved.
598
     * When so many tuples have been removed there's likely to be a lot of
599
     * gaps and it's unlikely that many non-movable tuples remain at the
600
     * end of the page.
601
     */
602
0
    if (nitems < PageGetMaxOffsetNumber(page) / 4)
603
0
    {
604
0
      i = 0;
605
0
      do
606
0
      {
607
0
        itemidptr = &itemidbase[i];
608
0
        memcpy(scratchptr + itemidptr->itemoff, page + itemidptr->itemoff,
609
0
             itemidptr->alignedlen);
610
0
        i++;
611
0
      } while (i < nitems);
612
613
      /* Set things up for the compactification code below */
614
0
      i = 0;
615
0
      itemidptr = &itemidbase[0];
616
0
      upper = phdr->pd_special;
617
0
    }
618
0
    else
619
0
    {
620
0
      upper = phdr->pd_special;
621
622
      /*
623
       * Many tuples are likely to already be in the correct location.
624
       * There's no need to copy these into the temp buffer.  Instead
625
       * we'll just skip forward in the itemidbase array to the position
626
       * that we do need to move tuples from so that the code below just
627
       * leaves these ones alone.
628
       */
629
0
      i = 0;
630
0
      do
631
0
      {
632
0
        itemidptr = &itemidbase[i];
633
0
        if (upper != itemidptr->itemoff + itemidptr->alignedlen)
634
0
          break;
635
0
        upper -= itemidptr->alignedlen;
636
637
0
        i++;
638
0
      } while (i < nitems);
639
640
      /* Copy all tuples that need to be moved into the temp buffer */
641
0
      memcpy(scratchptr + phdr->pd_upper,
642
0
           page + phdr->pd_upper,
643
0
           upper - phdr->pd_upper);
644
0
    }
645
646
    /*
647
     * Do the tuple compactification.  itemidptr is already pointing to
648
     * the first tuple that we're going to move.  Here we collapse the
649
     * memcpy calls for adjacent tuples into a single call.  This is done
650
     * by delaying the memcpy call until we find a gap that needs to be
651
     * closed.
652
     */
653
0
    copy_tail = copy_head = itemidptr->itemoff + itemidptr->alignedlen;
654
0
    for (; i < nitems; i++)
655
0
    {
656
0
      ItemId    lp;
657
658
0
      itemidptr = &itemidbase[i];
659
0
      lp = PageGetItemId(page, itemidptr->offsetindex + 1);
660
661
      /* copy pending tuples when we detect a gap */
662
0
      if (copy_head != itemidptr->itemoff + itemidptr->alignedlen)
663
0
      {
664
0
        memcpy((char *) page + upper,
665
0
             scratchptr + copy_head,
666
0
             copy_tail - copy_head);
667
668
        /*
669
         * We've now copied all tuples already seen, but not the
670
         * current tuple, so we set the copy_tail to the end of this
671
         * tuple.
672
         */
673
0
        copy_tail = itemidptr->itemoff + itemidptr->alignedlen;
674
0
      }
675
      /* shift the target offset down by the length of this tuple */
676
0
      upper -= itemidptr->alignedlen;
677
      /* point the copy_head to the start of this tuple */
678
0
      copy_head = itemidptr->itemoff;
679
680
      /* update the line pointer to reference the new offset */
681
0
      lp->lp_off = upper;
682
0
    }
683
684
    /* Copy the remaining chunk */
685
0
    memcpy((char *) page + upper,
686
0
         scratchptr + copy_head,
687
0
         copy_tail - copy_head);
688
0
  }
689
690
0
  phdr->pd_upper = upper;
691
0
}
692
693
/*
694
 * PageRepairFragmentation
695
 *
696
 * Frees fragmented space on a heap page following pruning.
697
 *
698
 * This routine is usable for heap pages only, but see PageIndexMultiDelete.
699
 *
700
 * This routine removes unused line pointers from the end of the line pointer
701
 * array.  This is possible when dead heap-only tuples get removed by pruning,
702
 * especially when there were HOT chains with several tuples each beforehand.
703
 *
704
 * Caller had better have a full cleanup lock on page's buffer.  As a side
705
 * effect the page's PD_HAS_FREE_LINES hint bit will be set or unset as
706
 * needed.  Caller might also need to account for a reduction in the length of
707
 * the line pointer array following array truncation.
708
 */
709
void
710
PageRepairFragmentation(Page page)
711
0
{
712
0
  Offset    pd_lower = ((PageHeader) page)->pd_lower;
713
0
  Offset    pd_upper = ((PageHeader) page)->pd_upper;
714
0
  Offset    pd_special = ((PageHeader) page)->pd_special;
715
0
  Offset    last_offset;
716
0
  itemIdCompactData itemidbase[MaxHeapTuplesPerPage];
717
0
  itemIdCompact itemidptr;
718
0
  ItemId    lp;
719
0
  int     nline,
720
0
        nstorage,
721
0
        nunused;
722
0
  OffsetNumber finalusedlp = InvalidOffsetNumber;
723
0
  int     i;
724
0
  Size    totallen;
725
0
  bool    presorted = true; /* For now */
726
727
  /*
728
   * It's worth the trouble to be more paranoid here than in most places,
729
   * because we are about to reshuffle data in (what is usually) a shared
730
   * disk buffer.  If we aren't careful then corrupted pointers, lengths,
731
   * etc could cause us to clobber adjacent disk buffers, spreading the data
732
   * loss further.  So, check everything.
733
   */
734
0
  if (pd_lower < SizeOfPageHeaderData ||
735
0
    pd_lower > pd_upper ||
736
0
    pd_upper > pd_special ||
737
0
    pd_special > BLCKSZ ||
738
0
    pd_special != MAXALIGN(pd_special))
739
0
    ereport(ERROR,
740
0
        (errcode(ERRCODE_DATA_CORRUPTED),
741
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
742
0
            pd_lower, pd_upper, pd_special)));
743
744
  /*
745
   * Run through the line pointer array and collect data about live items.
746
   */
747
0
  nline = PageGetMaxOffsetNumber(page);
748
0
  itemidptr = itemidbase;
749
0
  nunused = totallen = 0;
750
0
  last_offset = pd_special;
751
0
  for (i = FirstOffsetNumber; i <= nline; i++)
752
0
  {
753
0
    lp = PageGetItemId(page, i);
754
0
    if (ItemIdIsUsed(lp))
755
0
    {
756
0
      if (ItemIdHasStorage(lp))
757
0
      {
758
0
        itemidptr->offsetindex = i - 1;
759
0
        itemidptr->itemoff = ItemIdGetOffset(lp);
760
761
0
        if (last_offset > itemidptr->itemoff)
762
0
          last_offset = itemidptr->itemoff;
763
0
        else
764
0
          presorted = false;
765
766
0
        if (unlikely(itemidptr->itemoff < (int) pd_upper ||
767
0
               itemidptr->itemoff >= (int) pd_special))
768
0
          ereport(ERROR,
769
0
              (errcode(ERRCODE_DATA_CORRUPTED),
770
0
               errmsg("corrupted line pointer: %u",
771
0
                  itemidptr->itemoff)));
772
0
        itemidptr->alignedlen = MAXALIGN(ItemIdGetLength(lp));
773
0
        totallen += itemidptr->alignedlen;
774
0
        itemidptr++;
775
0
      }
776
777
0
      finalusedlp = i;  /* Could be the final non-LP_UNUSED item */
778
0
    }
779
0
    else
780
0
    {
781
      /* Unused entries should have lp_len = 0, but make sure */
782
0
      Assert(!ItemIdHasStorage(lp));
783
0
      ItemIdSetUnused(lp);
784
0
      nunused++;
785
0
    }
786
0
  }
787
788
0
  nstorage = itemidptr - itemidbase;
789
0
  if (nstorage == 0)
790
0
  {
791
    /* Page is completely empty, so just reset it quickly */
792
0
    ((PageHeader) page)->pd_upper = pd_special;
793
0
  }
794
0
  else
795
0
  {
796
    /* Need to compact the page the hard way */
797
0
    if (totallen > (Size) (pd_special - pd_lower))
798
0
      ereport(ERROR,
799
0
          (errcode(ERRCODE_DATA_CORRUPTED),
800
0
           errmsg("corrupted item lengths: total %zu, available space %u",
801
0
              totallen, pd_special - pd_lower)));
802
803
0
    compactify_tuples(itemidbase, nstorage, page, presorted);
804
0
  }
805
806
0
  if (finalusedlp != nline)
807
0
  {
808
    /* The last line pointer is not the last used line pointer */
809
0
    int     nunusedend = nline - finalusedlp;
810
811
0
    Assert(nunused >= nunusedend && nunusedend > 0);
812
813
    /* remove trailing unused line pointers from the count */
814
0
    nunused -= nunusedend;
815
    /* truncate the line pointer array */
816
0
    ((PageHeader) page)->pd_lower -= (sizeof(ItemIdData) * nunusedend);
817
0
  }
818
819
  /* Set hint bit for PageAddItemExtended */
820
0
  if (nunused > 0)
821
0
    PageSetHasFreeLinePointers(page);
822
0
  else
823
0
    PageClearHasFreeLinePointers(page);
824
0
}
825
826
/*
827
 * PageTruncateLinePointerArray
828
 *
829
 * Removes unused line pointers at the end of the line pointer array.
830
 *
831
 * This routine is usable for heap pages only.  It is called by VACUUM during
832
 * its second pass over the heap.  We expect at least one LP_UNUSED line
833
 * pointer on the page (if VACUUM didn't have an LP_DEAD item on the page that
834
 * it just set to LP_UNUSED then it should not call here).
835
 *
836
 * We avoid truncating the line pointer array to 0 items, if necessary by
837
 * leaving behind a single remaining LP_UNUSED item.  This is a little
838
 * arbitrary, but it seems like a good idea to avoid leaving a PageIsEmpty()
839
 * page behind.
840
 *
841
 * Caller can have either an exclusive lock or a full cleanup lock on page's
842
 * buffer.  The page's PD_HAS_FREE_LINES hint bit will be set or unset based
843
 * on whether or not we leave behind any remaining LP_UNUSED items.
844
 */
845
void
846
PageTruncateLinePointerArray(Page page)
847
0
{
848
0
  PageHeader  phdr = (PageHeader) page;
849
0
  bool    countdone = false,
850
0
        sethint = false;
851
0
  int     nunusedend = 0;
852
853
  /* Scan line pointer array back-to-front */
854
0
  for (int i = PageGetMaxOffsetNumber(page); i >= FirstOffsetNumber; i--)
855
0
  {
856
0
    ItemId    lp = PageGetItemId(page, i);
857
858
0
    if (!countdone && i > FirstOffsetNumber)
859
0
    {
860
      /*
861
       * Still determining which line pointers from the end of the array
862
       * will be truncated away.  Either count another line pointer as
863
       * safe to truncate, or notice that it's not safe to truncate
864
       * additional line pointers (stop counting line pointers).
865
       */
866
0
      if (!ItemIdIsUsed(lp))
867
0
        nunusedend++;
868
0
      else
869
0
        countdone = true;
870
0
    }
871
0
    else
872
0
    {
873
      /*
874
       * Once we've stopped counting we still need to figure out if
875
       * there are any remaining LP_UNUSED line pointers somewhere more
876
       * towards the front of the array.
877
       */
878
0
      if (!ItemIdIsUsed(lp))
879
0
      {
880
        /*
881
         * This is an unused line pointer that we won't be truncating
882
         * away -- so there is at least one.  Set hint on page.
883
         */
884
0
        sethint = true;
885
0
        break;
886
0
      }
887
0
    }
888
0
  }
889
890
0
  if (nunusedend > 0)
891
0
  {
892
0
    phdr->pd_lower -= sizeof(ItemIdData) * nunusedend;
893
894
#ifdef CLOBBER_FREED_MEMORY
895
    memset((char *) page + phdr->pd_lower, 0x7F,
896
         sizeof(ItemIdData) * nunusedend);
897
#endif
898
0
  }
899
0
  else
900
0
    Assert(sethint);
901
902
  /* Set hint bit for PageAddItemExtended */
903
0
  if (sethint)
904
0
    PageSetHasFreeLinePointers(page);
905
0
  else
906
0
    PageClearHasFreeLinePointers(page);
907
0
}
908
909
/*
910
 * PageGetFreeSpace
911
 *    Returns the size of the free (allocatable) space on a page,
912
 *    reduced by the space needed for a new line pointer.
913
 *
914
 * Note: this should usually only be used on index pages.  Use
915
 * PageGetHeapFreeSpace on heap pages.
916
 */
917
Size
918
PageGetFreeSpace(const PageData *page)
919
0
{
920
0
  const PageHeaderData *phdr = (const PageHeaderData *) page;
921
0
  int     space;
922
923
  /*
924
   * Use signed arithmetic here so that we behave sensibly if pd_lower >
925
   * pd_upper.
926
   */
927
0
  space = (int) phdr->pd_upper - (int) phdr->pd_lower;
928
929
0
  if (space < (int) sizeof(ItemIdData))
930
0
    return 0;
931
0
  space -= sizeof(ItemIdData);
932
933
0
  return (Size) space;
934
0
}
935
936
/*
937
 * PageGetFreeSpaceForMultipleTuples
938
 *    Returns the size of the free (allocatable) space on a page,
939
 *    reduced by the space needed for multiple new line pointers.
940
 *
941
 * Note: this should usually only be used on index pages.  Use
942
 * PageGetHeapFreeSpace on heap pages.
943
 */
944
Size
945
PageGetFreeSpaceForMultipleTuples(const PageData *page, int ntups)
946
0
{
947
0
  const PageHeaderData *phdr = (const PageHeaderData *) page;
948
0
  int     space;
949
950
  /*
951
   * Use signed arithmetic here so that we behave sensibly if pd_lower >
952
   * pd_upper.
953
   */
954
0
  space = (int) phdr->pd_upper - (int) phdr->pd_lower;
955
956
0
  if (space < (int) (ntups * sizeof(ItemIdData)))
957
0
    return 0;
958
0
  space -= ntups * sizeof(ItemIdData);
959
960
0
  return (Size) space;
961
0
}
962
963
/*
964
 * PageGetExactFreeSpace
965
 *    Returns the size of the free (allocatable) space on a page,
966
 *    without any consideration for adding/removing line pointers.
967
 */
968
Size
969
PageGetExactFreeSpace(const PageData *page)
970
0
{
971
0
  const PageHeaderData *phdr = (const PageHeaderData *) page;
972
0
  int     space;
973
974
  /*
975
   * Use signed arithmetic here so that we behave sensibly if pd_lower >
976
   * pd_upper.
977
   */
978
0
  space = (int) phdr->pd_upper - (int) phdr->pd_lower;
979
980
0
  if (space < 0)
981
0
    return 0;
982
983
0
  return (Size) space;
984
0
}
985
986
987
/*
988
 * PageGetHeapFreeSpace
989
 *    Returns the size of the free (allocatable) space on a page,
990
 *    reduced by the space needed for a new line pointer.
991
 *
992
 * The difference between this and PageGetFreeSpace is that this will return
993
 * zero if there are already MaxHeapTuplesPerPage line pointers in the page
994
 * and none are free.  We use this to enforce that no more than
995
 * MaxHeapTuplesPerPage line pointers are created on a heap page.  (Although
996
 * no more tuples than that could fit anyway, in the presence of redirected
997
 * or dead line pointers it'd be possible to have too many line pointers.
998
 * To avoid breaking code that assumes MaxHeapTuplesPerPage is a hard limit
999
 * on the number of line pointers, we make this extra check.)
1000
 */
1001
Size
1002
PageGetHeapFreeSpace(const PageData *page)
1003
0
{
1004
0
  Size    space;
1005
1006
0
  space = PageGetFreeSpace(page);
1007
0
  if (space > 0)
1008
0
  {
1009
0
    OffsetNumber offnum,
1010
0
          nline;
1011
1012
    /*
1013
     * Are there already MaxHeapTuplesPerPage line pointers in the page?
1014
     */
1015
0
    nline = PageGetMaxOffsetNumber(page);
1016
0
    if (nline >= MaxHeapTuplesPerPage)
1017
0
    {
1018
0
      if (PageHasFreeLinePointers(page))
1019
0
      {
1020
        /*
1021
         * Since this is just a hint, we must confirm that there is
1022
         * indeed a free line pointer
1023
         */
1024
0
        for (offnum = FirstOffsetNumber; offnum <= nline; offnum = OffsetNumberNext(offnum))
1025
0
        {
1026
0
          ItemId    lp = PageGetItemId(unconstify(PageData *, page), offnum);
1027
1028
0
          if (!ItemIdIsUsed(lp))
1029
0
            break;
1030
0
        }
1031
1032
0
        if (offnum > nline)
1033
0
        {
1034
          /*
1035
           * The hint is wrong, but we can't clear it here since we
1036
           * don't have the ability to mark the page dirty.
1037
           */
1038
0
          space = 0;
1039
0
        }
1040
0
      }
1041
0
      else
1042
0
      {
1043
        /*
1044
         * Although the hint might be wrong, PageAddItem will believe
1045
         * it anyway, so we must believe it too.
1046
         */
1047
0
        space = 0;
1048
0
      }
1049
0
    }
1050
0
  }
1051
0
  return space;
1052
0
}
1053
1054
1055
/*
1056
 * PageIndexTupleDelete
1057
 *
1058
 * This routine does the work of removing a tuple from an index page.
1059
 *
1060
 * Unlike heap pages, we compact out the line pointer for the removed tuple.
1061
 */
1062
void
1063
PageIndexTupleDelete(Page page, OffsetNumber offnum)
1064
0
{
1065
0
  PageHeader  phdr = (PageHeader) page;
1066
0
  char     *addr;
1067
0
  ItemId    tup;
1068
0
  Size    size;
1069
0
  unsigned  offset;
1070
0
  int     nbytes;
1071
0
  int     offidx;
1072
0
  int     nline;
1073
1074
  /*
1075
   * As with PageRepairFragmentation, paranoia seems justified.
1076
   */
1077
0
  if (phdr->pd_lower < SizeOfPageHeaderData ||
1078
0
    phdr->pd_lower > phdr->pd_upper ||
1079
0
    phdr->pd_upper > phdr->pd_special ||
1080
0
    phdr->pd_special > BLCKSZ ||
1081
0
    phdr->pd_special != MAXALIGN(phdr->pd_special))
1082
0
    ereport(ERROR,
1083
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1084
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
1085
0
            phdr->pd_lower, phdr->pd_upper, phdr->pd_special)));
1086
1087
0
  nline = PageGetMaxOffsetNumber(page);
1088
0
  if ((int) offnum <= 0 || (int) offnum > nline)
1089
0
    elog(ERROR, "invalid index offnum: %u", offnum);
1090
1091
  /* change offset number to offset index */
1092
0
  offidx = offnum - 1;
1093
1094
0
  tup = PageGetItemId(page, offnum);
1095
0
  Assert(ItemIdHasStorage(tup));
1096
0
  size = ItemIdGetLength(tup);
1097
0
  offset = ItemIdGetOffset(tup);
1098
1099
0
  if (offset < phdr->pd_upper || (offset + size) > phdr->pd_special ||
1100
0
    offset != MAXALIGN(offset))
1101
0
    ereport(ERROR,
1102
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1103
0
         errmsg("corrupted line pointer: offset = %u, size = %zu",
1104
0
            offset, size)));
1105
1106
  /* Amount of space to actually be deleted */
1107
0
  size = MAXALIGN(size);
1108
1109
  /*
1110
   * First, we want to get rid of the pd_linp entry for the index tuple. We
1111
   * copy all subsequent linp's back one slot in the array. We don't use
1112
   * PageGetItemId, because we are manipulating the _array_, not individual
1113
   * linp's.
1114
   */
1115
0
  nbytes = phdr->pd_lower -
1116
0
    ((char *) &phdr->pd_linp[offidx + 1] - (char *) phdr);
1117
1118
0
  if (nbytes > 0)
1119
0
    memmove(&(phdr->pd_linp[offidx]),
1120
0
        &(phdr->pd_linp[offidx + 1]),
1121
0
        nbytes);
1122
1123
  /*
1124
   * Now move everything between the old upper bound (beginning of tuple
1125
   * space) and the beginning of the deleted tuple forward, so that space in
1126
   * the middle of the page is left free.  If we've just deleted the tuple
1127
   * at the beginning of tuple space, then there's no need to do the copy.
1128
   */
1129
1130
  /* beginning of tuple space */
1131
0
  addr = (char *) page + phdr->pd_upper;
1132
1133
0
  if (offset > phdr->pd_upper)
1134
0
    memmove(addr + size, addr, offset - phdr->pd_upper);
1135
1136
  /* adjust free space boundary pointers */
1137
0
  phdr->pd_upper += size;
1138
0
  phdr->pd_lower -= sizeof(ItemIdData);
1139
1140
  /*
1141
   * Finally, we need to adjust the linp entries that remain.
1142
   *
1143
   * Anything that used to be before the deleted tuple's data was moved
1144
   * forward by the size of the deleted tuple.
1145
   */
1146
0
  if (!PageIsEmpty(page))
1147
0
  {
1148
0
    int     i;
1149
1150
0
    nline--;        /* there's one less than when we started */
1151
0
    for (i = 1; i <= nline; i++)
1152
0
    {
1153
0
      ItemId    ii = PageGetItemId(page, i);
1154
1155
0
      Assert(ItemIdHasStorage(ii));
1156
0
      if (ItemIdGetOffset(ii) <= offset)
1157
0
        ii->lp_off += size;
1158
0
    }
1159
0
  }
1160
0
}
1161
1162
1163
/*
1164
 * PageIndexMultiDelete
1165
 *
1166
 * This routine handles the case of deleting multiple tuples from an
1167
 * index page at once.  It is considerably faster than a loop around
1168
 * PageIndexTupleDelete ... however, the caller *must* supply the array
1169
 * of item numbers to be deleted in item number order!
1170
 */
1171
void
1172
PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
1173
0
{
1174
0
  PageHeader  phdr = (PageHeader) page;
1175
0
  Offset    pd_lower = phdr->pd_lower;
1176
0
  Offset    pd_upper = phdr->pd_upper;
1177
0
  Offset    pd_special = phdr->pd_special;
1178
0
  Offset    last_offset;
1179
0
  itemIdCompactData itemidbase[MaxIndexTuplesPerPage];
1180
0
  ItemIdData  newitemids[MaxIndexTuplesPerPage];
1181
0
  itemIdCompact itemidptr;
1182
0
  ItemId    lp;
1183
0
  int     nline,
1184
0
        nused;
1185
0
  Size    totallen;
1186
0
  Size    size;
1187
0
  unsigned  offset;
1188
0
  int     nextitm;
1189
0
  OffsetNumber offnum;
1190
0
  bool    presorted = true; /* For now */
1191
1192
0
  Assert(nitems <= MaxIndexTuplesPerPage);
1193
1194
  /*
1195
   * If there aren't very many items to delete, then retail
1196
   * PageIndexTupleDelete is the best way.  Delete the items in reverse
1197
   * order so we don't have to think about adjusting item numbers for
1198
   * previous deletions.
1199
   *
1200
   * TODO: tune the magic number here
1201
   */
1202
0
  if (nitems <= 2)
1203
0
  {
1204
0
    while (--nitems >= 0)
1205
0
      PageIndexTupleDelete(page, itemnos[nitems]);
1206
0
    return;
1207
0
  }
1208
1209
  /*
1210
   * As with PageRepairFragmentation, paranoia seems justified.
1211
   */
1212
0
  if (pd_lower < SizeOfPageHeaderData ||
1213
0
    pd_lower > pd_upper ||
1214
0
    pd_upper > pd_special ||
1215
0
    pd_special > BLCKSZ ||
1216
0
    pd_special != MAXALIGN(pd_special))
1217
0
    ereport(ERROR,
1218
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1219
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
1220
0
            pd_lower, pd_upper, pd_special)));
1221
1222
  /*
1223
   * Scan the line pointer array and build a list of just the ones we are
1224
   * going to keep.  Notice we do not modify the page yet, since we are
1225
   * still validity-checking.
1226
   */
1227
0
  nline = PageGetMaxOffsetNumber(page);
1228
0
  itemidptr = itemidbase;
1229
0
  totallen = 0;
1230
0
  nused = 0;
1231
0
  nextitm = 0;
1232
0
  last_offset = pd_special;
1233
0
  for (offnum = FirstOffsetNumber; offnum <= nline; offnum = OffsetNumberNext(offnum))
1234
0
  {
1235
0
    lp = PageGetItemId(page, offnum);
1236
0
    Assert(ItemIdHasStorage(lp));
1237
0
    size = ItemIdGetLength(lp);
1238
0
    offset = ItemIdGetOffset(lp);
1239
0
    if (offset < pd_upper ||
1240
0
      (offset + size) > pd_special ||
1241
0
      offset != MAXALIGN(offset))
1242
0
      ereport(ERROR,
1243
0
          (errcode(ERRCODE_DATA_CORRUPTED),
1244
0
           errmsg("corrupted line pointer: offset = %u, size = %zu",
1245
0
              offset, size)));
1246
1247
0
    if (nextitm < nitems && offnum == itemnos[nextitm])
1248
0
    {
1249
      /* skip item to be deleted */
1250
0
      nextitm++;
1251
0
    }
1252
0
    else
1253
0
    {
1254
0
      itemidptr->offsetindex = nused; /* where it will go */
1255
0
      itemidptr->itemoff = offset;
1256
1257
0
      if (last_offset > itemidptr->itemoff)
1258
0
        last_offset = itemidptr->itemoff;
1259
0
      else
1260
0
        presorted = false;
1261
1262
0
      itemidptr->alignedlen = MAXALIGN(size);
1263
0
      totallen += itemidptr->alignedlen;
1264
0
      newitemids[nused] = *lp;
1265
0
      itemidptr++;
1266
0
      nused++;
1267
0
    }
1268
0
  }
1269
1270
  /* this will catch invalid or out-of-order itemnos[] */
1271
0
  if (nextitm != nitems)
1272
0
    elog(ERROR, "incorrect index offsets supplied");
1273
1274
0
  if (totallen > (Size) (pd_special - pd_lower))
1275
0
    ereport(ERROR,
1276
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1277
0
         errmsg("corrupted item lengths: total %zu, available space %u",
1278
0
            totallen, pd_special - pd_lower)));
1279
1280
  /*
1281
   * Looks good. Overwrite the line pointers with the copy, from which we've
1282
   * removed all the unused items.
1283
   */
1284
0
  memcpy(phdr->pd_linp, newitemids, nused * sizeof(ItemIdData));
1285
0
  phdr->pd_lower = SizeOfPageHeaderData + nused * sizeof(ItemIdData);
1286
1287
  /* and compactify the tuple data */
1288
0
  if (nused > 0)
1289
0
    compactify_tuples(itemidbase, nused, page, presorted);
1290
0
  else
1291
0
    phdr->pd_upper = pd_special;
1292
0
}
1293
1294
1295
/*
1296
 * PageIndexTupleDeleteNoCompact
1297
 *
1298
 * Remove the specified tuple from an index page, but set its line pointer
1299
 * to "unused" instead of compacting it out, except that it can be removed
1300
 * if it's the last line pointer on the page.
1301
 *
1302
 * This is used for index AMs that require that existing TIDs of live tuples
1303
 * remain unchanged, and are willing to allow unused line pointers instead.
1304
 */
1305
void
1306
PageIndexTupleDeleteNoCompact(Page page, OffsetNumber offnum)
1307
0
{
1308
0
  PageHeader  phdr = (PageHeader) page;
1309
0
  char     *addr;
1310
0
  ItemId    tup;
1311
0
  Size    size;
1312
0
  unsigned  offset;
1313
0
  int     nline;
1314
1315
  /*
1316
   * As with PageRepairFragmentation, paranoia seems justified.
1317
   */
1318
0
  if (phdr->pd_lower < SizeOfPageHeaderData ||
1319
0
    phdr->pd_lower > phdr->pd_upper ||
1320
0
    phdr->pd_upper > phdr->pd_special ||
1321
0
    phdr->pd_special > BLCKSZ ||
1322
0
    phdr->pd_special != MAXALIGN(phdr->pd_special))
1323
0
    ereport(ERROR,
1324
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1325
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
1326
0
            phdr->pd_lower, phdr->pd_upper, phdr->pd_special)));
1327
1328
0
  nline = PageGetMaxOffsetNumber(page);
1329
0
  if ((int) offnum <= 0 || (int) offnum > nline)
1330
0
    elog(ERROR, "invalid index offnum: %u", offnum);
1331
1332
0
  tup = PageGetItemId(page, offnum);
1333
0
  Assert(ItemIdHasStorage(tup));
1334
0
  size = ItemIdGetLength(tup);
1335
0
  offset = ItemIdGetOffset(tup);
1336
1337
0
  if (offset < phdr->pd_upper || (offset + size) > phdr->pd_special ||
1338
0
    offset != MAXALIGN(offset))
1339
0
    ereport(ERROR,
1340
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1341
0
         errmsg("corrupted line pointer: offset = %u, size = %zu",
1342
0
            offset, size)));
1343
1344
  /* Amount of space to actually be deleted */
1345
0
  size = MAXALIGN(size);
1346
1347
  /*
1348
   * Either set the line pointer to "unused", or zap it if it's the last
1349
   * one.  (Note: it's possible that the next-to-last one(s) are already
1350
   * unused, but we do not trouble to try to compact them out if so.)
1351
   */
1352
0
  if ((int) offnum < nline)
1353
0
    ItemIdSetUnused(tup);
1354
0
  else
1355
0
  {
1356
0
    phdr->pd_lower -= sizeof(ItemIdData);
1357
0
    nline--;        /* there's one less than when we started */
1358
0
  }
1359
1360
  /*
1361
   * Now move everything between the old upper bound (beginning of tuple
1362
   * space) and the beginning of the deleted tuple forward, so that space in
1363
   * the middle of the page is left free.  If we've just deleted the tuple
1364
   * at the beginning of tuple space, then there's no need to do the copy.
1365
   */
1366
1367
  /* beginning of tuple space */
1368
0
  addr = (char *) page + phdr->pd_upper;
1369
1370
0
  if (offset > phdr->pd_upper)
1371
0
    memmove(addr + size, addr, offset - phdr->pd_upper);
1372
1373
  /* adjust free space boundary pointer */
1374
0
  phdr->pd_upper += size;
1375
1376
  /*
1377
   * Finally, we need to adjust the linp entries that remain.
1378
   *
1379
   * Anything that used to be before the deleted tuple's data was moved
1380
   * forward by the size of the deleted tuple.
1381
   */
1382
0
  if (!PageIsEmpty(page))
1383
0
  {
1384
0
    int     i;
1385
1386
0
    for (i = 1; i <= nline; i++)
1387
0
    {
1388
0
      ItemId    ii = PageGetItemId(page, i);
1389
1390
0
      if (ItemIdHasStorage(ii) && ItemIdGetOffset(ii) <= offset)
1391
0
        ii->lp_off += size;
1392
0
    }
1393
0
  }
1394
0
}
1395
1396
1397
/*
1398
 * PageIndexTupleOverwrite
1399
 *
1400
 * Replace a specified tuple on an index page.
1401
 *
1402
 * The new tuple is placed exactly where the old one had been, shifting
1403
 * other tuples' data up or down as needed to keep the page compacted.
1404
 * This is better than deleting and reinserting the tuple, because it
1405
 * avoids any data shifting when the tuple size doesn't change; and
1406
 * even when it does, we avoid moving the line pointers around.
1407
 * This could be used by an index AM that doesn't want to unset the
1408
 * LP_DEAD bit when it happens to be set.  It could conceivably also be
1409
 * used by an index AM that cares about the physical order of tuples as
1410
 * well as their logical/ItemId order.
1411
 *
1412
 * If there's insufficient space for the new tuple, return false.  Other
1413
 * errors represent data-corruption problems, so we just elog.
1414
 */
1415
bool
1416
PageIndexTupleOverwrite(Page page, OffsetNumber offnum,
1417
            const void *newtup, Size newsize)
1418
0
{
1419
0
  PageHeader  phdr = (PageHeader) page;
1420
0
  ItemId    tupid;
1421
0
  int     oldsize;
1422
0
  unsigned  offset;
1423
0
  Size    alignednewsize;
1424
0
  int     size_diff;
1425
0
  int     itemcount;
1426
1427
  /*
1428
   * As with PageRepairFragmentation, paranoia seems justified.
1429
   */
1430
0
  if (phdr->pd_lower < SizeOfPageHeaderData ||
1431
0
    phdr->pd_lower > phdr->pd_upper ||
1432
0
    phdr->pd_upper > phdr->pd_special ||
1433
0
    phdr->pd_special > BLCKSZ ||
1434
0
    phdr->pd_special != MAXALIGN(phdr->pd_special))
1435
0
    ereport(ERROR,
1436
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1437
0
         errmsg("corrupted page pointers: lower = %u, upper = %u, special = %u",
1438
0
            phdr->pd_lower, phdr->pd_upper, phdr->pd_special)));
1439
1440
0
  itemcount = PageGetMaxOffsetNumber(page);
1441
0
  if ((int) offnum <= 0 || (int) offnum > itemcount)
1442
0
    elog(ERROR, "invalid index offnum: %u", offnum);
1443
1444
0
  tupid = PageGetItemId(page, offnum);
1445
0
  Assert(ItemIdHasStorage(tupid));
1446
0
  oldsize = ItemIdGetLength(tupid);
1447
0
  offset = ItemIdGetOffset(tupid);
1448
1449
0
  if (offset < phdr->pd_upper || (offset + oldsize) > phdr->pd_special ||
1450
0
    offset != MAXALIGN(offset))
1451
0
    ereport(ERROR,
1452
0
        (errcode(ERRCODE_DATA_CORRUPTED),
1453
0
         errmsg("corrupted line pointer: offset = %u, size = %d",
1454
0
            offset, oldsize)));
1455
1456
  /*
1457
   * Determine actual change in space requirement, check for page overflow.
1458
   */
1459
0
  oldsize = MAXALIGN(oldsize);
1460
0
  alignednewsize = MAXALIGN(newsize);
1461
0
  if (alignednewsize > oldsize + (phdr->pd_upper - phdr->pd_lower))
1462
0
    return false;
1463
1464
  /*
1465
   * Relocate existing data and update line pointers, unless the new tuple
1466
   * is the same size as the old (after alignment), in which case there's
1467
   * nothing to do.  Notice that what we have to relocate is data before the
1468
   * target tuple, not data after, so it's convenient to express size_diff
1469
   * as the amount by which the tuple's size is decreasing, making it the
1470
   * delta to add to pd_upper and affected line pointers.
1471
   */
1472
0
  size_diff = oldsize - (int) alignednewsize;
1473
0
  if (size_diff != 0)
1474
0
  {
1475
0
    char     *addr = (char *) page + phdr->pd_upper;
1476
0
    int     i;
1477
1478
    /* relocate all tuple data before the target tuple */
1479
0
    memmove(addr + size_diff, addr, offset - phdr->pd_upper);
1480
1481
    /* adjust free space boundary pointer */
1482
0
    phdr->pd_upper += size_diff;
1483
1484
    /* adjust affected line pointers too */
1485
0
    for (i = FirstOffsetNumber; i <= itemcount; i++)
1486
0
    {
1487
0
      ItemId    ii = PageGetItemId(page, i);
1488
1489
      /* Allow items without storage; currently only BRIN needs that */
1490
0
      if (ItemIdHasStorage(ii) && ItemIdGetOffset(ii) <= offset)
1491
0
        ii->lp_off += size_diff;
1492
0
    }
1493
0
  }
1494
1495
  /* Update the item's tuple length without changing its lp_flags field */
1496
0
  tupid->lp_off = offset + size_diff;
1497
0
  tupid->lp_len = newsize;
1498
1499
  /* Copy new tuple data onto page */
1500
0
  memcpy(PageGetItem(page, tupid), newtup, newsize);
1501
1502
0
  return true;
1503
0
}
1504
1505
1506
/*
1507
 * Set checksum on a page.
1508
 *
1509
 * If the page is in shared buffers, it needs to be locked in at least
1510
 * share-exclusive mode.
1511
 *
1512
 * If checksums are disabled, or if the page is not initialized, just
1513
 * return. Otherwise compute and set the checksum.
1514
 *
1515
 * In the past this needed to be done on a copy of the page, due to the
1516
 * possibility of e.g., hint bits being set concurrently. However, this is not
1517
 * necessary anymore as hint bits won't be set while IO is going on.
1518
 */
1519
void
1520
PageSetChecksum(Page page, BlockNumber blkno)
1521
0
{
1522
0
  HOLD_INTERRUPTS();
1523
  /* If we don't need a checksum, just return */
1524
0
  if (PageIsNew(page) || !DataChecksumsNeedWrite())
1525
0
  {
1526
0
    RESUME_INTERRUPTS();
1527
0
    return;
1528
0
  }
1529
1530
0
  ((PageHeader) page)->pd_checksum = pg_checksum_page(page, blkno);
1531
  RESUME_INTERRUPTS();
1532
0
}