Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/nbtree/nbtxlog.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * nbtxlog.c
4
 *    WAL replay logic for btrees.
5
 *
6
 *
7
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994, Regents of the University of California
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/nbtree/nbtxlog.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/bufmask.h"
18
#include "access/nbtree.h"
19
#include "access/nbtxlog.h"
20
#include "access/transam.h"
21
#include "access/xlogutils.h"
22
#include "storage/standby.h"
23
#include "utils/memutils.h"
24
25
static MemoryContext opCtx;   /* working memory for operations */
26
27
/*
28
 * _bt_restore_page -- re-enter all the index tuples on a page
29
 *
30
 * The page is freshly init'd, and *from (length len) is a copy of what
31
 * had been its upper part (pd_upper to pd_special).  We assume that the
32
 * tuples had been added to the page in item-number order, and therefore
33
 * the one with highest item number appears first (lowest on the page).
34
 */
35
static void
36
_bt_restore_page(Page page, char *from, int len)
37
0
{
38
0
  IndexTupleData itupdata;
39
0
  Size    itemsz;
40
0
  char     *end = from + len;
41
0
  void     *items[MaxIndexTuplesPerPage];
42
0
  uint16    itemsizes[MaxIndexTuplesPerPage];
43
0
  int     i;
44
0
  int     nitems;
45
46
  /*
47
   * To get the items back in the original order, we add them to the page in
48
   * reverse.  To figure out where one tuple ends and another begins, we
49
   * have to scan them in forward order first.
50
   */
51
0
  i = 0;
52
0
  while (from < end)
53
0
  {
54
    /*
55
     * As we step through the items, 'from' won't always be properly
56
     * aligned, so we need to use memcpy().  Further, we use void * here
57
     * for our items array for the same reason; wouldn't want the compiler
58
     * or anyone thinking that an item is aligned when it isn't.
59
     */
60
0
    memcpy(&itupdata, from, sizeof(IndexTupleData));
61
0
    itemsz = IndexTupleSize(&itupdata);
62
0
    itemsz = MAXALIGN(itemsz);
63
64
0
    items[i] = from;
65
0
    itemsizes[i] = itemsz;
66
0
    i++;
67
68
0
    from += itemsz;
69
0
  }
70
0
  nitems = i;
71
72
0
  for (i = nitems - 1; i >= 0; i--)
73
0
  {
74
0
    if (PageAddItem(page, items[i], itemsizes[i], nitems - i, false, false) == InvalidOffsetNumber)
75
0
      elog(PANIC, "_bt_restore_page: cannot add item to page");
76
0
  }
77
0
}
78
79
static void
80
_bt_restore_meta(XLogReaderState *record, uint8 block_id)
81
0
{
82
0
  XLogRecPtr  lsn = record->EndRecPtr;
83
0
  Buffer    metabuf;
84
0
  Page    metapg;
85
0
  BTMetaPageData *md;
86
0
  BTPageOpaque pageop;
87
0
  xl_btree_metadata *xlrec;
88
0
  char     *ptr;
89
0
  Size    len;
90
91
0
  metabuf = XLogInitBufferForRedo(record, block_id);
92
0
  ptr = XLogRecGetBlockData(record, block_id, &len);
93
94
0
  Assert(len == sizeof(xl_btree_metadata));
95
0
  Assert(BufferGetBlockNumber(metabuf) == BTREE_METAPAGE);
96
0
  xlrec = (xl_btree_metadata *) ptr;
97
0
  metapg = BufferGetPage(metabuf);
98
99
0
  _bt_pageinit(metapg, BufferGetPageSize(metabuf));
100
101
0
  md = BTPageGetMeta(metapg);
102
0
  md->btm_magic = BTREE_MAGIC;
103
0
  md->btm_version = xlrec->version;
104
0
  md->btm_root = xlrec->root;
105
0
  md->btm_level = xlrec->level;
106
0
  md->btm_fastroot = xlrec->fastroot;
107
0
  md->btm_fastlevel = xlrec->fastlevel;
108
  /* Cannot log BTREE_MIN_VERSION index metapage without upgrade */
109
0
  Assert(md->btm_version >= BTREE_NOVAC_VERSION);
110
0
  md->btm_last_cleanup_num_delpages = xlrec->last_cleanup_num_delpages;
111
0
  md->btm_last_cleanup_num_heap_tuples = -1.0;
112
0
  md->btm_allequalimage = xlrec->allequalimage;
113
114
0
  pageop = BTPageGetOpaque(metapg);
115
0
  pageop->btpo_flags = BTP_META;
116
117
  /*
118
   * Set pd_lower just past the end of the metadata.  This is essential,
119
   * because without doing so, metadata will be lost if xlog.c compresses
120
   * the page.
121
   */
122
0
  ((PageHeader) metapg)->pd_lower =
123
0
    ((char *) md + sizeof(BTMetaPageData)) - (char *) metapg;
124
125
0
  PageSetLSN(metapg, lsn);
126
0
  MarkBufferDirty(metabuf);
127
0
  UnlockReleaseBuffer(metabuf);
128
0
}
129
130
/*
131
 * _bt_clear_incomplete_split -- clear INCOMPLETE_SPLIT flag on a page
132
 *
133
 * This is a common subroutine of the redo functions of all the WAL record
134
 * types that can insert a downlink: insert, split, and newroot.
135
 */
136
static void
137
_bt_clear_incomplete_split(XLogReaderState *record, uint8 block_id)
138
0
{
139
0
  XLogRecPtr  lsn = record->EndRecPtr;
140
0
  Buffer    buf;
141
142
0
  if (XLogReadBufferForRedo(record, block_id, &buf) == BLK_NEEDS_REDO)
143
0
  {
144
0
    Page    page = BufferGetPage(buf);
145
0
    BTPageOpaque pageop = BTPageGetOpaque(page);
146
147
0
    Assert(P_INCOMPLETE_SPLIT(pageop));
148
0
    pageop->btpo_flags &= ~BTP_INCOMPLETE_SPLIT;
149
150
0
    PageSetLSN(page, lsn);
151
0
    MarkBufferDirty(buf);
152
0
  }
153
0
  if (BufferIsValid(buf))
154
0
    UnlockReleaseBuffer(buf);
155
0
}
156
157
static void
158
btree_xlog_insert(bool isleaf, bool ismeta, bool posting,
159
          XLogReaderState *record)
