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/hash/hash_xlog.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * hash_xlog.c
4
 *    WAL replay logic for hash index.
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/hash/hash_xlog.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/bufmask.h"
18
#include "access/hash.h"
19
#include "access/hash_xlog.h"
20
#include "access/xlogutils.h"
21
#include "storage/standby.h"
22
23
/*
24
 * replay a hash index meta page
25
 */
26
static void
27
hash_xlog_init_meta_page(XLogReaderState *record)
28
0
{
29
0
  XLogRecPtr  lsn = record->EndRecPtr;
30
0
  Page    page;
31
0
  Buffer    metabuf;
32
33
0
  xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) XLogRecGetData(record);
34
35
  /* create the index' metapage */
36
0
  metabuf = XLogInitBufferForRedo(record, 0);
37
0
  Assert(BufferIsValid(metabuf));
38
0
  _hash_init_metabuffer(metabuf, xlrec->num_tuples, xlrec->procid,
39
0
              xlrec->ffactor, true);
40
0
  page = BufferGetPage(metabuf);
41
0
  PageSetLSN(page, lsn);
42
0
  MarkBufferDirty(metabuf);
43
0
  XLogFlushBufferForRedoIfInit(record, 0, metabuf);
44
45
  /* all done */
46
0
  UnlockReleaseBuffer(metabuf);
47
0
}
48
49
/*
50
 * replay a hash index bitmap page
51
 */
52
static void
53
hash_xlog_init_bitmap_page(XLogReaderState *record)
54
0
{
55
0
  XLogRecPtr  lsn = record->EndRecPtr;
56
0
  Buffer    bitmapbuf;
57
0
  Buffer    metabuf;
58
0
  Page    page;
59
0
  HashMetaPage metap;
60
0
  uint32    num_buckets;
61
62
0
  xl_hash_init_bitmap_page *xlrec = (xl_hash_init_bitmap_page *) XLogRecGetData(record);
63
64
  /*
65
   * Initialize bitmap page
66
   */
67
0
  bitmapbuf = XLogInitBufferForRedo(record, 0);
68
0
  _hash_initbitmapbuffer(bitmapbuf, xlrec->bmsize, true);
69
0
  PageSetLSN(BufferGetPage(bitmapbuf), lsn);
70
0
  MarkBufferDirty(bitmapbuf);
71
0
  XLogFlushBufferForRedoIfInit(record, 0, bitmapbuf);
72
0
  UnlockReleaseBuffer(bitmapbuf);
73
74
  /* add the new bitmap page to the metapage's list of bitmaps */
75
0
  if (XLogReadBufferForRedo(record, 1, &metabuf) == BLK_NEEDS_REDO)
76
0
  {
77
    /*
78
     * Note: in normal operation, we'd update the metapage while still
79
     * holding lock on the bitmap page.  But during replay it's not
80
     * necessary to hold that lock, since nobody can see it yet; the
81
     * creating transaction hasn't yet committed.
82
     */
83
0
    page = BufferGetPage(metabuf);
84
0
    metap = HashPageGetMeta(page);
85
86
0
    num_buckets = metap->hashm_maxbucket + 1;
87
0
    metap->hashm_mapp[metap->hashm_nmaps] = num_buckets + 1;
88
0
    metap->hashm_nmaps++;
89
90
0
    PageSetLSN(page, lsn);
91
0
    MarkBufferDirty(metabuf);
92
0
    XLogFlushBufferForRedoIfInit(record, 1, metabuf);
93
0
  }
94
0
  if (BufferIsValid(metabuf))
95
0
    UnlockReleaseBuffer(metabuf);
96
0
}
97
98
/*
99
 * replay a hash index insert without split
100
 */
101
static void
102
hash_xlog_insert(XLogReaderState *record)
103
0
{
104
0
  HashMetaPage metap;
105
0
  XLogRecPtr  lsn = record->EndRecPtr;
106
0
  xl_hash_insert *xlrec = (xl_hash_insert *) XLogRecGetData(record);
107
0
  Buffer    buffer;
108
0
  Page    page;
109
110
0
  if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
111
0
  {
112
0
    Size    datalen;
113
0
    char     *datapos = XLogRecGetBlockData(record, 0, &datalen);
114
115
0
    page = BufferGetPage(buffer);
116
117
0
    if (PageAddItem(page, datapos, datalen, xlrec->offnum, false, false) == InvalidOffsetNumber)
118
0
      elog(PANIC, "hash_xlog_insert: failed to add item");
119
120
0
    PageSetLSN(page, lsn);
121
0
    MarkBufferDirty(buffer);
122
0
  }
123
0
  if (BufferIsValid(buffer))
124
0
    UnlockReleaseBuffer(buffer);
125
126
0
  if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
127
0
  {
128
    /*
129
     * Note: in normal operation, we'd update the metapage while still
130
     * holding lock on the page we inserted into.  But during replay it's
131
     * not necessary to hold that lock, since no other index updates can
132
     * be happening concurrently.
133
     */
134
0
    page = BufferGetPage(buffer);
135
0
    metap = HashPageGetMeta(page);
136
0
    metap->hashm_ntuples += 1;
137
138
0
    PageSetLSN(page, lsn);
139
0
    MarkBufferDirty(buffer);
140
0
  }
141
0
  if (BufferIsValid(buffer))
142
0
    UnlockReleaseBuffer(buffer);
143
0
}
144
145
/*
146
 * replay addition of overflow page for hash index
147
 */
