/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 | } |