160
0
{
161
0
  XLogRecPtr  lsn = record->EndRecPtr;
162
0
  xl_btree_insert *xlrec = (xl_btree_insert *) XLogRecGetData(record);
163
0
  Buffer    buffer;
164
0
  Page    page;
165
166
  /*
167
   * Insertion to an internal page finishes an incomplete split at the child
168
   * level.  Clear the incomplete-split flag in the child.  Note: during
169
   * normal operation, the child and parent pages are locked at the same
170
   * time (the locks are coupled), so that clearing the flag and inserting
171
   * the downlink appear atomic to other backends.  We don't bother with
172
   * that during replay, because readers don't care about the
173
   * incomplete-split flag and there cannot be updates happening.
174
   */
175
0
  if (!isleaf)
176
0
    _bt_clear_incomplete_split(record, 1);
177
0
  if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
178
0
  {
179
0
    Size    datalen;
180
0
    char     *datapos = XLogRecGetBlockData(record, 0, &datalen);
181
182
0
    page = BufferGetPage(buffer);
183
184
0
    if (!posting)
185
0
    {
186
      /* Simple retail insertion */
187
0
      if (PageAddItem(page, datapos, datalen, xlrec->offnum, false, false) == InvalidOffsetNumber)
188
0
        elog(PANIC, "failed to add new item");
189
0
    }
190
0
    else
191
0
    {
192
0
      ItemId    itemid;
193
0
      IndexTuple  oposting,
194
0
            newitem,
195
0
            nposting;
196
0
      uint16    postingoff;
197
198
      /*
199
       * A posting list split occurred during leaf page insertion.  WAL
200
       * record data will start with an offset number representing the
201
       * point in an existing posting list that a split occurs at.
202
       *
203
       * Use _bt_swap_posting() to repeat posting list split steps from
204
       * primary.  Note that newitem from WAL record is 'orignewitem',
205
       * not the final version of newitem that is actually inserted on
206
       * page.
207
       */
208
0
      postingoff = *((uint16 *) datapos);
209
0
      datapos += sizeof(uint16);
210
0
      datalen -= sizeof(uint16);
211
212
0
      itemid = PageGetItemId(page, OffsetNumberPrev(xlrec->offnum));
213
0
      oposting = (IndexTuple) PageGetItem(page, itemid);
214
215
      /* Use mutable, aligned newitem copy in _bt_swap_posting() */
216
0
      Assert(isleaf && postingoff > 0);
217
0
      newitem = CopyIndexTuple((IndexTuple) datapos);
218
0
      nposting = _bt_swap_posting(newitem, oposting, postingoff);
219
220
      /* Replace existing posting list with post-split version */
221
0
      memcpy(oposting, nposting, MAXALIGN(IndexTupleSize(nposting)));
222
223
      /* Insert "final" new item (not orignewitem from WAL stream) */
224
0
      Assert(IndexTupleSize(newitem) == datalen);
225
0
      if (PageAddItem(page, newitem, datalen, xlrec->offnum, false, false) == InvalidOffsetNumber)
226
0
        elog(PANIC, "failed to add posting split new item");
227
0
    }
228
229
0
    PageSetLSN(page, lsn);
230
0
    MarkBufferDirty(buffer);
231
0
  }
232
0
  if (BufferIsValid(buffer))
233
0
    UnlockReleaseBuffer(buffer);
234
235
  /*
236
   * Note: in normal operation, we'd update the metapage while still holding
237
   * lock on the page we inserted into.  But during replay it's not
238
   * necessary to hold that lock, since no other index updates can be
239
   * happening concurrently, and readers will cope fine with following an
240
   * obsolete link from the metapage.
241
   */
242
0
  if (ismeta)
243
0
    _bt_restore_meta(record, 2);
244
0
}
245
246
static void
247
btree_xlog_split(bool newitemonleft, XLogReaderState *record)
248
0
{
249
0
  XLogRecPtr  lsn = record->EndRecPtr;
250
0
  xl_btree_split *xlrec = (xl_btree_split *) XLogRecGetData(record);
251
0
  bool    isleaf = (xlrec->level == 0);
252
0
  Buffer    buf;
253
0
  Buffer    rbuf;
254
0
  Page    rpage;
255
0
  BTPageOpaque ropaque;
256
0
  char     *datapos;
257
0
  Size    datalen;
258
0
  BlockNumber origpagenumber;
259
0
  BlockNumber rightpagenumber;
260
0
  BlockNumber spagenumber;
261
262
0
  XLogRecGetBlockTag(record, 0, NULL, NULL, &origpagenumber);
263
0
  XLogRecGetBlockTag(record, 1, NULL, NULL, &rightpagenumber);
264
0
  if (!XLogRecGetBlockTagExtended(record, 2, NULL, NULL, &spagenumber, NULL))
265
0
    spagenumber = P_NONE;
266
267
  /*
268
   * Clear the incomplete split flag on the appropriate child page one level
269
   * down when origpage/buf is an internal page (there must have been
270
   * cascading page splits during original execution in the event of an
271
   * internal page split).  This is like the corresponding btree_xlog_insert
272
   * call for internal pages.  We're not clearing the incomplete split flag
273
   * for the current page split here (you can think of this as part of the
274
   * insert of newitem that the page split action needs to perform in
275
   * passing).
276
   *
277
   * Like in btree_xlog_insert, this can be done before locking other pages.
278
   * We never need to couple cross-level locks in REDO routines.
279
   */
280
0
  if (!isleaf)
281
0
    _bt_clear_incomplete_split(record, 3);
282
283
  /* Reconstruct right (new) sibling page from scratch */
284
0
  rbuf = XLogInitBufferForRedo(record, 1);
285
0
  datapos = XLogRecGetBlockData(record, 1, &datalen);
286
0
  rpage = BufferGetPage(rbuf);
287
288
0
  _bt_pageinit(rpage, BufferGetPageSize(rbuf));
289
0
  ropaque = BTPageGetOpaque(rpage);
290
291
0
  ropaque->btpo_prev = origpagenumber;
292
0
  ropaque->btpo_next = spagenumber;
293
0
  ropaque->btpo_level = xlrec->level;
294
0
  ropaque->btpo_flags = isleaf ? BTP_LEAF : 0;
295
0
  ropaque->btpo_cycleid = 0;
296
297
0
  _bt_restore_page(rpage, datapos, datalen);
298
299
0
  PageSetLSN(rpage, lsn);
300
0
  MarkBufferDirty(rbuf);
301
302
  /* Now reconstruct original page (left half of split) */
303
0
  if (XLogReadBufferForRedo(record, 0, &buf) == BLK_NEEDS_REDO)
304
0
  {
305
    /*
306
     * To retain the same physical order of the tuples that they had, we
307
     * initialize a temporary empty page for the left page and add all the
308
     * items to that in item number order.  This mirrors how _bt_split()
309
     * works.  Retaining the same physical order makes WAL consistency
310
     * checking possible.  See also _bt_restore_page(), which does the
311
     * same for the right page.
312
     */
313
0
    Page    origpage = BufferGetPage(buf);
314
0
    BTPageOpaque oopaque = BTPageGetOpaque(origpage);
315
0
    OffsetNumber off;
316
0
    IndexTuple  newitem = NULL,
317
0
          left_hikey = NULL,
318
0
          nposting = NULL;
319
0
    Size    newitemsz = 0,
320
0
          left_hikeysz = 0;
321
0
    Page    leftpage;
322
0
    OffsetNumber leftoff,
323
0
          replacepostingoff = InvalidOffsetNumber;
324
325
0
    datapos = XLogRecGetBlockData(record, 0, &datalen);
326
327
0
    if (newitemonleft || xlrec->postingoff != 0)
328
0
    {
329
0
      newitem = (IndexTuple) datapos;
330
0
      newitemsz = MAXALIGN(IndexTupleSize(newitem));
331
0
      datapos += newitemsz;
332
0
      datalen -= newitemsz;
333
334
0
      if (xlrec->postingoff != 0)
335
0
      {
336
0
        ItemId    itemid;
337
0
        IndexTuple  oposting;
338
339
        /* Posting list must be at offset number before new item's */
340
0
        replacepostingoff = OffsetNumberPrev(xlrec->newitemoff);
341
342
        /* Use mutable, aligned newitem copy in _bt_swap_posting() */
343
0
        newitem = CopyIndexTuple(newitem);
344
0
        itemid = PageGetItemId(origpage, replacepostingoff);
345
0
        oposting = (IndexTuple) PageGetItem(origpage, itemid);
346
0
        nposting = _bt_swap_posting(newitem, oposting,
347
0
                      xlrec->postingoff);
348
0
      }
349
0
    }
350
351
    /*
352
     * Extract left hikey and its size.  We assume that 16-bit alignment
353
     * is enough to apply IndexTupleSize (since it's fetching from a
354
     * uint16 field).
355
     */
356
0
    left_hikey = (IndexTuple) datapos;
357
0
    left_hikeysz = MAXALIGN(IndexTupleSize(left_hikey));
358
0
    datapos += left_hikeysz;
359
0
    datalen -= left_hikeysz;
360
361
0
    Assert(datalen == 0);
362
363
0
    leftpage = PageGetTempPageCopySpecial(origpage);
364
365
    /* Add high key tuple from WAL record to temp page */
366
0
    leftoff = P_HIKEY;
367
0
    if (PageAddItem(leftpage, left_hikey, left_hikeysz, P_HIKEY, false, false) == InvalidOffsetNumber)
368
0
      elog(ERROR, "failed to add high key to left page after split");
369
0
    leftoff = OffsetNumberNext(leftoff);
370
371
0
    for (off = P_FIRSTDATAKEY(oopaque); off < xlrec->firstrightoff; off++)
372
0
    {
373
0
      ItemId    itemid;
374
0
      Size    itemsz;
375
0
      IndexTuple  item;
376
377
      /* Add replacement posting list when required */
378
0
      if (off == replacepostingoff)
379
0
      {
380
0
        Assert(newitemonleft ||
381
0
             xlrec->firstrightoff == xlrec->newitemoff);
382
0
        if (PageAddItem(leftpage, nposting, MAXALIGN(IndexTupleSize(nposting)), leftoff, false, false) == InvalidOffsetNumber)
383
0
          elog(ERROR, "failed to add new posting list item to left page after split");
384
0
        leftoff = OffsetNumberNext(leftoff);
385
0
        continue;   /* don't insert oposting */
386
0
      }
387
388
      /* add the new item if it was inserted on left page */
389
0
      else if (newitemonleft && off == xlrec->newitemoff)
390
0
      {
391
0
        if (PageAddItem(leftpage, newitem, newitemsz, leftoff, false, false) == InvalidOffsetNumber)
392
0
          elog(ERROR, "failed to add new item to left page after split");
393
0
        leftoff = OffsetNumberNext(leftoff);
394
0
      }
395
396
0
      itemid = PageGetItemId(origpage, off);
397
0
      itemsz = ItemIdGetLength(itemid);
398
0
      item = (IndexTuple) PageGetItem(origpage, itemid);
399
0
      if (PageAddItem(leftpage, item, itemsz, leftoff, false, false) == InvalidOffsetNumber)
400
0
        elog(ERROR, "failed to add old item to left page after split");
401
0
      leftoff = OffsetNumberNext(leftoff);
402
0
    }
403
404
    /* cope with possibility that newitem goes at the end */
405
0
    if (newitemonleft && off == xlrec->newitemoff)
406
0
    {
407
0
      if (PageAddItem(leftpage, newitem, newitemsz, leftoff, false, false) == InvalidOffsetNumber)
408
0
        elog(ERROR, "failed to add new item to left page after split");
409
0
      leftoff = OffsetNumberNext(leftoff);
410
0
    }
411
412
0
    PageRestoreTempPage(leftpage, origpage);
413
414
    /* Fix opaque fields */
415
0
    oopaque->btpo_flags = BTP_INCOMPLETE_SPLIT;
416
0
    if (isleaf)
417
0
      oopaque->btpo_flags |= BTP_LEAF;
418
0
    oopaque->btpo_next = rightpagenumber;
419
0
    oopaque->btpo_cycleid = 0;
420
421
0
    PageSetLSN(origpage, lsn);
422
0
    MarkBufferDirty(buf);
423
0
  }
424
425
  /* Fix left-link of the page to the right of the new right sibling */
426
0
  if (spagenumber != P_NONE)
427
0
  {
428
0
    Buffer    sbuf;
429
430
0
    if (XLogReadBufferForRedo(record, 2, &sbuf) == BLK_NEEDS_REDO)
431
0
    {
432
0
      Page    spage = BufferGetPage(sbuf);
433
0
      BTPageOpaque spageop = BTPageGetOpaque(spage);
434
435
0
      spageop->btpo_prev = rightpagenumber;
436
437
0
      PageSetLSN(spage, lsn);
438
0
      MarkBufferDirty(sbuf);
439
0
    }
440
0
    if (BufferIsValid(sbuf))
441
0
      UnlockReleaseBuffer(sbuf);
442
0
  }
443
444
  /*
445
   * Finally, release the remaining buffers.  sbuf, rbuf, and buf must be
446
   * released together, so that readers cannot observe inconsistencies.
447
   */
448
0
  UnlockReleaseBuffer(rbuf);
449
0
  if (BufferIsValid(buf))
450
0
    UnlockReleaseBuffer(buf);
451
0
}
452
453
static void
454
btree_xlog_dedup(XLogReaderState *record)
455
0
{
456
0
  XLogRecPtr  lsn = record->EndRecPtr;
457
0
  xl_btree_dedup *xlrec = (xl_btree_dedup *) XLogRecGetData(record);
458
0
  Buffer    buf;
459
460
0
  if (XLogReadBufferForRedo(record, 0, &buf) == BLK_NEEDS_REDO)
461
0
  {
462
0
    char     *ptr = XLogRecGetBlockData(record, 0, NULL);
463
0
    Page    page = BufferGetPage(buf);
464
0
    BTPageOpaque opaque = BTPageGetOpaque(page);
465
0
    OffsetNumber offnum,
466
0
          minoff,
467
0
          maxoff;
468
0
    BTDedupState state;
469
0
    BTDedupInterval *intervals;
470
0
    Page    newpage;
471
472
0
    state = palloc_object(BTDedupStateData);
473
0
    state->deduplicate = true; /* unused */
474
0
    state->nmaxitems = 0; /* unused */
475
    /* Conservatively use larger maxpostingsize than primary */
476
0
    state->maxpostingsize = BTMaxItemSize;
477
0
    state->base = NULL;
478
0
    state->baseoff = InvalidOffsetNumber;
479
0
    state->basetupsize = 0;
480
0
    state->htids = palloc(state->maxpostingsize);
481
0
    state->nhtids = 0;
482
0
    state->nitems = 0;
483
0
    state->phystupsize = 0;
484
0
    state->nintervals = 0;
485
486
0
    minoff = P_FIRSTDATAKEY(opaque);
487
0
    maxoff = PageGetMaxOffsetNumber(page);
488
0
    newpage = PageGetTempPageCopySpecial(page);
489
490
0
    if (!P_RIGHTMOST(opaque))
491
0
    {
492
0
      ItemId    itemid = PageGetItemId(page, P_HIKEY);
493
0
      Size    itemsz = ItemIdGetLength(itemid);
494
0
      IndexTuple  item = (IndexTuple) PageGetItem(page, itemid);
495
496
0
      if (PageAddItem(newpage, item, itemsz, P_HIKEY, false, false) == InvalidOffsetNumber)
497
0
        elog(ERROR, "deduplication failed to add highkey");
498
0
    }
499
500
0
    intervals = (BTDedupInterval *) ptr;
501
0
    for (offnum = minoff;
502
0
       offnum <= maxoff;
503
0
       offnum = OffsetNumberNext(offnum))
504
0
    {
505
0
      ItemId    itemid = PageGetItemId(page, offnum);
506
0
      IndexTuple  itup = (IndexTuple) PageGetItem(page, itemid);
507
508
0
      if (offnum == minoff)
509
0
        _bt_dedup_start_pending(state, itup, offnum);
510
0
      else if (state->nintervals < xlrec->nintervals &&
511
0
           state->baseoff == intervals[state->nintervals].baseoff &&
512
0
           state->nitems < intervals[state->nintervals].nitems)
513
0
      {
514
0
        if (!_bt_dedup_save_htid(state, itup))
515
0
          elog(ERROR, "deduplication failed to add heap tid to pending posting list");
516
0
      }
517
0
      else
518
0
      {
519
0
        _bt_dedup_finish_pending(newpage, state);
520
0
        _bt_dedup_start_pending(state, itup, offnum);
521
0
      }
522
0
    }
523
524
0
    _bt_dedup_finish_pending(newpage, state);
525
0
    Assert(state->nintervals == xlrec->nintervals);
526
0
    Assert(memcmp(state->intervals, intervals,
527
0
            state->nintervals * sizeof(BTDedupInterval)) == 0);
528
529
0
    if (P_HAS_GARBAGE(opaque))
530
0
    {
531
0
      BTPageOpaque nopaque = BTPageGetOpaque(newpage);
532
533
0
      nopaque->btpo_flags &= ~BTP_HAS_GARBAGE;
534
0
    }
535
536
0
    PageRestoreTempPage(newpage, page);
537
0
    PageSetLSN(page, lsn);
538
0
    MarkBufferDirty(buf);
539
0
  }
540
541
0
  if (BufferIsValid(buf))
542
0
    UnlockReleaseBuffer(buf);
543
0
}
544
545
static void
546
btree_xlog_updates(Page page, OffsetNumber *updatedoffsets,
547
           xl_btree_update *updates, int nupdated)