148
static void
149
hash_xlog_add_ovfl_page(XLogReaderState *record)
150
0
{
151
0
  XLogRecPtr  lsn = record->EndRecPtr;
152
0
  xl_hash_add_ovfl_page *xlrec = (xl_hash_add_ovfl_page *) XLogRecGetData(record);
153
0
  Buffer    leftbuf;
154
0
  Buffer    ovflbuf;
155
0
  Buffer    metabuf;
156
0
  BlockNumber leftblk;
157
0
  BlockNumber rightblk;
158
0
  BlockNumber newmapblk = InvalidBlockNumber;
159
0
  Page    ovflpage;
160
0
  HashPageOpaque ovflopaque;
161
0
  uint32     *num_bucket;
162
0
  char     *data;
163
0
  Size    datalen PG_USED_FOR_ASSERTS_ONLY;
164
0
  bool    new_bmpage = false;
165
166
0
  XLogRecGetBlockTag(record, 0, NULL, NULL, &rightblk);
167
0
  XLogRecGetBlockTag(record, 1, NULL, NULL, &leftblk);
168
169
0
  ovflbuf = XLogInitBufferForRedo(record, 0);
170
0
  Assert(BufferIsValid(ovflbuf));
171
172
0
  data = XLogRecGetBlockData(record, 0, &datalen);
173
0
  num_bucket = (uint32 *) data;
174
0
  Assert(datalen == sizeof(uint32));
175
0
  _hash_initbuf(ovflbuf, InvalidBlockNumber, *num_bucket, LH_OVERFLOW_PAGE,
176
0
          true);
177
  /* update backlink */
178
0
  ovflpage = BufferGetPage(ovflbuf);
179
0
  ovflopaque = HashPageGetOpaque(ovflpage);
180
0
  ovflopaque->hasho_prevblkno = leftblk;
181
182
0
  PageSetLSN(ovflpage, lsn);
183
0
  MarkBufferDirty(ovflbuf);
184
185
0
  if (XLogReadBufferForRedo(record, 1, &leftbuf) == BLK_NEEDS_REDO)
186
0
  {
187
0
    Page    leftpage;
188
0
    HashPageOpaque leftopaque;
189
190
0
    leftpage = BufferGetPage(leftbuf);
191
0
    leftopaque = HashPageGetOpaque(leftpage);
192
0
    leftopaque->hasho_nextblkno = rightblk;
193
194
0
    PageSetLSN(leftpage, lsn);
195
0
    MarkBufferDirty(leftbuf);
196
0
  }
197
198
0
  if (BufferIsValid(leftbuf))
199
0
    UnlockReleaseBuffer(leftbuf);
200
0
  UnlockReleaseBuffer(ovflbuf);
201
202
  /*
203
   * Note: in normal operation, we'd update the bitmap and meta page while
204
   * still holding lock on the overflow pages.  But during replay it's not
205
   * necessary to hold those locks, since no other index updates can be
206
   * happening concurrently.
207
   */
208
0
  if (XLogRecHasBlockRef(record, 2))
209
0
  {
210
0
    Buffer    mapbuffer;
211
212
0
    if (XLogReadBufferForRedo(record, 2, &mapbuffer) == BLK_NEEDS_REDO)
213
0
    {
214
0
      Page    mappage = BufferGetPage(mapbuffer);
215
0
      uint32     *freep = NULL;
216
0
      uint32     *bitmap_page_bit;
217
218
0
      freep = HashPageGetBitmap(mappage);
219
220
0
      data = XLogRecGetBlockData(record, 2, &datalen);
221
0
      bitmap_page_bit = (uint32 *) data;
222
223
0
      SETBIT(freep, *bitmap_page_bit);
224
225
0
      PageSetLSN(mappage, lsn);
226
0
      MarkBufferDirty(mapbuffer);
227
0
    }
228
0
    if (BufferIsValid(mapbuffer))
229
0
      UnlockReleaseBuffer(mapbuffer);
230
0
  }
231
232
0
  if (XLogRecHasBlockRef(record, 3))
233
0
  {
234
0
    Buffer    newmapbuf;
235
236
0
    newmapbuf = XLogInitBufferForRedo(record, 3);
237
238
0
    _hash_initbitmapbuffer(newmapbuf, xlrec->bmsize, true);
239
240
0
    new_bmpage = true;
241
0
    newmapblk = BufferGetBlockNumber(newmapbuf);
242
243
0
    MarkBufferDirty(newmapbuf);
244
0
    PageSetLSN(BufferGetPage(newmapbuf), lsn);
245
246
0
    UnlockReleaseBuffer(newmapbuf);
247
0
  }
248
249
0
  if (XLogReadBufferForRedo(record, 4, &metabuf) == BLK_NEEDS_REDO)
250
0
  {
251
0
    HashMetaPage metap;
252
0
    Page    page;
253
0
    uint32     *firstfree_ovflpage;
254
255
0
    data = XLogRecGetBlockData(record, 4, &datalen);
256
0
    firstfree_ovflpage = (uint32 *) data;
257
258
0
    page = BufferGetPage(metabuf);
259
0
    metap = HashPageGetMeta(page);
260
0
    metap->hashm_firstfree = *firstfree_ovflpage;
261
262
0
    if (!xlrec->bmpage_found)
263
0
    {
264
0
      metap->hashm_spares[metap->hashm_ovflpoint]++;
265
266
0
      if (new_bmpage)
267
0
      {
268
0
        Assert(BlockNumberIsValid(newmapblk));
269
270
0
        metap->hashm_mapp[metap->hashm_nmaps] = newmapblk;
271
0
        metap->hashm_nmaps++;
272
0
        metap->hashm_spares[metap->hashm_ovflpoint]++;
273
0
      }
274
0
    }
275
276
0
    PageSetLSN(page, lsn);
277
0
    MarkBufferDirty(metabuf);
278
0
  }
279
0
  if (BufferIsValid(metabuf))
280
0
    UnlockReleaseBuffer(metabuf);
281
0
}
282
283
/*
284
 * replay allocation of page for split operation
285
 */
