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