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