548
0
{
549
0
  BTVacuumPosting vacposting;
550
0
  IndexTuple  origtuple;
551
0
  ItemId    itemid;
552
0
  Size    itemsz;
553
554
0
  for (int i = 0; i < nupdated; i++)
555
0
  {
556
0
    itemid = PageGetItemId(page, updatedoffsets[i]);
557
0
    origtuple = (IndexTuple) PageGetItem(page, itemid);
558
559
0
    vacposting = palloc(offsetof(BTVacuumPostingData, deletetids) +
560
0
              updates->ndeletedtids * sizeof(uint16));
561
0
    vacposting->updatedoffset = updatedoffsets[i];
562
0
    vacposting->itup = origtuple;
563
0
    vacposting->ndeletedtids = updates->ndeletedtids;
564
0
    memcpy(vacposting->deletetids,
565
0
         (char *) updates + SizeOfBtreeUpdate,
566
0
         updates->ndeletedtids * sizeof(uint16));
567
568
0
    _bt_update_posting(vacposting);
569
570
    /* Overwrite updated version of tuple */
571
0
    itemsz = MAXALIGN(IndexTupleSize(vacposting->itup));
572
0
    if (!PageIndexTupleOverwrite(page, updatedoffsets[i], vacposting->itup, itemsz))
573
0
      elog(PANIC, "failed to update partially dead item");
574
575
0
    pfree(vacposting->itup);
576
0
    pfree(vacposting);
577
578
    /* advance to next xl_btree_update from array */
579
0
    updates = (xl_btree_update *)
580
0
      ((char *) updates + SizeOfBtreeUpdate +
581
0
       updates->ndeletedtids * sizeof(uint16));
582
0
  }
583
0
}
584
585
static void
586
btree_xlog_vacuum(XLogReaderState *record)
587
0
{
588
0
  XLogRecPtr  lsn = record->EndRecPtr;
589
0
  xl_btree_vacuum *xlrec = (xl_btree_vacuum *) XLogRecGetData(record);
590
0
  Buffer    buffer;
591
0
  Page    page;
592
0
  BTPageOpaque opaque;
593
594
  /*
595
   * We need to take a cleanup lock here, just like btvacuumpage(). However,
596
   * it isn't necessary to exhaustively get a cleanup lock on every block in
597
   * the index during recovery (just getting a cleanup lock on pages with
598
   * items to kill suffices).  See nbtree/README for details.
599
   */
600
0
  if (XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &buffer)
601
0
    == BLK_NEEDS_REDO)