286
static void
287
hash_xlog_split_allocate_page(XLogReaderState *record)
288
0
{
289
0
  XLogRecPtr  lsn = record->EndRecPtr;
290
0
  xl_hash_split_allocate_page *xlrec = (xl_hash_split_allocate_page *) XLogRecGetData(record);
291
0
  Buffer    oldbuf;
292
0
  Buffer    newbuf;
293
0
  Buffer    metabuf;
294
0
  XLogRedoAction action;
295
296
  /*
297
   * To be consistent with normal operation, here we take cleanup locks on
298
   * both the old and new buckets even though there can't be any concurrent
299
   * inserts.
300
   */
301
302
  /* replay the record for old bucket */
303
0
  action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &oldbuf);
304
305
  /*
306
   * Note that we still update the page even if it was restored from a full
307
   * page image, because the special space is not included in the image.
308
   */
309
0
  if (action == BLK_NEEDS_REDO || action == BLK_RESTORED)
310
0
  {
311
0
    Page    oldpage;
312
0
    HashPageOpaque oldopaque;
313
314
0
    oldpage = BufferGetPage(oldbuf);
315
0
    oldopaque = HashPageGetOpaque(oldpage);
316
317
0
    oldopaque->hasho_flag = xlrec->old_bucket_flag;
318
0
    oldopaque->hasho_prevblkno = xlrec->new_bucket;
319
320
0
    PageSetLSN(oldpage, lsn);
321
0
    MarkBufferDirty(oldbuf);
322
0
  }
323
324
  /* replay the record for new bucket */
325
0
  XLogReadBufferForRedoExtended(record, 1, RBM_ZERO_AND_CLEANUP_LOCK, true,
326
0
                  &newbuf);
327
0
  _hash_initbuf(newbuf, xlrec->new_bucket, xlrec->new_bucket,
328
0
          xlrec->new_bucket_flag, true);
329
0
  MarkBufferDirty(newbuf);
330
0
  PageSetLSN(BufferGetPage(newbuf), lsn);
331
332
  /*
333
   * We can release the lock on old bucket early as well but doing here to
334
   * consistent with normal operation.
335
   */
336
0
  if (BufferIsValid(oldbuf))
337
0
    UnlockReleaseBuffer(oldbuf);
338
0
  if (BufferIsValid(newbuf))
339
0
    UnlockReleaseBuffer(newbuf);
340
341
  /*
342
   * Note: in normal operation, we'd update the meta page while still
343
   * holding lock on the old and new bucket pages.  But during replay it's
344
   * not necessary to hold those locks, since no other bucket splits can be
345
   * happening concurrently.
346
   */
347
348
  /* replay the record for metapage changes */
349
0
  if (XLogReadBufferForRedo(record, 2, &metabuf) == BLK_NEEDS_REDO)
350
0
  {
351
0
    Page    page;
352
0
    HashMetaPage metap;
353
0
    Size    datalen;
354
0
    char     *data;
355
0
    uint32     *uidata;
356
0
    int     uidatacount;
357
358
0
    page = BufferGetPage(metabuf);
359
0
    metap = HashPageGetMeta(page);
360
0
    metap->hashm_maxbucket = xlrec->new_bucket;
361
362
0
    data = XLogRecGetBlockData(record, 2, &datalen);
363
364
    /*
365
     * This cast is ok because XLogRecGetBlockData() returns a MAXALIGNed
366
     * buffer.
367
     */
368
0
    uidata = (uint32 *) data;
369
0
    uidatacount = 0;
370
371
0
    if (xlrec->flags & XLH_SPLIT_META_UPDATE_MASKS)
372
0
    {
373
0
      uint32    lowmask = uidata[uidatacount++];
374
0
      uint32    highmask = uidata[uidatacount++];
375
376
      /* update metapage */
377
0
      metap->hashm_lowmask = lowmask;
378
0
      metap->hashm_highmask = highmask;
379
0
    }
380
381
0
    if (xlrec->flags & XLH_SPLIT_META_UPDATE_SPLITPOINT)
382
0
    {
383
0
      uint32    ovflpoint = uidata[uidatacount++];
384
0
      uint32    ovflpages = uidata[uidatacount++];
385
386
      /* update metapage */
387
0
      metap->hashm_ovflpoint = ovflpoint;
388
0
      metap->hashm_spares[ovflpoint] = ovflpages;
389
0
    }
390
391
0
    MarkBufferDirty(metabuf);
392
0
    PageSetLSN(BufferGetPage(metabuf), lsn);
393
0
  }
394
395
0
  if (BufferIsValid(metabuf))
396
0
    UnlockReleaseBuffer(metabuf);
397
0
}
398
399
/*
400
 * replay of split operation
401
 */
402
static void
403
hash_xlog_split_page(XLogReaderState *record)
404
0
{
405
0
  Buffer    buf;
406
407
0
  if (XLogReadBufferForRedo(record, 0, &buf) != BLK_RESTORED)
408
0
    elog(ERROR, "Hash split record did not contain a full-page image");
409
410
0
  UnlockReleaseBuffer(buf);
411
0
}
412
413
/*
414
 * replay completion of split operation
415
 */
