Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * heapam_xlog.c
4 : : * WAL replay logic for heap access method.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/heap/heapam_xlog.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include "access/bufmask.h"
18 : : #include "access/heapam.h"
19 : : #include "access/visibilitymap.h"
20 : : #include "access/xlog.h"
21 : : #include "access/xlogutils.h"
22 : : #include "storage/freespace.h"
23 : : #include "storage/standby.h"
24 : :
25 : :
26 : : /*
27 : : * Replay XLOG_HEAP2_PRUNE_* records.
28 : : */
29 : : static void
600 michael@paquier.xyz 30 :CBC 20083 : heap_xlog_prune_freeze(XLogReaderState *record)
31 : : {
32 : 20083 : XLogRecPtr lsn = record->EndRecPtr;
33 : 20083 : char *maindataptr = XLogRecGetData(record);
34 : : xl_heap_prune xlrec;
35 : : Buffer buffer;
36 : : RelFileLocator rlocator;
37 : : BlockNumber blkno;
204 melanieplageman@gmai 38 :GNC 20083 : Buffer vmbuffer = InvalidBuffer;
39 : 20083 : uint8 vmflags = 0;
40 : 20083 : Size freespace = 0;
19 41 : 20083 : bool do_update_fsm = false;
42 : :
600 michael@paquier.xyz 43 :CBC 20083 : XLogRecGetBlockTag(record, 0, &rlocator, NULL, &blkno);
44 : 20083 : memcpy(&xlrec, maindataptr, SizeOfHeapPrune);
45 : 20083 : maindataptr += SizeOfHeapPrune;
46 : :
47 : : /*
48 : : * We will take an ordinary exclusive lock or a cleanup lock depending on
49 : : * whether the XLHP_CLEANUP_LOCK flag is set. With an ordinary exclusive
50 : : * lock, we better not be doing anything that requires moving existing
51 : : * tuple data.
52 : : */
53 [ + + - + ]: 20083 : Assert((xlrec.flags & XLHP_CLEANUP_LOCK) != 0 ||
54 : : (xlrec.flags & (XLHP_HAS_REDIRECTIONS | XLHP_HAS_DEAD_ITEMS)) == 0);
55 : :
204 melanieplageman@gmai 56 [ + + ]:GNC 20083 : if (xlrec.flags & XLHP_VM_ALL_VISIBLE)
57 : : {
58 : 10211 : vmflags = VISIBILITYMAP_ALL_VISIBLE;
59 [ + + ]: 10211 : if (xlrec.flags & XLHP_VM_ALL_FROZEN)
60 : 6226 : vmflags |= VISIBILITYMAP_ALL_FROZEN;
61 : : }
62 : :
63 : : /*
64 : : * After xl_heap_prune is the optional snapshot conflict horizon.
65 : : *
66 : : * In Hot Standby mode, we must ensure that there are no running queries
67 : : * which would conflict with the changes in this record. That means we
68 : : * can't replay this record if it removes tuples that are still visible to
69 : : * transactions on the standby, freeze tuples with xids that are still
70 : : * considered running on the standby, or set a page as all-visible in the
71 : : * VM if it isn't all-visible to all transactions on the standby.
72 : : */
600 michael@paquier.xyz 73 [ + + ]:CBC 20083 : if ((xlrec.flags & XLHP_HAS_CONFLICT_HORIZON) != 0)
74 : : {
75 : : TransactionId snapshot_conflict_horizon;
76 : :
77 : : /* memcpy() because snapshot_conflict_horizon is stored unaligned */
78 : 15755 : memcpy(&snapshot_conflict_horizon, maindataptr, sizeof(TransactionId));
79 : 15755 : maindataptr += sizeof(TransactionId);
80 : :
81 [ + + ]: 15755 : if (InHotStandby)
82 : 15305 : ResolveRecoveryConflictWithSnapshot(snapshot_conflict_horizon,
83 : 15305 : (xlrec.flags & XLHP_IS_CATALOG_REL) != 0,
84 : : rlocator);
85 : : }
86 : :
87 : : /*
88 : : * If we have a full-page image of the heap block, restore it and we're
89 : : * done with the heap block.
90 : : */
204 melanieplageman@gmai 91 [ + + ]:GNC 20083 : if (XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL,
92 : 20083 : (xlrec.flags & XLHP_CLEANUP_LOCK) != 0,
93 : : &buffer) == BLK_NEEDS_REDO)
94 : : {
249 peter@eisentraut.org 95 : 14205 : Page page = BufferGetPage(buffer);
96 : : OffsetNumber *redirected;
97 : : OffsetNumber *nowdead;
98 : : OffsetNumber *nowunused;
99 : : int nredirected;
100 : : int ndead;
101 : : int nunused;
102 : : int nplans;
103 : : Size datalen;
104 : : xlhp_freeze_plan *plans;
105 : : OffsetNumber *frz_offsets;
600 michael@paquier.xyz 106 :CBC 14205 : char *dataptr = XLogRecGetBlockData(record, 0, &datalen);
107 : : bool do_prune;
108 : :
109 : 14205 : heap_xlog_deserialize_prune_and_freeze(dataptr, xlrec.flags,
110 : : &nplans, &plans, &frz_offsets,
111 : : &nredirected, &redirected,
112 : : &ndead, &nowdead,
113 : : &nunused, &nowunused);
114 : :
204 melanieplageman@gmai 115 [ + + + + :GNC 14205 : do_prune = nredirected > 0 || ndead > 0 || nunused > 0;
+ + ]
116 : :
117 : : /* Ensure the record does something */
118 [ + + + + : 14205 : Assert(do_prune || nplans > 0 || vmflags & VISIBILITYMAP_VALID_BITS);
- + ]
119 : :
120 : : /*
121 : : * Update all line pointers per the record, and repair fragmentation
122 : : * if needed.
123 : : */
124 [ + + ]: 14205 : if (do_prune)
600 michael@paquier.xyz 125 :CBC 11150 : heap_page_prune_execute(buffer,
126 : 11150 : (xlrec.flags & XLHP_CLEANUP_LOCK) == 0,
127 : : redirected, nredirected,
128 : : nowdead, ndead,
129 : : nowunused, nunused);
130 : :
131 : : /* Freeze tuples */
132 [ + + ]: 15984 : for (int p = 0; p < nplans; p++)
133 : : {
134 : : HeapTupleFreeze frz;
135 : :
136 : : /*
137 : : * Convert freeze plan representation from WAL record into
138 : : * per-tuple format used by heap_execute_freeze_tuple
139 : : */
140 : 1779 : frz.xmax = plans[p].xmax;
141 : 1779 : frz.t_infomask2 = plans[p].t_infomask2;
142 : 1779 : frz.t_infomask = plans[p].t_infomask;
143 : 1779 : frz.frzflags = plans[p].frzflags;
144 : 1779 : frz.offset = InvalidOffsetNumber; /* unused, but be tidy */
145 : :
146 [ + + ]: 87571 : for (int i = 0; i < plans[p].ntuples; i++)
147 : : {
148 : 85792 : OffsetNumber offset = *(frz_offsets++);
149 : : ItemId lp;
150 : : HeapTupleHeader tuple;
151 : :
152 : 85792 : lp = PageGetItemId(page, offset);
153 : 85792 : tuple = (HeapTupleHeader) PageGetItem(page, lp);
154 : 85792 : heap_execute_freeze_tuple(tuple, &frz);
155 : : }
156 : : }
157 : :
158 : : /* There should be no more data */
159 [ - + ]: 14205 : Assert((char *) frz_offsets == dataptr + datalen);
160 : :
161 : : /*
162 : : * The critical integrity requirement here is that we must never end
163 : : * up with the visibility map bit set and the page-level
164 : : * PD_ALL_VISIBLE bit unset. If that were to occur, a subsequent page
165 : : * modification would fail to clear the visibility map bit.
166 : : */
204 melanieplageman@gmai 167 [ + + ]:GNC 14205 : if (vmflags & VISIBILITYMAP_VALID_BITS)
168 : : {
169 : 6223 : PageSetAllVisible(page);
64 170 : 6223 : PageClearPrunable(page);
171 : : }
172 : :
204 173 : 14205 : MarkBufferDirty(buffer);
174 : :
175 : : /*
176 : : * See log_heap_prune_and_freeze() for commentary on when we set the
177 : : * heap page LSN.
178 : : */
179 [ + + + + ]: 14205 : if (do_prune || nplans > 0 ||
180 [ + - + + : 1906 : ((vmflags & VISIBILITYMAP_VALID_BITS) && XLogHintBitIsNeeded()))
+ - ]
204 melanieplageman@gmai 181 :CBC 14205 : PageSetLSN(page, lsn);
182 : :
183 : : /*
184 : : * Note: we don't worry about updating the page's prunability hints.
185 : : * At worst this will cause an extra prune cycle to occur soon.
186 : : */
187 : : }
188 : :
189 : : /*
190 : : * If we 1) released any space or line pointers or 2) set PD_ALL_VISIBLE
191 : : * or the VM, update the freespace map.
192 : : *
193 : : * Even when no actual space is freed (when only marking the page
194 : : * all-visible or frozen), we still update the FSM. Because the FSM is
195 : : * unlogged and maintained heuristically, it often becomes stale on
196 : : * standbys. If such a standby is later promoted and runs VACUUM, it will
197 : : * skip recalculating free space for pages that were marked
198 : : * all-visible/all-frozen. FreeSpaceMapVacuum() can then propagate overly
199 : : * optimistic free space values upward, causing future insertions to
200 : : * select pages that turn out to be unusable. In bulk, this can lead to
201 : : * long stalls.
202 : : *
203 : : * To prevent this, always update the FSM even when only marking a page
204 : : * all-visible/all-frozen.
205 : : *
206 : : * Do this regardless of whether a full-page image is logged, since FSM
207 : : * data is not part of the page itself.
208 : : */
600 michael@paquier.xyz 209 [ + - ]: 20083 : if (BufferIsValid(buffer))
210 : : {
204 melanieplageman@gmai 211 [ + + ]:GNC 20083 : if ((xlrec.flags & (XLHP_HAS_REDIRECTIONS |
212 : : XLHP_HAS_DEAD_ITEMS |
213 : 5847 : XLHP_HAS_NOW_UNUSED_ITEMS)) ||
214 [ + + ]: 5847 : (vmflags & VISIBILITYMAP_VALID_BITS))
215 : : {
216 : 20065 : freespace = PageGetHeapFreeSpace(BufferGetPage(buffer));
19 217 : 20065 : do_update_fsm = true;
218 : : }
219 : :
220 : : /*
221 : : * We want to avoid holding an exclusive lock on the heap buffer while
222 : : * doing IO (either of the FSM or the VM), so we'll release it now.
223 : : */
204 224 : 20083 : UnlockReleaseBuffer(buffer);
225 : : }
226 : :
227 : : /*
228 : : * Now read and update the VM block.
229 : : *
230 : : * We must redo changes to the VM even if the heap page was skipped due to
231 : : * LSN interlock. See comment in heap_xlog_multi_insert() for more details
232 : : * on replaying changes to the VM.
233 : : */
234 [ + + + + ]: 30294 : if ((vmflags & VISIBILITYMAP_VALID_BITS) &&
235 : 10211 : XLogReadBufferForRedoExtended(record, 1,
236 : : RBM_ZERO_ON_ERROR,
237 : : false,
238 : : &vmbuffer) == BLK_NEEDS_REDO)
239 : : {
204 melanieplageman@gmai 240 :CBC 9843 : Page vmpage = BufferGetPage(vmbuffer);
241 : :
242 : : /* initialize the page if it was read as zeros */
243 [ + + ]: 9843 : if (PageIsNew(vmpage))
204 melanieplageman@gmai 244 :GBC 2 : PageInit(vmpage, BLCKSZ, 0);
245 : :
42 melanieplageman@gmai 246 :GNC 9843 : visibilitymap_set(blkno, vmbuffer, vmflags, rlocator);
247 : :
204 248 [ - + ]: 9843 : Assert(BufferIsDirty(vmbuffer));
249 : 9843 : PageSetLSN(vmpage, lsn);
250 : : }
251 : :
252 [ + + ]: 20083 : if (BufferIsValid(vmbuffer))
204 melanieplageman@gmai 253 :CBC 10211 : UnlockReleaseBuffer(vmbuffer);
254 : :
19 melanieplageman@gmai 255 [ + + ]:GNC 20083 : if (do_update_fsm)
204 256 : 20065 : XLogRecordPageWithFreeSpace(rlocator, blkno, freespace);
600 michael@paquier.xyz 257 :CBC 20083 : }
258 : :
259 : : /*
260 : : * Given an "infobits" field from an XLog record, set the correct bits in the
261 : : * given infomask and infomask2 for the tuple touched by the record.
262 : : *
263 : : * (This is the reverse of compute_infobits).
264 : : */
265 : : static void
266 : 467848 : fix_infomask_from_infobits(uint8 infobits, uint16 *infomask, uint16 *infomask2)
267 : : {
268 : 467848 : *infomask &= ~(HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY |
269 : : HEAP_XMAX_KEYSHR_LOCK | HEAP_XMAX_EXCL_LOCK);
270 : 467848 : *infomask2 &= ~HEAP_KEYS_UPDATED;
271 : :
272 [ + + ]: 467848 : if (infobits & XLHL_XMAX_IS_MULTI)
273 : 3 : *infomask |= HEAP_XMAX_IS_MULTI;
274 [ + + ]: 467848 : if (infobits & XLHL_XMAX_LOCK_ONLY)
275 : 55993 : *infomask |= HEAP_XMAX_LOCK_ONLY;
276 [ + + ]: 467848 : if (infobits & XLHL_XMAX_EXCL_LOCK)
277 : 55226 : *infomask |= HEAP_XMAX_EXCL_LOCK;
278 : : /* note HEAP_XMAX_SHR_LOCK isn't considered here */
279 [ + + ]: 467848 : if (infobits & XLHL_XMAX_KEYSHR_LOCK)
280 : 781 : *infomask |= HEAP_XMAX_KEYSHR_LOCK;
281 : :
282 [ + + ]: 467848 : if (infobits & XLHL_KEYS_UPDATED)
283 : 317206 : *infomask2 |= HEAP_KEYS_UPDATED;
284 : 467848 : }
285 : :
286 : : /*
287 : : * Replay XLOG_HEAP_DELETE records.
288 : : */
289 : : static void
290 : 317925 : heap_xlog_delete(XLogReaderState *record)
291 : : {
292 : 317925 : XLogRecPtr lsn = record->EndRecPtr;
293 : 317925 : xl_heap_delete *xlrec = (xl_heap_delete *) XLogRecGetData(record);
294 : : Buffer buffer;
295 : : Page page;
296 : : ItemId lp;
297 : : HeapTupleHeader htup;
298 : : BlockNumber blkno;
299 : : RelFileLocator target_locator;
300 : : ItemPointerData target_tid;
301 : :
302 : 317925 : XLogRecGetBlockTag(record, 0, &target_locator, NULL, &blkno);
303 : 317925 : ItemPointerSetBlockNumber(&target_tid, blkno);
304 : 317925 : ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum);
305 : :
306 : : /*
307 : : * The visibility map may need to be fixed even if the heap page is
308 : : * already up-to-date.
309 : : */
310 [ + + ]: 317925 : if (xlrec->flags & XLH_DELETE_ALL_VISIBLE_CLEARED)
311 : : {
312 : 51 : Relation reln = CreateFakeRelcacheEntry(target_locator);
313 : 51 : Buffer vmbuffer = InvalidBuffer;
314 : :
315 : 51 : visibilitymap_pin(reln, blkno, &vmbuffer);
316 : 51 : visibilitymap_clear(reln, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS);
317 : 51 : ReleaseBuffer(vmbuffer);
318 : 51 : FreeFakeRelcacheEntry(reln);
319 : : }
320 : :
321 [ + + ]: 317925 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
322 : : {
323 : 315736 : page = BufferGetPage(buffer);
324 : :
141 tgl@sss.pgh.pa.us 325 [ + - - + ]:GNC 315736 : if (xlrec->offnum < 1 || xlrec->offnum > PageGetMaxOffsetNumber(page))
141 tgl@sss.pgh.pa.us 326 [ # # ]:UNC 0 : elog(PANIC, "offnum out of range");
141 tgl@sss.pgh.pa.us 327 :GNC 315736 : lp = PageGetItemId(page, xlrec->offnum);
328 [ - + ]: 315736 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 329 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
330 : :
600 michael@paquier.xyz 331 :CBC 315736 : htup = (HeapTupleHeader) PageGetItem(page, lp);
332 : :
333 : 315736 : htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
334 : 315736 : htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
335 : 315736 : HeapTupleHeaderClearHotUpdated(htup);
336 : 315736 : fix_infomask_from_infobits(xlrec->infobits_set,
337 : : &htup->t_infomask, &htup->t_infomask2);
338 [ + - ]: 315736 : if (!(xlrec->flags & XLH_DELETE_IS_SUPER))
339 : 315736 : HeapTupleHeaderSetXmax(htup, xlrec->xmax);
340 : : else
600 michael@paquier.xyz 341 :UBC 0 : HeapTupleHeaderSetXmin(htup, InvalidTransactionId);
600 michael@paquier.xyz 342 :CBC 315736 : HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
343 : :
344 : : /* Mark the page as a candidate for pruning */
345 [ - + + + : 315736 : PageSetPrunable(page, XLogRecGetXid(record));
+ + ]
346 : :
347 [ + + ]: 315736 : if (xlrec->flags & XLH_DELETE_ALL_VISIBLE_CLEARED)
348 : 18 : PageClearAllVisible(page);
349 : :
350 : : /* Make sure t_ctid is set correctly */
351 [ + + ]: 315736 : if (xlrec->flags & XLH_DELETE_IS_PARTITION_MOVE)
352 : 152 : HeapTupleHeaderSetMovedPartitions(htup);
353 : : else
354 : 315584 : htup->t_ctid = target_tid;
355 : 315736 : PageSetLSN(page, lsn);
356 : 315736 : MarkBufferDirty(buffer);
357 : : }
358 [ + - ]: 317925 : if (BufferIsValid(buffer))
359 : 317925 : UnlockReleaseBuffer(buffer);
360 : 317925 : }
361 : :
362 : : /*
363 : : * Replay XLOG_HEAP_INSERT records.
364 : : */
365 : : static void
366 : 1322456 : heap_xlog_insert(XLogReaderState *record)
367 : : {
368 : 1322456 : XLogRecPtr lsn = record->EndRecPtr;
369 : 1322456 : xl_heap_insert *xlrec = (xl_heap_insert *) XLogRecGetData(record);
370 : : Buffer buffer;
371 : : Page page;
372 : : union
373 : : {
374 : : HeapTupleHeaderData hdr;
375 : : char data[MaxHeapTupleSize];
376 : : } tbuf;
377 : : HeapTupleHeader htup;
378 : : xl_heap_header xlhdr;
379 : : uint32 newlen;
380 : 1322456 : Size freespace = 0;
381 : : RelFileLocator target_locator;
382 : : BlockNumber blkno;
383 : : ItemPointerData target_tid;
384 : : XLogRedoAction action;
385 : :
386 : 1322456 : XLogRecGetBlockTag(record, 0, &target_locator, NULL, &blkno);
387 : 1322456 : ItemPointerSetBlockNumber(&target_tid, blkno);
388 : 1322456 : ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum);
389 : :
390 : : /* No freezing in the heap_insert() code path */
313 melanieplageman@gmai 391 [ - + ]: 1322456 : Assert(!(xlrec->flags & XLH_INSERT_ALL_FROZEN_SET));
392 : :
393 : : /*
394 : : * The visibility map may need to be fixed even if the heap page is
395 : : * already up-to-date.
396 : : */
600 michael@paquier.xyz 397 [ + + ]: 1322456 : if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
398 : : {
399 : 1059 : Relation reln = CreateFakeRelcacheEntry(target_locator);
400 : 1059 : Buffer vmbuffer = InvalidBuffer;
401 : :
402 : 1059 : visibilitymap_pin(reln, blkno, &vmbuffer);
403 : 1059 : visibilitymap_clear(reln, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS);
404 : 1059 : ReleaseBuffer(vmbuffer);
405 : 1059 : FreeFakeRelcacheEntry(reln);
406 : : }
407 : :
408 : : /*
409 : : * If we inserted the first and only tuple on the page, re-initialize the
410 : : * page from scratch.
411 : : */
412 [ + + ]: 1322456 : if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
413 : : {
414 : 17707 : buffer = XLogInitBufferForRedo(record, 0);
415 : 17707 : page = BufferGetPage(buffer);
416 : 17707 : PageInit(page, BufferGetPageSize(buffer), 0);
417 : 17707 : action = BLK_NEEDS_REDO;
418 : : }
419 : : else
420 : 1304749 : action = XLogReadBufferForRedo(record, 0, &buffer);
421 [ + + ]: 1322456 : if (action == BLK_NEEDS_REDO)
422 : : {
423 : : Size datalen;
424 : : char *data;
425 : :
426 : 1319368 : page = BufferGetPage(buffer);
427 : :
428 [ - + ]: 1319368 : if (PageGetMaxOffsetNumber(page) + 1 < xlrec->offnum)
600 michael@paquier.xyz 429 [ # # ]:UBC 0 : elog(PANIC, "invalid max offset number");
430 : :
600 michael@paquier.xyz 431 :CBC 1319368 : data = XLogRecGetBlockData(record, 0, &datalen);
432 : :
433 : 1319368 : newlen = datalen - SizeOfHeapHeader;
434 [ + - - + ]: 1319368 : Assert(datalen > SizeOfHeapHeader && newlen <= MaxHeapTupleSize);
447 peter@eisentraut.org 435 : 1319368 : memcpy(&xlhdr, data, SizeOfHeapHeader);
600 michael@paquier.xyz 436 : 1319368 : data += SizeOfHeapHeader;
437 : :
438 : 1319368 : htup = &tbuf.hdr;
447 peter@eisentraut.org 439 [ + - - + : 1319368 : MemSet(htup, 0, SizeofHeapTupleHeader);
- - - - -
- ]
440 : : /* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
600 michael@paquier.xyz 441 : 1319368 : memcpy((char *) htup + SizeofHeapTupleHeader,
442 : : data,
443 : : newlen);
444 : 1319368 : newlen += SizeofHeapTupleHeader;
445 : 1319368 : htup->t_infomask2 = xlhdr.t_infomask2;
446 : 1319368 : htup->t_infomask = xlhdr.t_infomask;
447 : 1319368 : htup->t_hoff = xlhdr.t_hoff;
448 : 1319368 : HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
449 : 1319368 : HeapTupleHeaderSetCmin(htup, FirstCommandId);
450 : 1319368 : htup->t_ctid = target_tid;
451 : :
190 peter@eisentraut.org 452 [ - + ]:GNC 1319368 : if (PageAddItem(page, htup, newlen, xlrec->offnum, true, true) == InvalidOffsetNumber)
600 michael@paquier.xyz 453 [ # # ]:UBC 0 : elog(PANIC, "failed to add tuple");
454 : :
600 michael@paquier.xyz 455 :CBC 1319368 : freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
456 : :
457 : : /*
458 : : * Set the page prunable to trigger on-access pruning later, which may
459 : : * set the page all-visible in the VM. See comments in heap_insert().
460 : : */
36 melanieplageman@gmai 461 [ + - ]:GNC 1319368 : if (TransactionIdIsNormal(XLogRecGetXid(record)) &&
462 [ + + ]: 1319368 : !HeapTupleHeaderXminFrozen(htup))
463 [ - + + + : 1318838 : PageSetPrunable(page, XLogRecGetXid(record));
+ + ]
464 : :
600 michael@paquier.xyz 465 :CBC 1319368 : PageSetLSN(page, lsn);
466 : :
467 [ + + ]: 1319368 : if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
468 : 310 : PageClearAllVisible(page);
469 : :
470 : 1319368 : MarkBufferDirty(buffer);
471 : : }
472 [ + - ]: 1322456 : if (BufferIsValid(buffer))
473 : 1322456 : UnlockReleaseBuffer(buffer);
474 : :
475 : : /*
476 : : * If the page is running low on free space, update the FSM as well.
477 : : * Arbitrarily, our definition of "low" is less than 20%. We can't do much
478 : : * better than that without knowing the fill-factor for the table.
479 : : *
480 : : * XXX: Don't do this if the page was restored from full page image. We
481 : : * don't bother to update the FSM in that case, it doesn't need to be
482 : : * totally accurate anyway.
483 : : */
484 [ + + + + ]: 1322456 : if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5)
485 : 259110 : XLogRecordPageWithFreeSpace(target_locator, blkno, freespace);
486 : 1322456 : }
487 : :
488 : : /*
489 : : * Replay XLOG_HEAP2_MULTI_INSERT records.
490 : : */
491 : : static void
492 : 67279 : heap_xlog_multi_insert(XLogReaderState *record)
493 : : {
494 : 67279 : XLogRecPtr lsn = record->EndRecPtr;
495 : : xl_heap_multi_insert *xlrec;
496 : : RelFileLocator rlocator;
497 : : BlockNumber blkno;
498 : : Buffer buffer;
499 : : Page page;
500 : : union
501 : : {
502 : : HeapTupleHeaderData hdr;
503 : : char data[MaxHeapTupleSize];
504 : : } tbuf;
505 : : HeapTupleHeader htup;
506 : : uint32 newlen;
507 : 67279 : Size freespace = 0;
508 : : int i;
509 : 67279 : bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
510 : : XLogRedoAction action;
208 melanieplageman@gmai 511 :GNC 67279 : Buffer vmbuffer = InvalidBuffer;
512 : :
513 : : /*
514 : : * Insertion doesn't overwrite MVCC data, so no conflict processing is
515 : : * required.
516 : : */
600 michael@paquier.xyz 517 :CBC 67279 : xlrec = (xl_heap_multi_insert *) XLogRecGetData(record);
518 : :
519 : 67279 : XLogRecGetBlockTag(record, 0, &rlocator, NULL, &blkno);
520 : :
521 : : /* check that the mutually exclusive flags are not both set */
522 [ + + - + ]: 67279 : Assert(!((xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) &&
523 : : (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)));
524 : :
525 : : /*
526 : : * The visibility map may need to be fixed even if the heap page is
527 : : * already up-to-date.
528 : : */
529 [ + + ]: 67279 : if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
530 : : {
531 : 1128 : Relation reln = CreateFakeRelcacheEntry(rlocator);
532 : :
533 : 1128 : visibilitymap_pin(reln, blkno, &vmbuffer);
534 : 1128 : visibilitymap_clear(reln, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS);
535 : 1128 : ReleaseBuffer(vmbuffer);
208 melanieplageman@gmai 536 :GNC 1128 : vmbuffer = InvalidBuffer;
600 michael@paquier.xyz 537 :CBC 1128 : FreeFakeRelcacheEntry(reln);
538 : : }
539 : :
540 [ + + ]: 67279 : if (isinit)
541 : : {
542 : 1865 : buffer = XLogInitBufferForRedo(record, 0);
543 : 1865 : page = BufferGetPage(buffer);
544 : 1865 : PageInit(page, BufferGetPageSize(buffer), 0);
545 : 1865 : action = BLK_NEEDS_REDO;
546 : : }
547 : : else
548 : 65414 : action = XLogReadBufferForRedo(record, 0, &buffer);
549 [ + + ]: 67279 : if (action == BLK_NEEDS_REDO)
550 : : {
551 : : char *tupdata;
552 : : char *endptr;
553 : : Size len;
554 : :
555 : : /* Tuples are stored as block data */
556 : 65457 : tupdata = XLogRecGetBlockData(record, 0, &len);
557 : 65457 : endptr = tupdata + len;
558 : :
249 peter@eisentraut.org 559 :GNC 65457 : page = BufferGetPage(buffer);
560 : :
600 michael@paquier.xyz 561 [ + + ]:CBC 287564 : for (i = 0; i < xlrec->ntuples; i++)
562 : : {
563 : : OffsetNumber offnum;
564 : : xl_multi_insert_tuple *xlhdr;
565 : :
566 : : /*
567 : : * If we're reinitializing the page, the tuples are stored in
568 : : * order from FirstOffsetNumber. Otherwise there's an array of
569 : : * offsets in the WAL record, and the tuples come after that.
570 : : */
571 [ + + ]: 222107 : if (isinit)
572 : 99643 : offnum = FirstOffsetNumber + i;
573 : : else
574 : 122464 : offnum = xlrec->offsets[i];
575 [ - + ]: 222107 : if (PageGetMaxOffsetNumber(page) + 1 < offnum)
600 michael@paquier.xyz 576 [ # # ]:UBC 0 : elog(PANIC, "invalid max offset number");
577 : :
600 michael@paquier.xyz 578 :CBC 222107 : xlhdr = (xl_multi_insert_tuple *) SHORTALIGN(tupdata);
579 : 222107 : tupdata = ((char *) xlhdr) + SizeOfMultiInsertTuple;
580 : :
581 : 222107 : newlen = xlhdr->datalen;
582 [ - + ]: 222107 : Assert(newlen <= MaxHeapTupleSize);
583 : 222107 : htup = &tbuf.hdr;
447 peter@eisentraut.org 584 [ + - - + : 222107 : MemSet(htup, 0, SizeofHeapTupleHeader);
- - - - -
- ]
585 : : /* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
600 michael@paquier.xyz 586 : 222107 : memcpy((char *) htup + SizeofHeapTupleHeader,
587 : : tupdata,
588 : : newlen);
589 : 222107 : tupdata += newlen;
590 : :
591 : 222107 : newlen += SizeofHeapTupleHeader;
592 : 222107 : htup->t_infomask2 = xlhdr->t_infomask2;
593 : 222107 : htup->t_infomask = xlhdr->t_infomask;
594 : 222107 : htup->t_hoff = xlhdr->t_hoff;
595 : 222107 : HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
596 : 222107 : HeapTupleHeaderSetCmin(htup, FirstCommandId);
597 : 222107 : ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
598 : 222107 : ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
599 : :
190 peter@eisentraut.org 600 :GNC 222107 : offnum = PageAddItem(page, htup, newlen, offnum, true, true);
600 michael@paquier.xyz 601 [ - + ]:CBC 222107 : if (offnum == InvalidOffsetNumber)
600 michael@paquier.xyz 602 [ # # ]:UBC 0 : elog(PANIC, "failed to add tuple");
603 : : }
600 michael@paquier.xyz 604 [ - + ]:CBC 65457 : if (tupdata != endptr)
600 michael@paquier.xyz 605 [ # # ]:UBC 0 : elog(PANIC, "total tuple length mismatch");
606 : :
600 michael@paquier.xyz 607 :CBC 65457 : freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
608 : :
609 : 65457 : PageSetLSN(page, lsn);
610 : :
611 [ + + ]: 65457 : if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
612 : 70 : PageClearAllVisible(page);
613 : :
614 : : /*
615 : : * XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible. If
616 : : * we are not setting the page frozen, then set the page's prunable
617 : : * hint so that we trigger on-access pruning later which may set the
618 : : * page all-visible in the VM.
619 : : */
620 [ + + ]: 65457 : if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
621 : : {
622 : 4 : PageSetAllVisible(page);
64 melanieplageman@gmai 623 :GNC 4 : PageClearPrunable(page);
624 : : }
625 : : else
36 626 [ - + + + : 65453 : PageSetPrunable(page, XLogRecGetXid(record));
+ + ]
627 : :
600 michael@paquier.xyz 628 :CBC 65457 : MarkBufferDirty(buffer);
629 : : }
630 [ + - ]: 67279 : if (BufferIsValid(buffer))
631 : 67279 : UnlockReleaseBuffer(buffer);
632 : :
208 melanieplageman@gmai 633 :GNC 67279 : buffer = InvalidBuffer;
634 : :
635 : : /*
636 : : * Read and update the visibility map (VM) block.
637 : : *
638 : : * We must always redo VM changes, even if the corresponding heap page
639 : : * update was skipped due to the LSN interlock. Each VM block covers
640 : : * multiple heap pages, so later WAL records may update other bits in the
641 : : * same block. If this record includes an FPI (full-page image),
642 : : * subsequent WAL records may depend on it to guard against torn pages.
643 : : *
644 : : * Heap page changes are replayed first to preserve the invariant:
645 : : * PD_ALL_VISIBLE must be set on the heap page if the VM bit is set.
646 : : *
647 : : * Note that we released the heap page lock above. During normal
648 : : * operation, this would be unsafe — a concurrent modification could
649 : : * clear PD_ALL_VISIBLE while the VM bit remained set, violating the
650 : : * invariant.
651 : : *
652 : : * During recovery, however, no concurrent writers exist. Therefore,
653 : : * updating the VM without holding the heap page lock is safe enough. This
654 : : * same approach is taken when replaying XLOG_HEAP2_PRUNE* records (see
655 : : * heap_xlog_prune_freeze()).
656 : : */
657 [ + + - + ]: 67283 : if ((xlrec->flags & XLH_INSERT_ALL_FROZEN_SET) &&
658 : 4 : XLogReadBufferForRedoExtended(record, 1, RBM_ZERO_ON_ERROR, false,
659 : : &vmbuffer) == BLK_NEEDS_REDO)
660 : : {
208 melanieplageman@gmai 661 :UNC 0 : Page vmpage = BufferGetPage(vmbuffer);
662 : :
663 : : /* initialize the page if it was read as zeros */
664 [ # # ]: 0 : if (PageIsNew(vmpage))
665 : 0 : PageInit(vmpage, BLCKSZ, 0);
666 : :
42 667 : 0 : visibilitymap_set(blkno,
668 : : vmbuffer,
669 : : VISIBILITYMAP_ALL_VISIBLE |
670 : : VISIBILITYMAP_ALL_FROZEN,
671 : : rlocator);
672 : :
204 673 [ # # ]: 0 : Assert(BufferIsDirty(vmbuffer));
208 674 : 0 : PageSetLSN(vmpage, lsn);
675 : : }
676 : :
208 melanieplageman@gmai 677 [ + + ]:GNC 67279 : if (BufferIsValid(vmbuffer))
678 : 4 : UnlockReleaseBuffer(vmbuffer);
679 : :
680 : : /*
681 : : * If the page is running low on free space, update the FSM as well.
682 : : * Arbitrarily, our definition of "low" is less than 20%. We can't do much
683 : : * better than that without knowing the fill-factor for the table.
684 : : *
685 : : * XXX: Don't do this if the page was restored from full page image. We
686 : : * don't bother to update the FSM in that case, it doesn't need to be
687 : : * totally accurate anyway.
688 : : */
600 michael@paquier.xyz 689 [ + + + + ]:CBC 67279 : if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5)
690 : 18018 : XLogRecordPageWithFreeSpace(rlocator, blkno, freespace);
691 : 67279 : }
692 : :
693 : : /*
694 : : * Replay XLOG_HEAP_UPDATE and XLOG_HEAP_HOT_UPDATE records.
695 : : */
696 : : static void
697 : 96449 : heap_xlog_update(XLogReaderState *record, bool hot_update)
698 : : {
699 : 96449 : XLogRecPtr lsn = record->EndRecPtr;
700 : 96449 : xl_heap_update *xlrec = (xl_heap_update *) XLogRecGetData(record);
701 : : RelFileLocator rlocator;
702 : : BlockNumber oldblk;
703 : : BlockNumber newblk;
704 : : ItemPointerData newtid;
705 : : Buffer obuffer,
706 : : nbuffer;
707 : : Page opage,
708 : : npage;
709 : : OffsetNumber offnum;
710 : : ItemId lp;
711 : : HeapTupleData oldtup;
712 : : HeapTupleHeader htup;
713 : 96449 : uint16 prefixlen = 0,
714 : 96449 : suffixlen = 0;
715 : : char *newp;
716 : : union
717 : : {
718 : : HeapTupleHeaderData hdr;
719 : : char data[MaxHeapTupleSize];
720 : : } tbuf;
721 : : xl_heap_header xlhdr;
722 : : uint32 newlen;
723 : 96449 : Size freespace = 0;
724 : : XLogRedoAction oldaction;
725 : : XLogRedoAction newaction;
726 : :
727 : : /* initialize to keep the compiler quiet */
728 : 96449 : oldtup.t_data = NULL;
729 : 96449 : oldtup.t_len = 0;
730 : :
731 : 96449 : XLogRecGetBlockTag(record, 0, &rlocator, NULL, &newblk);
732 [ + + ]: 96449 : if (XLogRecGetBlockTagExtended(record, 1, NULL, NULL, &oldblk, NULL))
733 : : {
734 : : /* HOT updates are never done across pages */
735 [ - + ]: 54902 : Assert(!hot_update);
736 : : }
737 : : else
738 : 41547 : oldblk = newblk;
739 : :
740 : 96449 : ItemPointerSet(&newtid, newblk, xlrec->new_offnum);
741 : :
742 : : /*
743 : : * The visibility map may need to be fixed even if the heap page is
744 : : * already up-to-date.
745 : : */
746 [ + + ]: 96449 : if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED)
747 : : {
748 : 286 : Relation reln = CreateFakeRelcacheEntry(rlocator);
749 : 286 : Buffer vmbuffer = InvalidBuffer;
750 : :
751 : 286 : visibilitymap_pin(reln, oldblk, &vmbuffer);
752 : 286 : visibilitymap_clear(reln, oldblk, vmbuffer, VISIBILITYMAP_VALID_BITS);
753 : 286 : ReleaseBuffer(vmbuffer);
754 : 286 : FreeFakeRelcacheEntry(reln);
755 : : }
756 : :
757 : : /*
758 : : * In normal operation, it is important to lock the two pages in
759 : : * page-number order, to avoid possible deadlocks against other update
760 : : * operations going the other way. However, during WAL replay there can
761 : : * be no other update happening, so we don't need to worry about that. But
762 : : * we *do* need to worry that we don't expose an inconsistent state to Hot
763 : : * Standby queries --- so the original page can't be unlocked before we've
764 : : * added the new tuple to the new page.
765 : : */
766 : :
767 : : /* Deal with old tuple version */
768 : 96449 : oldaction = XLogReadBufferForRedo(record, (oldblk == newblk) ? 0 : 1,
769 : : &obuffer);
770 [ + + ]: 96449 : if (oldaction == BLK_NEEDS_REDO)
771 : : {
51 melanieplageman@gmai 772 :GNC 96119 : opage = BufferGetPage(obuffer);
600 michael@paquier.xyz 773 :CBC 96119 : offnum = xlrec->old_offnum;
51 melanieplageman@gmai 774 [ + - - + ]:GNC 96119 : if (offnum < 1 || offnum > PageGetMaxOffsetNumber(opage))
141 tgl@sss.pgh.pa.us 775 [ # # ]:UNC 0 : elog(PANIC, "offnum out of range");
51 melanieplageman@gmai 776 :GNC 96119 : lp = PageGetItemId(opage, offnum);
141 tgl@sss.pgh.pa.us 777 [ - + ]: 96119 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 778 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
779 : :
51 melanieplageman@gmai 780 :GNC 96119 : htup = (HeapTupleHeader) PageGetItem(opage, lp);
781 : :
600 michael@paquier.xyz 782 :CBC 96119 : oldtup.t_data = htup;
783 : 96119 : oldtup.t_len = ItemIdGetLength(lp);
784 : :
785 : 96119 : htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
786 : 96119 : htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
787 [ + + ]: 96119 : if (hot_update)
788 : 38199 : HeapTupleHeaderSetHotUpdated(htup);
789 : : else
790 : 57920 : HeapTupleHeaderClearHotUpdated(htup);
791 : 96119 : fix_infomask_from_infobits(xlrec->old_infobits_set, &htup->t_infomask,
792 : : &htup->t_infomask2);
793 : 96119 : HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
794 : 96119 : HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
795 : : /* Set forward chain link in t_ctid */
796 : 96119 : htup->t_ctid = newtid;
797 : :
798 : : /* Mark the page as a candidate for pruning */
51 melanieplageman@gmai 799 [ - + + + :GNC 96119 : PageSetPrunable(opage, XLogRecGetXid(record));
+ + ]
800 : :
600 michael@paquier.xyz 801 [ + + ]:CBC 96119 : if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED)
51 melanieplageman@gmai 802 :GNC 282 : PageClearAllVisible(opage);
803 : :
804 : 96119 : PageSetLSN(opage, lsn);
600 michael@paquier.xyz 805 :CBC 96119 : MarkBufferDirty(obuffer);
806 : : }
807 : :
808 : : /*
809 : : * Read the page the new tuple goes into, if different from old.
810 : : */
811 [ + + ]: 96449 : if (oldblk == newblk)
812 : : {
813 : 41547 : nbuffer = obuffer;
814 : 41547 : newaction = oldaction;
815 : : }
816 [ + + ]: 54902 : else if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
817 : : {
818 : 673 : nbuffer = XLogInitBufferForRedo(record, 0);
51 melanieplageman@gmai 819 :GNC 673 : npage = BufferGetPage(nbuffer);
820 : 673 : PageInit(npage, BufferGetPageSize(nbuffer), 0);
600 michael@paquier.xyz 821 :CBC 673 : newaction = BLK_NEEDS_REDO;
822 : : }
823 : : else
824 : 54229 : newaction = XLogReadBufferForRedo(record, 0, &nbuffer);
825 : :
826 : : /*
827 : : * The visibility map may need to be fixed even if the heap page is
828 : : * already up-to-date.
829 : : */
830 [ + + ]: 96449 : if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED)
831 : : {
832 : 170 : Relation reln = CreateFakeRelcacheEntry(rlocator);
833 : 170 : Buffer vmbuffer = InvalidBuffer;
834 : :
835 : 170 : visibilitymap_pin(reln, newblk, &vmbuffer);
836 : 170 : visibilitymap_clear(reln, newblk, vmbuffer, VISIBILITYMAP_VALID_BITS);
837 : 170 : ReleaseBuffer(vmbuffer);
838 : 170 : FreeFakeRelcacheEntry(reln);
839 : : }
840 : :
841 : : /* Deal with new tuple */
842 [ + + ]: 96449 : if (newaction == BLK_NEEDS_REDO)
843 : : {
844 : : char *recdata;
845 : : char *recdata_end;
846 : : Size datalen;
847 : : Size tuplen;
848 : :
849 : 95907 : recdata = XLogRecGetBlockData(record, 0, &datalen);
850 : 95907 : recdata_end = recdata + datalen;
851 : :
51 melanieplageman@gmai 852 :GNC 95907 : npage = BufferGetPage(nbuffer);
853 : :
600 michael@paquier.xyz 854 :CBC 95907 : offnum = xlrec->new_offnum;
51 melanieplageman@gmai 855 [ - + ]:GNC 95907 : if (PageGetMaxOffsetNumber(npage) + 1 < offnum)
600 michael@paquier.xyz 856 [ # # ]:UBC 0 : elog(PANIC, "invalid max offset number");
857 : :
600 michael@paquier.xyz 858 [ + + ]:CBC 95907 : if (xlrec->flags & XLH_UPDATE_PREFIX_FROM_OLD)
859 : : {
860 [ - + ]: 17624 : Assert(newblk == oldblk);
861 : 17624 : memcpy(&prefixlen, recdata, sizeof(uint16));
862 : 17624 : recdata += sizeof(uint16);
863 : : }
864 [ + + ]: 95907 : if (xlrec->flags & XLH_UPDATE_SUFFIX_FROM_OLD)
865 : : {
866 [ - + ]: 35300 : Assert(newblk == oldblk);
867 : 35300 : memcpy(&suffixlen, recdata, sizeof(uint16));
868 : 35300 : recdata += sizeof(uint16);
869 : : }
870 : :
447 peter@eisentraut.org 871 : 95907 : memcpy(&xlhdr, recdata, SizeOfHeapHeader);
600 michael@paquier.xyz 872 : 95907 : recdata += SizeOfHeapHeader;
873 : :
874 : 95907 : tuplen = recdata_end - recdata;
875 [ - + ]: 95907 : Assert(tuplen <= MaxHeapTupleSize);
876 : :
877 : 95907 : htup = &tbuf.hdr;
447 peter@eisentraut.org 878 [ + - - + : 95907 : MemSet(htup, 0, SizeofHeapTupleHeader);
- - - - -
- ]
879 : :
880 : : /*
881 : : * Reconstruct the new tuple using the prefix and/or suffix from the
882 : : * old tuple, and the data stored in the WAL record.
883 : : */
600 michael@paquier.xyz 884 : 95907 : newp = (char *) htup + SizeofHeapTupleHeader;
885 [ + + ]: 95907 : if (prefixlen > 0)
886 : : {
887 : : int len;
888 : :
889 : : /* copy bitmap [+ padding] [+ oid] from WAL record */
890 : 17624 : len = xlhdr.t_hoff - SizeofHeapTupleHeader;
891 : 17624 : memcpy(newp, recdata, len);
892 : 17624 : recdata += len;
893 : 17624 : newp += len;
894 : :
895 : : /* copy prefix from old tuple */
896 : 17624 : memcpy(newp, (char *) oldtup.t_data + oldtup.t_data->t_hoff, prefixlen);
897 : 17624 : newp += prefixlen;
898 : :
899 : : /* copy new tuple data from WAL record */
900 : 17624 : len = tuplen - (xlhdr.t_hoff - SizeofHeapTupleHeader);
901 : 17624 : memcpy(newp, recdata, len);
902 : 17624 : recdata += len;
903 : 17624 : newp += len;
904 : : }
905 : : else
906 : : {
907 : : /*
908 : : * copy bitmap [+ padding] [+ oid] + data from record, all in one
909 : : * go
910 : : */
911 : 78283 : memcpy(newp, recdata, tuplen);
912 : 78283 : recdata += tuplen;
913 : 78283 : newp += tuplen;
914 : : }
915 [ - + ]: 95907 : Assert(recdata == recdata_end);
916 : :
917 : : /* copy suffix from old tuple */
918 [ + + ]: 95907 : if (suffixlen > 0)
919 : 35300 : memcpy(newp, (char *) oldtup.t_data + oldtup.t_len - suffixlen, suffixlen);
920 : :
921 : 95907 : newlen = SizeofHeapTupleHeader + tuplen + prefixlen + suffixlen;
922 : 95907 : htup->t_infomask2 = xlhdr.t_infomask2;
923 : 95907 : htup->t_infomask = xlhdr.t_infomask;
924 : 95907 : htup->t_hoff = xlhdr.t_hoff;
925 : :
926 : 95907 : HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
927 : 95907 : HeapTupleHeaderSetCmin(htup, FirstCommandId);
928 : 95907 : HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
929 : : /* Make sure there is no forward chain link in t_ctid */
930 : 95907 : htup->t_ctid = newtid;
931 : :
51 melanieplageman@gmai 932 :GNC 95907 : offnum = PageAddItem(npage, htup, newlen, offnum, true, true);
600 michael@paquier.xyz 933 [ - + ]:CBC 95907 : if (offnum == InvalidOffsetNumber)
600 michael@paquier.xyz 934 [ # # ]:UBC 0 : elog(PANIC, "failed to add tuple");
935 : :
600 michael@paquier.xyz 936 [ + + ]:CBC 95907 : if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED)
51 melanieplageman@gmai 937 :GNC 78 : PageClearAllVisible(npage);
938 : :
939 : : /* needed to update FSM below */
940 : 95907 : freespace = PageGetHeapFreeSpace(npage);
941 : :
942 : 95907 : PageSetLSN(npage, lsn);
943 : : /* See heap_insert() for why we set pd_prune_xid on insert */
36 944 [ - + + + : 95907 : PageSetPrunable(npage, XLogRecGetXid(record));
+ + ]
600 michael@paquier.xyz 945 :CBC 95907 : MarkBufferDirty(nbuffer);
946 : : }
947 : :
948 [ + - + + ]: 96449 : if (BufferIsValid(nbuffer) && nbuffer != obuffer)
949 : 54902 : UnlockReleaseBuffer(nbuffer);
950 [ + - ]: 96449 : if (BufferIsValid(obuffer))
951 : 96449 : UnlockReleaseBuffer(obuffer);
952 : :
953 : : /*
954 : : * If the new page is running low on free space, update the FSM as well.
955 : : * Arbitrarily, our definition of "low" is less than 20%. We can't do much
956 : : * better than that without knowing the fill-factor for the table.
957 : : *
958 : : * However, don't update the FSM on HOT updates, because after crash
959 : : * recovery, either the old or the new tuple will certainly be dead and
960 : : * prunable. After pruning, the page will have roughly as much free space
961 : : * as it did before the update, assuming the new tuple is about the same
962 : : * size as the old one.
963 : : *
964 : : * XXX: Don't do this if the page was restored from full page image. We
965 : : * don't bother to update the FSM in that case, it doesn't need to be
966 : : * totally accurate anyway.
967 : : */
968 [ + + + + : 96449 : if (newaction == BLK_NEEDS_REDO && !hot_update && freespace < BLCKSZ / 5)
+ + ]
969 : 11796 : XLogRecordPageWithFreeSpace(rlocator, newblk, freespace);
970 : 96449 : }
971 : :
972 : : /*
973 : : * Replay XLOG_HEAP_CONFIRM records.
974 : : */
975 : : static void
976 : 93 : heap_xlog_confirm(XLogReaderState *record)
977 : : {
978 : 93 : XLogRecPtr lsn = record->EndRecPtr;
979 : 93 : xl_heap_confirm *xlrec = (xl_heap_confirm *) XLogRecGetData(record);
980 : : Buffer buffer;
981 : : Page page;
982 : : OffsetNumber offnum;
983 : : ItemId lp;
984 : : HeapTupleHeader htup;
985 : :
986 [ + - ]: 93 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
987 : : {
988 : 93 : page = BufferGetPage(buffer);
989 : :
990 : 93 : offnum = xlrec->offnum;
141 tgl@sss.pgh.pa.us 991 [ + - - + ]:GNC 93 : if (offnum < 1 || offnum > PageGetMaxOffsetNumber(page))
141 tgl@sss.pgh.pa.us 992 [ # # ]:UNC 0 : elog(PANIC, "offnum out of range");
141 tgl@sss.pgh.pa.us 993 :GNC 93 : lp = PageGetItemId(page, offnum);
994 [ - + ]: 93 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 995 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
996 : :
600 michael@paquier.xyz 997 :CBC 93 : htup = (HeapTupleHeader) PageGetItem(page, lp);
998 : :
999 : : /*
1000 : : * Confirm tuple as actually inserted
1001 : : */
1002 : 93 : ItemPointerSet(&htup->t_ctid, BufferGetBlockNumber(buffer), offnum);
1003 : :
1004 : 93 : PageSetLSN(page, lsn);
1005 : 93 : MarkBufferDirty(buffer);
1006 : : }
1007 [ + - ]: 93 : if (BufferIsValid(buffer))
1008 : 93 : UnlockReleaseBuffer(buffer);
1009 : 93 : }
1010 : :
1011 : : /*
1012 : : * Replay XLOG_HEAP_LOCK records.
1013 : : */
1014 : : static void
1015 : 56212 : heap_xlog_lock(XLogReaderState *record)
1016 : : {
1017 : 56212 : XLogRecPtr lsn = record->EndRecPtr;
1018 : 56212 : xl_heap_lock *xlrec = (xl_heap_lock *) XLogRecGetData(record);
1019 : : Buffer buffer;
1020 : : Page page;
1021 : : OffsetNumber offnum;
1022 : : ItemId lp;
1023 : : HeapTupleHeader htup;
1024 : :
1025 : : /*
1026 : : * The visibility map may need to be fixed even if the heap page is
1027 : : * already up-to-date.
1028 : : */
1029 [ + + ]: 56212 : if (xlrec->flags & XLH_LOCK_ALL_FROZEN_CLEARED)
1030 : : {
1031 : : RelFileLocator rlocator;
1032 : 46 : Buffer vmbuffer = InvalidBuffer;
1033 : : BlockNumber block;
1034 : : Relation reln;
1035 : :
1036 : 46 : XLogRecGetBlockTag(record, 0, &rlocator, NULL, &block);
1037 : 46 : reln = CreateFakeRelcacheEntry(rlocator);
1038 : :
1039 : 46 : visibilitymap_pin(reln, block, &vmbuffer);
1040 : 46 : visibilitymap_clear(reln, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN);
1041 : :
1042 : 46 : ReleaseBuffer(vmbuffer);
1043 : 46 : FreeFakeRelcacheEntry(reln);
1044 : : }
1045 : :
1046 [ + + ]: 56212 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
1047 : : {
249 peter@eisentraut.org 1048 :GNC 55993 : page = BufferGetPage(buffer);
1049 : :
600 michael@paquier.xyz 1050 :CBC 55993 : offnum = xlrec->offnum;
141 tgl@sss.pgh.pa.us 1051 [ + - - + ]:GNC 55993 : if (offnum < 1 || offnum > PageGetMaxOffsetNumber(page))
141 tgl@sss.pgh.pa.us 1052 [ # # ]:UNC 0 : elog(PANIC, "offnum out of range");
141 tgl@sss.pgh.pa.us 1053 :GNC 55993 : lp = PageGetItemId(page, offnum);
1054 [ - + ]: 55993 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 1055 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
1056 : :
600 michael@paquier.xyz 1057 :CBC 55993 : htup = (HeapTupleHeader) PageGetItem(page, lp);
1058 : :
1059 : 55993 : htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
1060 : 55993 : htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
1061 : 55993 : fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
1062 : : &htup->t_infomask2);
1063 : :
1064 : : /*
1065 : : * Clear relevant update flags, but only if the modified infomask says
1066 : : * there's no update.
1067 : : */
1068 [ + - ]: 55993 : if (HEAP_XMAX_IS_LOCKED_ONLY(htup->t_infomask))
1069 : : {
1070 : 55993 : HeapTupleHeaderClearHotUpdated(htup);
1071 : : /* Make sure there is no forward chain link in t_ctid */
1072 : 55993 : ItemPointerSet(&htup->t_ctid,
1073 : : BufferGetBlockNumber(buffer),
1074 : : offnum);
1075 : : }
1076 : 55993 : HeapTupleHeaderSetXmax(htup, xlrec->xmax);
1077 : 55993 : HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
1078 : 55993 : PageSetLSN(page, lsn);
1079 : 55993 : MarkBufferDirty(buffer);
1080 : : }
1081 [ + - ]: 56212 : if (BufferIsValid(buffer))
1082 : 56212 : UnlockReleaseBuffer(buffer);
1083 : 56212 : }
1084 : :
1085 : : /*
1086 : : * Replay XLOG_HEAP2_LOCK_UPDATED records.
1087 : : */
1088 : : static void
600 michael@paquier.xyz 1089 :UBC 0 : heap_xlog_lock_updated(XLogReaderState *record)
1090 : : {
1091 : 0 : XLogRecPtr lsn = record->EndRecPtr;
1092 : : xl_heap_lock_updated *xlrec;
1093 : : Buffer buffer;
1094 : : Page page;
1095 : : OffsetNumber offnum;
1096 : : ItemId lp;
1097 : : HeapTupleHeader htup;
1098 : :
1099 : 0 : xlrec = (xl_heap_lock_updated *) XLogRecGetData(record);
1100 : :
1101 : : /*
1102 : : * The visibility map may need to be fixed even if the heap page is
1103 : : * already up-to-date.
1104 : : */
1105 [ # # ]: 0 : if (xlrec->flags & XLH_LOCK_ALL_FROZEN_CLEARED)
1106 : : {
1107 : : RelFileLocator rlocator;
1108 : 0 : Buffer vmbuffer = InvalidBuffer;
1109 : : BlockNumber block;
1110 : : Relation reln;
1111 : :
1112 : 0 : XLogRecGetBlockTag(record, 0, &rlocator, NULL, &block);
1113 : 0 : reln = CreateFakeRelcacheEntry(rlocator);
1114 : :
1115 : 0 : visibilitymap_pin(reln, block, &vmbuffer);
1116 : 0 : visibilitymap_clear(reln, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN);
1117 : :
1118 : 0 : ReleaseBuffer(vmbuffer);
1119 : 0 : FreeFakeRelcacheEntry(reln);
1120 : : }
1121 : :
1122 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
1123 : : {
1124 : 0 : page = BufferGetPage(buffer);
1125 : :
1126 : 0 : offnum = xlrec->offnum;
141 tgl@sss.pgh.pa.us 1127 [ # # # # ]:UNC 0 : if (offnum < 1 || offnum > PageGetMaxOffsetNumber(page))
1128 [ # # ]: 0 : elog(PANIC, "offnum out of range");
1129 : 0 : lp = PageGetItemId(page, offnum);
1130 [ # # ]: 0 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 1131 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
1132 : :
1133 : 0 : htup = (HeapTupleHeader) PageGetItem(page, lp);
1134 : :
1135 : 0 : htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
1136 : 0 : htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
1137 : 0 : fix_infomask_from_infobits(xlrec->infobits_set, &htup->t_infomask,
1138 : : &htup->t_infomask2);
1139 : 0 : HeapTupleHeaderSetXmax(htup, xlrec->xmax);
1140 : :
1141 : 0 : PageSetLSN(page, lsn);
1142 : 0 : MarkBufferDirty(buffer);
1143 : : }
1144 [ # # ]: 0 : if (BufferIsValid(buffer))
1145 : 0 : UnlockReleaseBuffer(buffer);
1146 : 0 : }
1147 : :
1148 : : /*
1149 : : * Replay XLOG_HEAP_INPLACE records.
1150 : : */
1151 : : static void
600 michael@paquier.xyz 1152 :CBC 8365 : heap_xlog_inplace(XLogReaderState *record)
1153 : : {
1154 : 8365 : XLogRecPtr lsn = record->EndRecPtr;
1155 : 8365 : xl_heap_inplace *xlrec = (xl_heap_inplace *) XLogRecGetData(record);
1156 : : Buffer buffer;
1157 : : Page page;
1158 : : OffsetNumber offnum;
1159 : : ItemId lp;
1160 : : HeapTupleHeader htup;
1161 : : uint32 oldlen;
1162 : : Size newlen;
1163 : :
1164 [ + + ]: 8365 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
1165 : : {
1166 : 8148 : char *newtup = XLogRecGetBlockData(record, 0, &newlen);
1167 : :
1168 : 8148 : page = BufferGetPage(buffer);
1169 : :
1170 : 8148 : offnum = xlrec->offnum;
141 tgl@sss.pgh.pa.us 1171 [ + - - + ]:GNC 8148 : if (offnum < 1 || offnum > PageGetMaxOffsetNumber(page))
141 tgl@sss.pgh.pa.us 1172 [ # # ]:UNC 0 : elog(PANIC, "offnum out of range");
141 tgl@sss.pgh.pa.us 1173 :GNC 8148 : lp = PageGetItemId(page, offnum);
1174 [ - + ]: 8148 : if (!ItemIdIsNormal(lp))
600 michael@paquier.xyz 1175 [ # # ]:UBC 0 : elog(PANIC, "invalid lp");
1176 : :
600 michael@paquier.xyz 1177 :CBC 8148 : htup = (HeapTupleHeader) PageGetItem(page, lp);
1178 : :
1179 : 8148 : oldlen = ItemIdGetLength(lp) - htup->t_hoff;
1180 [ - + ]: 8148 : if (oldlen != newlen)
600 michael@paquier.xyz 1181 [ # # ]:UBC 0 : elog(PANIC, "wrong tuple length");
1182 : :
600 michael@paquier.xyz 1183 :CBC 8148 : memcpy((char *) htup + htup->t_hoff, newtup, newlen);
1184 : :
1185 : 8148 : PageSetLSN(page, lsn);
1186 : 8148 : MarkBufferDirty(buffer);
1187 : : }
1188 [ + - ]: 8365 : if (BufferIsValid(buffer))
1189 : 8365 : UnlockReleaseBuffer(buffer);
1190 : :
557 noah@leadboat.com 1191 : 8365 : ProcessCommittedInvalidationMessages(xlrec->msgs,
1192 : : xlrec->nmsgs,
1193 : 8365 : xlrec->relcacheInitFileInval,
1194 : : xlrec->dbId,
1195 : : xlrec->tsId);
600 michael@paquier.xyz 1196 : 8365 : }
1197 : :
1198 : : void
1199 : 1801502 : heap_redo(XLogReaderState *record)
1200 : : {
1201 : 1801502 : uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
1202 : :
1203 : : /*
1204 : : * These operations don't overwrite MVCC data so no conflict processing is
1205 : : * required. The ones in heap2 rmgr do.
1206 : : */
1207 : :
1208 [ + + + + : 1801502 : switch (info & XLOG_HEAP_OPMASK)
+ + + +
- ]
1209 : : {
1210 : 1322456 : case XLOG_HEAP_INSERT:
1211 : 1322456 : heap_xlog_insert(record);
1212 : 1322456 : break;
1213 : 317925 : case XLOG_HEAP_DELETE:
1214 : 317925 : heap_xlog_delete(record);
1215 : 317925 : break;
1216 : 57966 : case XLOG_HEAP_UPDATE:
1217 : 57966 : heap_xlog_update(record, false);
1218 : 57966 : break;
1219 : 2 : case XLOG_HEAP_TRUNCATE:
1220 : :
1221 : : /*
1222 : : * TRUNCATE is a no-op because the actions are already logged as
1223 : : * SMGR WAL records. TRUNCATE WAL record only exists for logical
1224 : : * decoding.
1225 : : */
1226 : 2 : break;
1227 : 38483 : case XLOG_HEAP_HOT_UPDATE:
1228 : 38483 : heap_xlog_update(record, true);
1229 : 38483 : break;
1230 : 93 : case XLOG_HEAP_CONFIRM:
1231 : 93 : heap_xlog_confirm(record);
1232 : 93 : break;
1233 : 56212 : case XLOG_HEAP_LOCK:
1234 : 56212 : heap_xlog_lock(record);
1235 : 56212 : break;
1236 : 8365 : case XLOG_HEAP_INPLACE:
1237 : 8365 : heap_xlog_inplace(record);
1238 : 8365 : break;
600 michael@paquier.xyz 1239 :UBC 0 : default:
1240 [ # # ]: 0 : elog(PANIC, "heap_redo: unknown op code %u", info);
1241 : : }
600 michael@paquier.xyz 1242 :CBC 1801502 : }
1243 : :
1244 : : void
1245 : 88410 : heap2_redo(XLogReaderState *record)
1246 : : {
1247 : 88410 : uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
1248 : :
1249 [ + + - + : 88410 : switch (info & XLOG_HEAP_OPMASK)
- - - ]
1250 : : {
1251 : 20083 : case XLOG_HEAP2_PRUNE_ON_ACCESS:
1252 : : case XLOG_HEAP2_PRUNE_VACUUM_SCAN:
1253 : : case XLOG_HEAP2_PRUNE_VACUUM_CLEANUP:
1254 : 20083 : heap_xlog_prune_freeze(record);
1255 : 20083 : break;
1256 : 67279 : case XLOG_HEAP2_MULTI_INSERT:
1257 : 67279 : heap_xlog_multi_insert(record);
1258 : 67279 : break;
600 michael@paquier.xyz 1259 :UBC 0 : case XLOG_HEAP2_LOCK_UPDATED:
1260 : 0 : heap_xlog_lock_updated(record);
1261 : 0 : break;
600 michael@paquier.xyz 1262 :CBC 1048 : case XLOG_HEAP2_NEW_CID:
1263 : :
1264 : : /*
1265 : : * Nothing to do on a real replay, only used during logical
1266 : : * decoding.
1267 : : */
1268 : 1048 : break;
600 michael@paquier.xyz 1269 :UBC 0 : case XLOG_HEAP2_REWRITE:
1270 : 0 : heap_xlog_logical_rewrite(record);
1271 : 0 : break;
1272 : 0 : default:
1273 [ # # ]: 0 : elog(PANIC, "heap2_redo: unknown op code %u", info);
1274 : : }
600 michael@paquier.xyz 1275 :CBC 88410 : }
1276 : :
1277 : : /*
1278 : : * Mask a heap page before performing consistency checks on it.
1279 : : */
1280 : : void
1281 : 3012116 : heap_mask(char *pagedata, BlockNumber blkno)
1282 : : {
1283 : 3012116 : Page page = (Page) pagedata;
1284 : : OffsetNumber off;
1285 : :
1286 : 3012116 : mask_page_lsn_and_checksum(page);
1287 : :
1288 : 3012116 : mask_page_hint_bits(page);
1289 : 3012116 : mask_unused_space(page);
1290 : :
1291 [ + + ]: 246925262 : for (off = 1; off <= PageGetMaxOffsetNumber(page); off++)
1292 : : {
1293 : 243913146 : ItemId iid = PageGetItemId(page, off);
1294 : : char *page_item;
1295 : :
1296 : 243913146 : page_item = (char *) (page + ItemIdGetOffset(iid));
1297 : :
1298 [ + + ]: 243913146 : if (ItemIdIsNormal(iid))
1299 : : {
1300 : 228734044 : HeapTupleHeader page_htup = (HeapTupleHeader) page_item;
1301 : :
1302 : : /*
1303 : : * If xmin of a tuple is not yet frozen, we should ignore
1304 : : * differences in hint bits, since they can be set without
1305 : : * emitting WAL.
1306 : : */
1307 [ + + ]: 228734044 : if (!HeapTupleHeaderXminFrozen(page_htup))
1308 : 224968350 : page_htup->t_infomask &= ~HEAP_XACT_MASK;
1309 : : else
1310 : : {
1311 : : /* Still we need to mask xmax hint bits. */
1312 : 3765694 : page_htup->t_infomask &= ~HEAP_XMAX_INVALID;
1313 : 3765694 : page_htup->t_infomask &= ~HEAP_XMAX_COMMITTED;
1314 : : }
1315 : :
1316 : : /*
1317 : : * During replay, we set Command Id to FirstCommandId. Hence, mask
1318 : : * it. See heap_xlog_insert() for details.
1319 : : */
1320 : 228734044 : page_htup->t_choice.t_heap.t_field3.t_cid = MASK_MARKER;
1321 : :
1322 : : /*
1323 : : * For a speculative tuple, heap_insert() does not set ctid in the
1324 : : * caller-passed heap tuple itself, leaving the ctid field to
1325 : : * contain a speculative token value - a per-backend monotonically
1326 : : * increasing identifier. Besides, it does not WAL-log ctid under
1327 : : * any circumstances.
1328 : : *
1329 : : * During redo, heap_xlog_insert() sets t_ctid to current block
1330 : : * number and self offset number. It doesn't care about any
1331 : : * speculative insertions on the primary. Hence, we set t_ctid to
1332 : : * current block number and self offset number to ignore any
1333 : : * inconsistency.
1334 : : */
1335 [ + + ]: 228734044 : if (HeapTupleHeaderIsSpeculative(page_htup))
1336 : 94 : ItemPointerSet(&page_htup->t_ctid, blkno, off);
1337 : :
1338 : : /*
1339 : : * NB: Not ignoring ctid changes due to the tuple having moved
1340 : : * (i.e. HeapTupleHeaderIndicatesMovedPartitions), because that's
1341 : : * important information that needs to be in-sync between primary
1342 : : * and standby, and thus is WAL logged.
1343 : : */
1344 : : }
1345 : :
1346 : : /*
1347 : : * Ignore any padding bytes after the tuple, when the length of the
1348 : : * item is not MAXALIGNed.
1349 : : */
1350 [ + + ]: 243913146 : if (ItemIdHasStorage(iid))
1351 : : {
1352 : 228734044 : int len = ItemIdGetLength(iid);
1353 : 228734044 : int padlen = MAXALIGN(len) - len;
1354 : :
1355 [ + + ]: 228734044 : if (padlen > 0)
1356 : 123548648 : memset(page_item + len, MASK_MARKER, padlen);
1357 : : }
1358 : : }
1359 : 3012116 : }
|