602
0
  {
603
0
    char     *ptr = XLogRecGetBlockData(record, 0, NULL);
604
605
0
    page = BufferGetPage(buffer);
606
607
0
    if (xlrec->nupdated > 0)
608
0
    {
609
0
      OffsetNumber *updatedoffsets;
610
0
      xl_btree_update *updates;
611
612
0
      updatedoffsets = (OffsetNumber *)
613
0
        (ptr + xlrec->ndeleted * sizeof(OffsetNumber));
614
0
      updates = (xl_btree_update *) ((char *) updatedoffsets +
615
0
                       xlrec->nupdated *
616
0
                       sizeof(OffsetNumber));
617
618
0
      btree_xlog_updates(page, updatedoffsets, updates, xlrec->nupdated);
619
0
    }
620
621
0
    if (xlrec->ndeleted > 0)
622
0
      PageIndexMultiDelete(page, (OffsetNumber *) ptr, xlrec->ndeleted);
623
624
    /*
625
     * Clear the vacuum cycle ID, and mark the page as not containing any
626
     * LP_DEAD items
627
     */
628
0
    opaque = BTPageGetOpaque(page);
629
0
    opaque->btpo_cycleid = 0;
630
0
    opaque->btpo_flags &= ~BTP_HAS_GARBAGE;
631
632
0
    PageSetLSN(page, lsn);
633
0
    MarkBufferDirty(buffer);
634
0
  }