416
static void
417
hash_xlog_split_complete(XLogReaderState *record)
418
0
{
419
0
  XLogRecPtr  lsn = record->EndRecPtr;
420
0
  xl_hash_split_complete *xlrec = (xl_hash_split_complete *) XLogRecGetData(record);
421
0
  Buffer    oldbuf;
422
0
  Buffer    newbuf;
423
0
  XLogRedoAction action;
424
425
  /* replay the record for old bucket */
426
0
  action = XLogReadBufferForRedo(record, 0, &oldbuf);
427
428
  /*
429
   * Note that we still update the page even if it was restored from a full
430
   * page image, because the bucket flag is not included in the image.
431
   */
432
0
  if (action == BLK_NEEDS_REDO || action == BLK_RESTORED)
433
0
  {
434
0
    Page    oldpage;
435
0
    HashPageOpaque oldopaque;
436
437
0
    oldpage = BufferGetPage(oldbuf);
438
0
    oldopaque = HashPageGetOpaque(oldpage);
439
440
0
    oldopaque->hasho_flag = xlrec->old_bucket_flag;
441
442
0
    PageSetLSN(oldpage, lsn);
443
0
    MarkBufferDirty(oldbuf);
444
0
  }
445
0
  if (BufferIsValid(oldbuf))
446
0
    UnlockReleaseBuffer(oldbuf);
447
448
  /* replay the record for new bucket */
449
0
  action = XLogReadBufferForRedo(record, 1, &newbuf);
450
451
  /*
452
   * Note that we still update the page even if it was restored from a full
453
   * page image, because the bucket flag is not included in the image.
454
   */
455
0
  if (action == BLK_NEEDS_REDO || action == BLK_RESTORED)
456
0
  {
457
0
    Page    newpage;
458
0
    HashPageOpaque nopaque;
459
460
0
    newpage = BufferGetPage(newbuf);
461
0
    nopaque = HashPageGetOpaque(newpage);
462
463
0
    nopaque->hasho_flag = xlrec->new_bucket_flag;
464
465
0
    PageSetLSN(newpage, lsn);
466
0
    MarkBufferDirty(newbuf);
467
0
  }
468
0
  if (BufferIsValid(newbuf))
469
0
    UnlockReleaseBuffer(newbuf);
470
0
}
471
472
/*
473
 * replay move of page contents for squeeze operation of hash index
474
 */
475
static void
476
hash_xlog_move_page_contents(XLogReaderState *record)
477
0
{
478
0
  XLogRecPtr  lsn = record->EndRecPtr;
479
0
  xl_hash_move_page_contents *xldata = (xl_hash_move_page_contents *) XLogRecGetData(record);
480
0
  Buffer    bucketbuf = InvalidBuffer;
481
0
  Buffer    writebuf = InvalidBuffer;
482
0
  Buffer    deletebuf = InvalidBuffer;
483
0
  XLogRedoAction action;
484
485
  /*
486
   * Ensure we have a cleanup lock on primary bucket page before we start
487
   * with the actual replay operation.  This is to ensure that neither a
488
   * scan can start nor a scan can be already-in-progress during the replay
489
   * of this operation.  If we allow scans during this operation, then they
490
   * can miss some records or show the same record multiple times.
491
   */
492
0
  if (xldata->is_prim_bucket_same_wrt)
493
0
    action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &writebuf);
494
0
  else
495
0
  {
496
    /*
497
     * we don't care for return value as the purpose of reading bucketbuf
498
     * is to ensure a cleanup lock on primary bucket page.
499
     */
500
0
    (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf);
501
502
0
    action = XLogReadBufferForRedo(record, 1, &writebuf);
503
0
  }
504
505
  /* replay the record for adding entries in overflow buffer */
506
0
  if (action == BLK_NEEDS_REDO)
507
0
  {
508
0
    Page    writepage;
509
0
    char     *begin;
510
0
    char     *data;
511
0
    Size    datalen;
512
0
    uint16    ninserted = 0;
513
514
0
    data = begin = XLogRecGetBlockData(record, 1, &datalen);
515
516
0
    writepage = BufferGetPage(writebuf);
517
518
0
    if (xldata->ntups > 0)
519
0
    {
520
0
      OffsetNumber *towrite = (OffsetNumber *) data;
521
522
0
      data += sizeof(OffsetNumber) * xldata->ntups;
523
524
0
      while (data - begin < datalen)
525
0
      {
526
0
        IndexTuple  itup = (IndexTuple) data;
527
0
        Size    itemsz;
528
0
        OffsetNumber l;
529
530
0
        itemsz = IndexTupleSize(itup);
531
0
        itemsz = MAXALIGN(itemsz);
532
533
0
        data += itemsz;
534
535
0
        l = PageAddItem(writepage, itup, itemsz, towrite[ninserted], false, false);
536
0
        if (l == InvalidOffsetNumber)
537
0
          elog(ERROR, "hash_xlog_move_page_contents: failed to add item to hash index page, size %zu bytes", itemsz);
538
539
0
        ninserted++;
540
0
      }
541
0
    }
542
543
    /*
544
     * number of tuples inserted must be same as requested in REDO record.
545
     */
546
0
    Assert(ninserted == xldata->ntups);
547
548
0
    PageSetLSN(writepage, lsn);
549
0
    MarkBufferDirty(writebuf);
550
0
  }
551
552
  /* replay the record for deleting entries from overflow buffer */
553
0
  if (XLogReadBufferForRedo(record, 2, &deletebuf) == BLK_NEEDS_REDO)
554
0
  {
555
0
    Page    page;
556
0
    char     *ptr;
557
0
    Size    len;
558
559
0
    ptr = XLogRecGetBlockData(record, 2, &len);
560
561
0
    page = BufferGetPage(deletebuf);
562
563
0
    if (len > 0)
564
0
    {
565
0
      OffsetNumber *unused;
566
0
      OffsetNumber *unend;
567
568
0
      unused = (OffsetNumber *) ptr;
569
0
      unend = (OffsetNumber *) (ptr + len);
570
571
0
      if ((unend - unused) > 0)
572
0
        PageIndexMultiDelete(page, unused, unend - unused);
573
0
    }
574
575
0
    PageSetLSN(page, lsn);
576
0
    MarkBufferDirty(deletebuf);
577
0
  }