635
0
  if (BufferIsValid(buffer))
636
0
    UnlockReleaseBuffer(buffer);
637
0
}
638
639
static void
640
btree_xlog_delete(XLogReaderState *record)
641
0
{
642
0
  XLogRecPtr  lsn = record->EndRecPtr;
643
0
  xl_btree_delete *xlrec = (xl_btree_delete *) XLogRecGetData(record);
644
0
  Buffer    buffer;
645
0
  Page    page;
646
0
  BTPageOpaque opaque;
647
648
  /*
649
   * If we have any conflict processing to do, it must happen before we
650
   * update the page
651
   */
652
0
  if (InHotStandby)
653
0
  {
654
0
    RelFileLocator rlocator;
655
656
0
    XLogRecGetBlockTag(record, 0, &rlocator, NULL, NULL);
657
658
0
    ResolveRecoveryConflictWithSnapshot(xlrec->snapshotConflictHorizon,
659
0
                      xlrec->isCatalogRel,
660
0
                      rlocator);
661
0
  }
662
663
  /*
664
   * We don't need to take a cleanup lock to apply these changes. See
665
   * nbtree/README for details.
666
   */
667
0
  if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
668
0
  {
669
0
    char     *ptr = XLogRecGetBlockData(record, 0, NULL);
670
671
0
    page = BufferGetPage(buffer);
672
673
0
    if (xlrec->nupdated > 0)
674
0
    {
675
0
      OffsetNumber *updatedoffsets;
676
0
      xl_btree_update *updates;
677
678
0
      updatedoffsets = (OffsetNumber *)
679
0
        (ptr + xlrec->ndeleted * sizeof(OffsetNumber));
680
0
      updates = (xl_btree_update *) ((char *) updatedoffsets +
681
0
                       xlrec->nupdated *
682
0
                       sizeof(OffsetNumber));
683
684
0
      btree_xlog_updates(page, updatedoffsets, updates, xlrec->nupdated);
685
0
    }
686
687
0
    if (xlrec->ndeleted > 0)
688
0
      PageIndexMultiDelete(page, (OffsetNumber *) ptr, xlrec->ndeleted);
689
690
    /*
691
     * Do *not* clear the vacuum cycle ID, but do mark the page as not
692
     * containing any LP_DEAD items
693
     */
694
0
    opaque = BTPageGetOpaque(page);
695
0
    opaque->btpo_flags &= ~BTP_HAS_GARBAGE;
696
697
0
    PageSetLSN(page, lsn);
698
0
    MarkBufferDirty(buffer);
699
0
  }
700
0
  if (BufferIsValid(buffer))
701
0
    UnlockReleaseBuffer(buffer);
702
0
}
703
704
static void
705
btree_xlog_mark_page_halfdead(uint8 info, XLogReaderState *record)
706
0
{
707
0
  XLogRecPtr  lsn = record->EndRecPtr;
708
0
  xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *) XLogRecGetData(record);