578
579
  /*
580
   * Replay is complete, now we can release the buffers. We release locks at
581
   * end of replay operation to ensure that we hold lock on primary bucket
582
   * page till end of operation.  We can optimize by releasing the lock on
583
   * write buffer as soon as the operation for same is complete, if it is
584
   * not same as primary bucket page, but that doesn't seem to be worth
585
   * complicating the code.
586
   */
587
0
  if (BufferIsValid(deletebuf))
588
0
    UnlockReleaseBuffer(deletebuf);
589
590
0
  if (BufferIsValid(writebuf))
591
0
    UnlockReleaseBuffer(writebuf);
592
593
0
  if (BufferIsValid(bucketbuf))
594
0
    UnlockReleaseBuffer(bucketbuf);
595
0
}
596
597
/*
598
 * replay squeeze page operation of hash index
599
 */
600
static void
601
hash_xlog_squeeze_page(XLogReaderState *record)
602
0
{
603
0
  XLogRecPtr  lsn = record->EndRecPtr;
604
0
  xl_hash_squeeze_page *xldata = (xl_hash_squeeze_page *) XLogRecGetData(record);
605
0
  Buffer    bucketbuf = InvalidBuffer;
606
0
  Buffer    writebuf = InvalidBuffer;
607
0
  Buffer    ovflbuf;
608
0
  Buffer    prevbuf = InvalidBuffer;
609
0
  Buffer    mapbuf;
610
0
  XLogRedoAction action;
611
612
  /*
613
   * Ensure we have a cleanup lock on primary bucket page before we start
614
   * with the actual replay operation.  This is to ensure that neither a
615
   * scan can start nor a scan can be already-in-progress during the replay
616
   * of this operation.  If we allow scans during this operation, then they
617
   * can miss some records or show the same record multiple times.
618
   */
619
0
  if (xldata->is_prim_bucket_same_wrt)
620
0
    action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &writebuf);
621
0
  else
622
0
  {
623
    /*
624
     * we don't care for return value as the purpose of reading bucketbuf
625
     * is to ensure a cleanup lock on primary bucket page.
626
     */
627
0
    (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf);
628
629
0
    if (xldata->ntups > 0 || xldata->is_prev_bucket_same_wrt)
630
0
      action = XLogReadBufferForRedo(record, 1, &writebuf);
631
0
    else
632
0
      action = BLK_NOTFOUND;
633
0
  }
634
635
  /* replay the record for adding entries in overflow buffer */
636
0
  if (action == BLK_NEEDS_REDO)
637
0
  {
638
0
    Page    writepage;
639
0
    char     *begin;
640
0
    char     *data;
641
0
    Size    datalen;
642
0
    uint16    ninserted = 0;
643
0
    bool    mod_wbuf = false;
644
645
0
    data = begin = XLogRecGetBlockData(record, 1, &datalen);
646
647
0
    writepage = BufferGetPage(writebuf);
648
649
0
    if (xldata->ntups > 0)
650
0
    {
651
0
      OffsetNumber *towrite = (OffsetNumber *) data;
652
653
0
      data += sizeof(OffsetNumber) * xldata->ntups;
654
655
0
      while (data - begin < datalen)
656
0
      {
657
0
        IndexTuple  itup = (IndexTuple) data;
658
0
        Size    itemsz;
659
0
        OffsetNumber l;
660
661
0
        itemsz = IndexTupleSize(itup);
662
0
        itemsz = MAXALIGN(itemsz);
663
664
0
        data += itemsz;
665
666
0
        l = PageAddItem(writepage, itup, itemsz, towrite[ninserted], false, false);
667
0
        if (l == InvalidOffsetNumber)
668
0
          elog(ERROR, "hash_xlog_squeeze_page: failed to add item to hash index page, size %zu bytes", itemsz);
669
670
0
        ninserted++;
671
0
      }
672
673
0
      mod_wbuf = true;
674
0
    }
675
0
    else
676
0
    {
677
      /*
678
       * Ensure that the required flags are set when there are no
679
       * tuples.  See _hash_freeovflpage().
680
       */
681
0
      Assert(xldata->is_prim_bucket_same_wrt ||
682
0
           xldata->is_prev_bucket_same_wrt);
683
0
    }
684
685
    /*
686
     * number of tuples inserted must be same as requested in REDO record.
687
     */
688
0
    Assert(ninserted == xldata->ntups);
689
690
    /*
691
     * if the page on which are adding tuples is a page previous to freed
692
     * overflow page, then update its nextblkno.
693
     */
694
0
    if (xldata->is_prev_bucket_same_wrt)
695
0
    {
696
0
      HashPageOpaque writeopaque = HashPageGetOpaque(writepage);
697
698
0
      writeopaque->hasho_nextblkno = xldata->nextblkno;
699
0
      mod_wbuf = true;
700
0
    }
701
702
    /* Set LSN and mark writebuf dirty iff it is modified */
703
0
    if (mod_wbuf)
704
0
    {
705
0
      PageSetLSN(writepage, lsn);
706
0
      MarkBufferDirty(writebuf);
707
0
    }
708
0
  }
709
710
  /* replay the record for initializing overflow buffer */
711
0
  if (XLogReadBufferForRedo(record, 2, &ovflbuf) == BLK_NEEDS_REDO)
712
0
  {
713
0
    Page    ovflpage;
714
0
    HashPageOpaque ovflopaque;
715
716
0
    ovflpage = BufferGetPage(ovflbuf);
717
718
0
    _hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf));
719
720
0
    ovflopaque = HashPageGetOpaque(ovflpage);
721
722
0
    ovflopaque->hasho_prevblkno = InvalidBlockNumber;
723
0
    ovflopaque->hasho_nextblkno = InvalidBlockNumber;
724
0
    ovflopaque->hasho_bucket = InvalidBucket;
725
0
    ovflopaque->hasho_flag = LH_UNUSED_PAGE;
726
0
    ovflopaque->hasho_page_id = HASHO_PAGE_ID;
727
728
0
    PageSetLSN(ovflpage, lsn);
729
0
    MarkBufferDirty(ovflbuf);
730
0
  }
731
0
  if (BufferIsValid(ovflbuf))
732
0
    UnlockReleaseBuffer(ovflbuf);
733
734
  /* replay the record for page previous to the freed overflow page */
735
0
  if (!xldata->is_prev_bucket_same_wrt &&
736
0
    XLogReadBufferForRedo(record, 3, &prevbuf) == BLK_NEEDS_REDO)
737
0
  {
738
0
    Page    prevpage = BufferGetPage(prevbuf);
739
0
    HashPageOpaque prevopaque = HashPageGetOpaque(prevpage);
740
741
0
    prevopaque->hasho_nextblkno = xldata->nextblkno;
742
743
0
    PageSetLSN(prevpage, lsn);
744
0
    MarkBufferDirty(prevbuf);
745
0
  }
746
0
  if (BufferIsValid(prevbuf))
747
0
    UnlockReleaseBuffer(prevbuf);
748
749
  /* replay the record for page next to the freed overflow page */
750
0
  if (XLogRecHasBlockRef(record, 4))
751
0
  {
752
0
    Buffer    nextbuf;
753
754
0
    if (XLogReadBufferForRedo(record, 4, &nextbuf) == BLK_NEEDS_REDO)
755
0
    {
756
0
      Page    nextpage = BufferGetPage(nextbuf);
757
0
      HashPageOpaque nextopaque = HashPageGetOpaque(nextpage);
758
759
0
      nextopaque->hasho_prevblkno = xldata->prevblkno;
760
761
0
      PageSetLSN(nextpage, lsn);
762
0
      MarkBufferDirty(nextbuf);
763
0
    }
764
0
    if (BufferIsValid(nextbuf))
765
0
      UnlockReleaseBuffer(nextbuf);
766
0
  }
767
768
0
  if (BufferIsValid(writebuf))
769
0
    UnlockReleaseBuffer(writebuf);
770
771
0
  if (BufferIsValid(bucketbuf))
772
0
    UnlockReleaseBuffer(bucketbuf);
773
774
  /*
775
   * Note: in normal operation, we'd update the bitmap and meta page while
776
   * still holding lock on the primary bucket page and overflow pages.  But
777
   * during replay it's not necessary to hold those locks, since no other
778
   * index updates can be happening concurrently.
779
   */
780
  /* replay the record for bitmap page */
781
0
  if (XLogReadBufferForRedo(record, 5, &mapbuf) == BLK_NEEDS_REDO)
782
0
  {
783
0
    Page    mappage = BufferGetPage(mapbuf);
784
0
    uint32     *freep = NULL;
785
0
    char     *data;
786
0
    uint32     *bitmap_page_bit;
787
0
    Size    datalen;
788
789
0
    freep = HashPageGetBitmap(mappage);
790
791
0
    data = XLogRecGetBlockData(record, 5, &datalen);
792
0
    bitmap_page_bit = (uint32 *) data;
793
794
0
    CLRBIT(freep, *bitmap_page_bit);
795
796
0
    PageSetLSN(mappage, lsn);
797
0
    MarkBufferDirty(mapbuf);
798
0
  }
799
0
  if (BufferIsValid(mapbuf))
800
0
    UnlockReleaseBuffer(mapbuf);
801
802
  /* replay the record for meta page */
803
0
  if (XLogRecHasBlockRef(record, 6))
804
0
  {
805
0
    Buffer    metabuf;
806
807
0
    if (XLogReadBufferForRedo(record, 6, &metabuf) == BLK_NEEDS_REDO)
808
0
    {
809
0
      HashMetaPage metap;
810
0
      Page    page;
811
0
      char     *data;
812
0
      uint32     *firstfree_ovflpage;
813
0
      Size    datalen;
814
815
0
      data = XLogRecGetBlockData(record, 6, &datalen);
816
0
      firstfree_ovflpage = (uint32 *) data;
817
818
0
      page = BufferGetPage(metabuf);
819
0
      metap = HashPageGetMeta(page);
820
0
      metap->hashm_firstfree = *firstfree_ovflpage;
821
822
0
      PageSetLSN(page, lsn);
823
0
      MarkBufferDirty(metabuf);
824
0
    }
825
0
    if (BufferIsValid(metabuf))
826
0
      UnlockReleaseBuffer(metabuf);
827
0
  }
828
0
}
829
830
/*
831
 * replay delete operation of hash index
832
 */
833
static void
834
hash_xlog_delete(XLogReaderState *record)
835
0
{
836
0
  XLogRecPtr  lsn = record->EndRecPtr;
837
0
  xl_hash_delete *xldata = (xl_hash_delete *) XLogRecGetData(record);
838
0
  Buffer    bucketbuf = InvalidBuffer;
839
0
  Buffer    deletebuf;
840
0
  Page    page;
841
0
  XLogRedoAction action;
842
843
  /*
844
   * Ensure we have a cleanup lock on primary bucket page before we start
845
   * with the actual replay operation.  This is to ensure that neither a
846
   * scan can start nor a scan can be already-in-progress during the replay
847
   * of this operation.  If we allow scans during this operation, then they
848
   * can miss some records or show the same record multiple times.
849
   */
850
0
  if (xldata->is_primary_bucket_page)
851
0
    action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &deletebuf);