709
0
  Buffer    buffer;
710
0
  Page    page;
711
0
  BTPageOpaque pageop;
712
0
  IndexTupleData trunctuple;
713
714
  /*
715
   * In normal operation, we would lock all the pages this WAL record
716
   * touches before changing any of them.  In WAL replay, it should be okay
717
   * to lock just one page at a time, since no concurrent index updates can
718
   * be happening, and readers should not care whether they arrive at the
719
   * target page or not (since it's surely empty).
720
   */
721
722
  /* to-be-deleted subtree's parent page */
723
0
  if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
724
0
  {
725
0
    OffsetNumber poffset;
726
0
    ItemId    itemid;
727
0
    IndexTuple  itup;
728
0
    OffsetNumber nextoffset;
729
0
    BlockNumber rightsib;
730
731
0
    page = BufferGetPage(buffer);
732
0
    pageop = BTPageGetOpaque(page);
733
734
0
    poffset = xlrec->poffset;
735
736
0
    nextoffset = OffsetNumberNext(poffset);
737
0
    itemid = PageGetItemId(page, nextoffset);
738
0
    itup = (IndexTuple) PageGetItem(page, itemid);
739
0
    rightsib = BTreeTupleGetDownLink(itup);
740
741
0
    itemid = PageGetItemId(page, poffset);
742
0
    itup = (IndexTuple) PageGetItem(page, itemid);
743
0
    BTreeTupleSetDownLink(itup, rightsib);
744
0
    nextoffset = OffsetNumberNext(poffset);
745
0
    PageIndexTupleDelete(page, nextoffset);
746
747
0
    PageSetLSN(page, lsn);
748
0
    MarkBufferDirty(buffer);
749
0
  }
750
751
  /*
752
   * Don't need to couple cross-level locks in REDO routines, so release
753
   * lock on internal page immediately
754
   */
755
0
  if (BufferIsValid(buffer))
756
0
    UnlockReleaseBuffer(buffer);
757
758
  /* Rewrite the leaf page as a halfdead page */
759
0
  buffer = XLogInitBufferForRedo(record, 0);
760
0
  page = BufferGetPage(buffer);
761
762
0
  _bt_pageinit(page, BufferGetPageSize(buffer));
763
0
  pageop = BTPageGetOpaque(page);
764
765
0
  pageop->btpo_prev = xlrec->leftblk;
766
0
  pageop->btpo_next = xlrec->rightblk;
767
0
  pageop->btpo_level = 0;
768
0
  pageop->btpo_flags = BTP_HALF_DEAD | BTP_LEAF;
769
0
  pageop->btpo_cycleid = 0;
770
771
  /*
772
   * Construct a dummy high key item that points to top parent page (value
773
   * is InvalidBlockNumber when the top parent page is the leaf page itself)
774
   */
775
0
  MemSet(&trunctuple, 0, sizeof(IndexTupleData));
776
0
  trunctuple.t_info = sizeof(IndexTupleData);
777
0
  BTreeTupleSetTopParent(&trunctuple, xlrec->topparent);
778
779
0
  if (PageAddItem(page, &trunctuple, sizeof(IndexTupleData), P_HIKEY, false, false) == InvalidOffsetNumber)
780
0
    elog(ERROR, "could not add dummy high key to half-dead page");
781
782
0
  PageSetLSN(page, lsn);
783
0
  MarkBufferDirty(buffer);
784
0
  UnlockReleaseBuffer(buffer);
785
0
}
786
787
788
static void
789
btree_xlog_unlink_page(uint8 info, XLogReaderState *record)
790
0
{
791
0
  XLogRecPtr  lsn = record->EndRecPtr;
792
0
  xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *) XLogRecGetData(record);
793
0
  BlockNumber leftsib;
794
0
  BlockNumber rightsib;
795
0
  uint32    level;
796
0
  bool    isleaf;
797
0
  FullTransactionId safexid;
798
0
  Buffer    leftbuf;
799
0
  Buffer    target;
800
0
  Buffer    rightbuf;
801
0
  Page    page;
802
0
  BTPageOpaque pageop;
803
804
0
  leftsib = xlrec->leftsib;
805
0
  rightsib = xlrec->rightsib;
806
0
  level = xlrec->level;
807
0
  isleaf = (level == 0);
808
0
  safexid = xlrec->safexid;
809
810
  /* No leaftopparent for level 0 (leaf page) or level 1 target */
811
0
  Assert(!BlockNumberIsValid(xlrec->leaftopparent) || level > 1);
812
813
  /*
814
   * In normal operation, we would lock all the pages this WAL record
815
   * touches before changing any of them.  In WAL replay, we at least lock
816
   * the pages in the same standard left-to-right order (leftsib, target,
817
   * rightsib), and don't release the sibling locks until the target is
818
   * marked deleted.
819
   */
820
821
  /* Fix right-link of left sibling, if any */
822
0
  if (leftsib != P_NONE)
823
0
  {
824
0
    if (XLogReadBufferForRedo(record, 1, &leftbuf) == BLK_NEEDS_REDO)
825
0
    {
826
0
      page = BufferGetPage(leftbuf);
827
0
      pageop = BTPageGetOpaque(page);
828
0
      pageop->btpo_next = rightsib;
829
830
0
      PageSetLSN(page, lsn);
831
0
      MarkBufferDirty(leftbuf);
832
0
    }
833
0
  }
834
0
  else
835
0
    leftbuf = InvalidBuffer;
836
837
  /* Rewrite target page as empty deleted page */
838
0
  target = XLogInitBufferForRedo(record, 0);
839
0
  page = BufferGetPage(target);