852
0
  else
853
0
  {
854
    /*
855
     * we don't care for return value as the purpose of reading bucketbuf
856
     * is to ensure a cleanup lock on primary bucket page.
857
     */
858
0
    (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf);
859
860
0
    action = XLogReadBufferForRedo(record, 1, &deletebuf);
861
0
  }
862
863
  /* replay the record for deleting entries in bucket page */
864
0
  if (action == BLK_NEEDS_REDO)
865
0
  {
866
0
    char     *ptr;
867
0
    Size    len;
868
869
0
    ptr = XLogRecGetBlockData(record, 1, &len);
870
871
0
    page = BufferGetPage(deletebuf);
872
873
0
    if (len > 0)
874
0
    {
875
0
      OffsetNumber *unused;
876
0
      OffsetNumber *unend;
877
878
0
      unused = (OffsetNumber *) ptr;
879
0
      unend = (OffsetNumber *) (ptr + len);
880
881
0
      if ((unend - unused) > 0)
882
0
        PageIndexMultiDelete(page, unused, unend - unused);
883
0
    }
884
885
    /*
886
     * Mark the page as not containing any LP_DEAD items only if
887
     * clear_dead_marking flag is set to true. See comments in
888
     * hashbucketcleanup() for details.
889
     */
890
0
    if (xldata->clear_dead_marking)
891
0
    {
892
0
      HashPageOpaque pageopaque;
893
894
0
      pageopaque = HashPageGetOpaque(page);
895
0
      pageopaque->hasho_flag &= ~LH_PAGE_HAS_DEAD_TUPLES;
896
0
    }
897
898
0
    PageSetLSN(page, lsn);
899
0
    MarkBufferDirty(deletebuf);
900
0
  }
901
0
  if (BufferIsValid(deletebuf))
902
0
    UnlockReleaseBuffer(deletebuf);
903
904
0
  if (BufferIsValid(bucketbuf))
905
0
    UnlockReleaseBuffer(bucketbuf);
906
0
}
907
908
/*
909
 * replay split cleanup flag operation for primary bucket page.
910
 */
911
static void
912
hash_xlog_split_cleanup(XLogReaderState *record)
913
0
{
914
0
  XLogRecPtr  lsn = record->EndRecPtr;
915
0
  Buffer    buffer;
916
0
  Page    page;
917
918
0
  if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
919
0
  {
920
0
    HashPageOpaque bucket_opaque;
921
922
0
    page = BufferGetPage(buffer);
923
924
0
    bucket_opaque = HashPageGetOpaque(page);
925
0
    bucket_opaque->hasho_flag &= ~LH_BUCKET_NEEDS_SPLIT_CLEANUP;
926
0
    PageSetLSN(page, lsn);
927
0
    MarkBufferDirty(buffer);
928
0
  }
929
0
  if (BufferIsValid(buffer))
930
0
    UnlockReleaseBuffer(buffer);
931
0
}
932
933
/*
934
 * replay for update meta page
935
 */
936
static void
937
hash_xlog_update_meta_page(XLogReaderState *record)
938
0
{
939
0
  HashMetaPage metap;
940
0
  XLogRecPtr  lsn = record->EndRecPtr;
941
0
  xl_hash_update_meta_page *xldata = (xl_hash_update_meta_page *) XLogRecGetData(record);
942
0
  Buffer    metabuf;
943
0
  Page    page;
944
945
0
  if (XLogReadBufferForRedo(record, 0, &metabuf) == BLK_NEEDS_REDO)
946
0
  {
947
0
    page = BufferGetPage(metabuf);
948
0
    metap = HashPageGetMeta(page);
949
950
0
    metap->hashm_ntuples = xldata->ntuples;
951
952
0
    PageSetLSN(page, lsn);
953
0
    MarkBufferDirty(metabuf);
954
0
  }
955
0
  if (BufferIsValid(metabuf))
956
0
    UnlockReleaseBuffer(metabuf);
957
0
}
958
959
/*
960
 * replay delete operation in hash index to remove
961
 * tuples marked as DEAD during index tuple insertion.
962
 */
963
static void
964
hash_xlog_vacuum_one_page(XLogReaderState *record)
965
0
{
966
0
  XLogRecPtr  lsn = record->EndRecPtr;
967
0
  xl_hash_vacuum_one_page *xldata;
968
0
  Buffer    buffer;
969
0
  Buffer    metabuf;
970
0
  Page    page;
971
0
  XLogRedoAction action;
972
0
  HashPageOpaque pageopaque;
973
0
  OffsetNumber *toDelete;
974
975
0
  xldata = (xl_hash_vacuum_one_page *) XLogRecGetData(record);
976
0
  toDelete = xldata->offsets;
977
978
  /*
979
   * If we have any conflict processing to do, it must happen before we
980
   * update the page.
981
   *
982
   * Hash index records that are marked as LP_DEAD and being removed during
983
   * hash index tuple insertion can conflict with standby queries. You might
984
   * think that vacuum records would conflict as well, but we've handled
985
   * that already.  XLOG_HEAP2_PRUNE_VACUUM_SCAN records provide the highest
986
   * xid cleaned by the vacuum of the heap and so we can resolve any
987
   * conflicts just once when that arrives.  After that we know that no
988
   * conflicts exist from individual hash index vacuum records on that
989
   * index.
990
   */
991
0
  if (InHotStandby)
992
0
  {
993
0
    RelFileLocator rlocator;
994
995
0
    XLogRecGetBlockTag(record, 0, &rlocator, NULL, NULL);
996
0
    ResolveRecoveryConflictWithSnapshot(xldata->snapshotConflictHorizon,
997
0
                      xldata->isCatalogRel,
998
0
                      rlocator);
999
0
  }
1000
1001
0
  action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &buffer);