840
841
0
  _bt_pageinit(page, BufferGetPageSize(target));
842
0
  pageop = BTPageGetOpaque(page);
843
844
0
  pageop->btpo_prev = leftsib;
845
0
  pageop->btpo_next = rightsib;
846
0
  pageop->btpo_level = level;
847
0
  BTPageSetDeleted(page, safexid);
848
0
  if (isleaf)
849
0
    pageop->btpo_flags |= BTP_LEAF;
850
0
  pageop->btpo_cycleid = 0;
851
852
0
  PageSetLSN(page, lsn);
853
0
  MarkBufferDirty(target);
854
855
  /* Fix left-link of right sibling */
856
0
  if (XLogReadBufferForRedo(record, 2, &rightbuf) == BLK_NEEDS_REDO)
857
0
  {
858
0
    page = BufferGetPage(rightbuf);
859
0
    pageop = BTPageGetOpaque(page);
860
0
    pageop->btpo_prev = leftsib;
861
862
0
    PageSetLSN(page, lsn);
863
0
    MarkBufferDirty(rightbuf);
864
0
  }
865
866
  /* Release siblings */
867
0
  if (BufferIsValid(leftbuf))
868
0
    UnlockReleaseBuffer(leftbuf);
869
0
  if (BufferIsValid(rightbuf))
870
0
    UnlockReleaseBuffer(rightbuf);
871
872
  /* Release target */
873
0
  UnlockReleaseBuffer(target);
874
875
  /*
876
   * If we deleted a parent of the targeted leaf page, instead of the leaf
877
   * itself, update the leaf to point to the next remaining child in the
878
   * to-be-deleted subtree
879
   */
880
0
  if (XLogRecHasBlockRef(record, 3))
881
0
  {
882
    /*
883
     * There is no real data on the page, so we just re-create it from
884
     * scratch using the information from the WAL record.
885
     *
886
     * Note that we don't end up here when the target page is also the
887
     * leafbuf page.  There is no need to add a dummy hikey item with a
888
     * top parent link when deleting leafbuf because it's the last page
889
     * we'll delete in the subtree undergoing deletion.
890
     */
891
0
    Buffer    leafbuf;
892
0
    IndexTupleData trunctuple;
893
894
0
    Assert(!isleaf);
895
896
0
    leafbuf = XLogInitBufferForRedo(record, 3);
897
0
    page = BufferGetPage(leafbuf);
898
899
0
    _bt_pageinit(page, BufferGetPageSize(leafbuf));
900
0
    pageop = BTPageGetOpaque(page);
901
902
0
    pageop->btpo_flags = BTP_HALF_DEAD | BTP_LEAF;
903
0
    pageop->btpo_prev = xlrec->leafleftsib;
904
0
    pageop->btpo_next = xlrec->leafrightsib;
905
0
    pageop->btpo_level = 0;
906
0
    pageop->btpo_cycleid = 0;
907
908
    /* Add a dummy hikey item */
909
0
    MemSet(&trunctuple, 0, sizeof(IndexTupleData));
910
0
    trunctuple.t_info = sizeof(IndexTupleData);
911
0
    BTreeTupleSetTopParent(&trunctuple, xlrec->leaftopparent);
912
913
0
    if (PageAddItem(page, &trunctuple, sizeof(IndexTupleData), P_HIKEY, false, false) == InvalidOffsetNumber)
914
0
      elog(ERROR, "could not add dummy high key to half-dead page");
915
916
0
    PageSetLSN(page, lsn);
917
0
    MarkBufferDirty(leafbuf);
918
0
    UnlockReleaseBuffer(leafbuf);
919
0
  }
920
921
  /* Update metapage if needed */
922
0
  if (info == XLOG_BTREE_UNLINK_PAGE_META)
923
0
    _bt_restore_meta(record, 4);
924
0
}
925
926
static void
927
btree_xlog_newroot(XLogReaderState *record)
928
0
{
929
0
  XLogRecPtr  lsn = record->EndRecPtr;
930
0
  xl_btree_newroot *xlrec = (xl_btree_newroot *) XLogRecGetData(record);
931
0
  Buffer    buffer;
932
0
  Page    page;
933
0
  BTPageOpaque pageop;
934
0
  char     *ptr;
935
0
  Size    len;
936
937
0
  buffer = XLogInitBufferForRedo(record, 0);
938
0
  page = BufferGetPage(buffer);
939
940
0
  _bt_pageinit(page, BufferGetPageSize(buffer));
941
0
  pageop = BTPageGetOpaque(page);
942
943
0
  pageop->btpo_flags = BTP_ROOT;
944
0
  pageop->btpo_prev = pageop->btpo_next = P_NONE;
945
0
  pageop->btpo_level = xlrec->level;
946
0
  if (xlrec->level == 0)
947
0
    pageop->btpo_flags |= BTP_LEAF;
948
0
  pageop->btpo_cycleid = 0;
949
950
0
  if (xlrec->level > 0)
951
0
  {
952
0
    ptr = XLogRecGetBlockData(record, 0, &len);
953
0
    _bt_restore_page(page, ptr, len);
954
955
    /* Clear the incomplete-split flag in left child */
956
0
    _bt_clear_incomplete_split(record, 1);
957
0
  }
958
959
0
  PageSetLSN(page, lsn);
960
0
  MarkBufferDirty(buffer);
961
0
  UnlockReleaseBuffer(buffer);
962
963
0
  _bt_restore_meta(record, 2);
964
0
}
965
966
/*
967
 * In general VACUUM must defer recycling as a way of avoiding certain race
968
 * conditions.  Deleted pages contain a safexid value that is used by VACUUM
969
 * to determine whether or not it's safe to place a page that was deleted by
970
 * VACUUM earlier into the FSM now.  See nbtree/README.
971
 *
972
 * As far as any backend operating during original execution is concerned, the
973
 * FSM is a cache of recycle-safe pages; the mere presence of the page in the
974
 * FSM indicates that the page must already be safe to recycle (actually,
975
 * _bt_allocbuf() verifies it's safe using BTPageIsRecyclable(), but that's
976
 * just because it would be unwise to completely trust the FSM, given its
977
 * current limitations).
978
 *
979
 * This isn't sufficient to prevent similar concurrent recycling race
980
 * conditions during Hot Standby, though.  For that we need to log a
981
 * xl_btree_reuse_page record at the point that a page is actually recycled
982
 * and reused for an entirely unrelated page inside _bt_split().  These
983
 * records include the same safexid value from the original deleted page,
984
 * stored in the record's snapshotConflictHorizon field.
985
 *
986
 * The GlobalVisCheckRemovableFullXid() test in BTPageIsRecyclable() is used
987
 * to determine if it's safe to recycle a page.  This mirrors our own test:
988
 * the PGPROC->xmin > limitXmin test inside GetConflictingVirtualXIDs().
989
 * Consequently, one XID value achieves the same exclusion effect on primary
990
 * and standby.
991
 */