1002
1003
0
  if (action == BLK_NEEDS_REDO)
1004
0
  {
1005
0
    page = BufferGetPage(buffer);
1006
1007
0
    PageIndexMultiDelete(page, toDelete, xldata->ntuples);
1008
1009
    /*
1010
     * Mark the page as not containing any LP_DEAD items. See comments in
1011
     * _hash_vacuum_one_page() for details.
1012
     */
1013
0
    pageopaque = HashPageGetOpaque(page);
1014
0
    pageopaque->hasho_flag &= ~LH_PAGE_HAS_DEAD_TUPLES;
1015
1016
0
    PageSetLSN(page, lsn);
1017
0
    MarkBufferDirty(buffer);
1018
0
  }
1019
0
  if (BufferIsValid(buffer))
1020
0
    UnlockReleaseBuffer(buffer);
1021
1022
0
  if (XLogReadBufferForRedo(record, 1, &metabuf) == BLK_NEEDS_REDO)
1023
0
  {
1024
0
    Page    metapage;
1025
0
    HashMetaPage metap;
1026
1027
0
    metapage = BufferGetPage(metabuf);
1028
0
    metap = HashPageGetMeta(metapage);
1029
1030
0
    metap->hashm_ntuples -= xldata->ntuples;
1031
1032
0
    PageSetLSN(metapage, lsn);
1033
0
    MarkBufferDirty(metabuf);
1034
0
  }
1035
0
  if (BufferIsValid(metabuf))
1036
0
    UnlockReleaseBuffer(metabuf);
1037
0
}
1038
1039
void
1040
hash_redo(XLogReaderState *record)
1041
0
{
1042
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
1043
1044
0
  switch (info)
1045
0
  {
1046
0
    case XLOG_HASH_INIT_META_PAGE:
1047
0
      hash_xlog_init_meta_page(record);
1048
0
      break;
1049
0
    case XLOG_HASH_INIT_BITMAP_PAGE:
1050
0
      hash_xlog_init_bitmap_page(record);
1051
0
      break;
1052
0
    case XLOG_HASH_INSERT:
1053
0
      hash_xlog_insert(record);
1054
0
      break;
1055
0
    case XLOG_HASH_ADD_OVFL_PAGE:
1056
0
      hash_xlog_add_ovfl_page(record);
1057
0
      break;
1058
0
    case XLOG_HASH_SPLIT_ALLOCATE_PAGE:
1059
0
      hash_xlog_split_allocate_page(record);
1060
0
      break;
1061
0
    case XLOG_HASH_SPLIT_PAGE:
1062
0
      hash_xlog_split_page(record);
1063
0
      break;
1064
0
    case XLOG_HASH_SPLIT_COMPLETE:
1065
0
      hash_xlog_split_complete(record);
1066
0
      break;
1067
0
    case XLOG_HASH_MOVE_PAGE_CONTENTS:
1068
0
      hash_xlog_move_page_contents(record);
1069
0
      break;
1070
0
    case XLOG_HASH_SQUEEZE_PAGE:
1071
0
      hash_xlog_squeeze_page(record);
1072
0
      break;
1073
0
    case XLOG_HASH_DELETE:
1074
0
      hash_xlog_delete(record);
1075
0
      break;
1076
0
    case XLOG_HASH_SPLIT_CLEANUP:
1077
0
      hash_xlog_split_cleanup(record);
1078
0
      break;
1079
0
    case XLOG_HASH_UPDATE_META_PAGE:
1080
0
      hash_xlog_update_meta_page(record);
1081
0
      break;
1082
0
    case XLOG_HASH_VACUUM_ONE_PAGE:
1083
0
      hash_xlog_vacuum_one_page(record);
1084
0
      break;
1085
0
    default:
1086
0
      elog(PANIC, "hash_redo: unknown op code %u", info);
1087
0
  }
1088
0
}
1089
1090
/*
1091
 * Mask a hash page before performing consistency checks on it.
1092
 */
1093
void
1094
hash_mask(char *pagedata, BlockNumber blkno)
1095
0
{
1096
0
  Page    page = (Page) pagedata;
1097
0
  HashPageOpaque opaque;
1098
0
  int     pagetype;
1099
1100
0
  mask_page_lsn_and_checksum(page);
1101
1102
0
  mask_page_hint_bits(page);
1103
0
  mask_unused_space(page);
1104
1105
0
  opaque = HashPageGetOpaque(page);
1106
1107
0
  pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
1108
0
  if (pagetype == LH_UNUSED_PAGE)
1109
0
  {
1110
    /*
1111
     * Mask everything on a UNUSED page.
1112
     */
1113
0
    mask_page_content(page);
1114
0
  }
1115
0
  else if (pagetype == LH_BUCKET_PAGE ||
1116
0
       pagetype == LH_OVERFLOW_PAGE)
1117
0
  {
1118
    /*
1119
     * In hash bucket and overflow pages, it is possible to modify the
1120
     * LP_FLAGS without emitting any WAL record. Hence, mask the line
1121
     * pointer flags. See hashgettuple(), _hash_kill_items() for details.
1122
     */
1123
0
    mask_lp_flags(page);
1124
0
  }
1125
1126
  /*
1127
   * It is possible that the hint bit LH_PAGE_HAS_DEAD_TUPLES may remain
1128
   * unlogged. So, mask it. See _hash_kill_items() for details.
1129
   */
1130
0
  opaque->hasho_flag &= ~LH_PAGE_HAS_DEAD_TUPLES;
1131
0
}