992
static void
993
btree_xlog_reuse_page(XLogReaderState *record)
994
0
{
995
0
  xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) XLogRecGetData(record);
996
997
0
  if (InHotStandby)
998
0
    ResolveRecoveryConflictWithSnapshotFullXid(xlrec->snapshotConflictHorizon,
999
0
                           xlrec->isCatalogRel,
1000
0
                           xlrec->locator);
1001
0
}
1002
1003
void
1004
btree_redo(XLogReaderState *record)
1005
0
{
1006
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
1007
0
  MemoryContext oldCtx;
1008
1009
0
  oldCtx = MemoryContextSwitchTo(opCtx);
1010
0
  switch (info)
1011
0
  {
1012
0
    case XLOG_BTREE_INSERT_LEAF:
1013
0
      btree_xlog_insert(true, false, false, record);
1014
0
      break;
1015
0
    case XLOG_BTREE_INSERT_UPPER:
1016
0
      btree_xlog_insert(false, false, false, record);
1017
0
      break;
1018
0
    case XLOG_BTREE_INSERT_META:
1019
0
      btree_xlog_insert(false, true, false, record);
1020
0
      break;
1021
0
    case XLOG_BTREE_SPLIT_L:
1022
0
      btree_xlog_split(true, record);
1023
0
      break;
1024
0
    case XLOG_BTREE_SPLIT_R:
1025
0
      btree_xlog_split(false, record);
1026
0
      break;
1027
0
    case XLOG_BTREE_INSERT_POST:
1028
0
      btree_xlog_insert(true, false, true, record);
1029
0
      break;
1030
0
    case XLOG_BTREE_DEDUP:
1031
0
      btree_xlog_dedup(record);
1032
0
      break;
1033
0
    case XLOG_BTREE_VACUUM:
1034
0
      btree_xlog_vacuum(record);
1035
0
      break;
1036
0
    case XLOG_BTREE_DELETE:
1037
0
      btree_xlog_delete(record);
1038
0
      break;
1039
0
    case XLOG_BTREE_MARK_PAGE_HALFDEAD:
1040
0
      btree_xlog_mark_page_halfdead(info, record);
1041
0
      break;
1042
0
    case XLOG_BTREE_UNLINK_PAGE:
1043
0
    case XLOG_BTREE_UNLINK_PAGE_META:
1044
0
      btree_xlog_unlink_page(info, record);
1045
0
      break;
1046
0
    case XLOG_BTREE_NEWROOT:
1047
0
      btree_xlog_newroot(record);
1048
0
      break;
1049
0
    case XLOG_BTREE_REUSE_PAGE:
1050
0
      btree_xlog_reuse_page(record);
1051
0
      break;
1052
0
    case XLOG_BTREE_META_CLEANUP:
1053
0
      _bt_restore_meta(record, 0);
1054
0
      break;
1055
0
    default:
1056
0
      elog(PANIC, "btree_redo: unknown op code %u", info);
1057
0
  }
1058
0
  MemoryContextSwitchTo(oldCtx);
1059
0
  MemoryContextReset(opCtx);
1060
0
}
1061
1062
void
1063
btree_xlog_startup(void)
1064
0
{
1065
0
  opCtx = AllocSetContextCreate(CurrentMemoryContext,
1066
0
                  "Btree recovery temporary context",
1067
0
                  ALLOCSET_DEFAULT_SIZES);
1068
0
}
1069
1070
void
1071
btree_xlog_cleanup(void)
1072
0
{
1073
0
  MemoryContextDelete(opCtx);
1074
0
  opCtx = NULL;
1075
0
}
1076
1077
/*
1078
 * Mask a btree page before performing consistency checks on it.
1079
 */
1080
void
1081
btree_mask(char *pagedata, BlockNumber blkno)
1082
0
{
1083
0
  Page    page = (Page) pagedata;
1084
0
  BTPageOpaque maskopaq;
1085
1086
0
  mask_page_lsn_and_checksum(page);
1087
1088
0
  mask_page_hint_bits(page);
1089
0
  mask_unused_space(page);
1090
1091
0
  maskopaq = BTPageGetOpaque(page);
1092
1093
0
  if (P_ISLEAF(maskopaq))
1094
0
  {
1095
    /*
1096
     * In btree leaf pages, it is possible to modify the LP_FLAGS without
1097
     * emitting any WAL record. Hence, mask the line pointer flags. See
1098
     * _bt_killitems(), _bt_check_unique() for details.
1099
     */
1100
0
    mask_lp_flags(page);
1101
0
  }
1102
1103
  /*
1104
   * BTP_HAS_GARBAGE is just an un-logged hint bit. So, mask it. See
1105
   * _bt_delete_or_dedup_one_page(), _bt_killitems(), and _bt_check_unique()
1106
   * for details.
1107
   */
1108
0
  maskopaq->btpo_flags &= ~BTP_HAS_GARBAGE;
1109
1110
  /*
1111
   * During replay of a btree page split, we don't set the BTP_SPLIT_END
1112
   * flag of the right sibling and initialize the cycle_id to 0 for the same
1113
   * page. See btree_xlog_split() for details.
1114
   */
1115
0
  maskopaq->btpo_flags &= ~BTP_SPLIT_END;
1116
0
  maskopaq->btpo_cycleid = 0;
1117
0
}