Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * ginxlog.c
4 : : * WAL replay logic for inverted index.
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/gin/ginxlog.c
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include "access/bufmask.h"
17 : : #include "access/gin_private.h"
18 : : #include "access/ginxlog.h"
19 : : #include "access/xlogutils.h"
20 : : #include "utils/memutils.h"
21 : :
22 : : static MemoryContext opCtx; /* working memory for operations */
23 : :
24 : : static void
25 : 0 : ginRedoClearIncompleteSplit(XLogReaderState *record, uint8 block_id)
26 : : {
27 : 0 : XLogRecPtr lsn = record->EndRecPtr;
28 : 0 : Buffer buffer;
29 : 0 : Page page;
30 : :
31 [ # # ]: 0 : if (XLogReadBufferForRedo(record, block_id, &buffer) == BLK_NEEDS_REDO)
32 : : {
33 : 0 : page = BufferGetPage(buffer);
34 : 0 : GinPageGetOpaque(page)->flags &= ~GIN_INCOMPLETE_SPLIT;
35 : :
36 : 0 : PageSetLSN(page, lsn);
37 : 0 : MarkBufferDirty(buffer);
38 : 0 : }
39 [ # # ]: 0 : if (BufferIsValid(buffer))
40 : 0 : UnlockReleaseBuffer(buffer);
41 : 0 : }
42 : :
43 : : static void
44 : 0 : ginRedoCreatePTree(XLogReaderState *record)
45 : : {
46 : 0 : XLogRecPtr lsn = record->EndRecPtr;
47 : 0 : ginxlogCreatePostingTree *data = (ginxlogCreatePostingTree *) XLogRecGetData(record);
48 : 0 : char *ptr;
49 : 0 : Buffer buffer;
50 : 0 : Page page;
51 : :
52 : 0 : buffer = XLogInitBufferForRedo(record, 0);
53 : 0 : page = BufferGetPage(buffer);
54 : :
55 : 0 : GinInitBuffer(buffer, GIN_DATA | GIN_LEAF | GIN_COMPRESSED);
56 : :
57 : 0 : ptr = XLogRecGetData(record) + sizeof(ginxlogCreatePostingTree);
58 : :
59 : : /* Place page data */
60 : 0 : memcpy(GinDataLeafPageGetPostingList(page), ptr, data->size);
61 : :
62 [ # # ]: 0 : GinDataPageSetDataSize(page, data->size);
63 : :
64 : 0 : PageSetLSN(page, lsn);
65 : :
66 : 0 : MarkBufferDirty(buffer);
67 : 0 : UnlockReleaseBuffer(buffer);
68 : 0 : }
69 : :
70 : : static void
71 : 0 : ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata)
72 : : {
73 : 0 : Page page = BufferGetPage(buffer);
74 : 0 : ginxlogInsertEntry *data = (ginxlogInsertEntry *) rdata;
75 : 0 : OffsetNumber offset = data->offset;
76 : 0 : IndexTuple itup;
77 : :
78 [ # # ]: 0 : if (rightblkno != InvalidBlockNumber)
79 : : {
80 : : /* update link to right page after split */
81 [ # # ]: 0 : Assert(!GinPageIsLeaf(page));
82 [ # # ]: 0 : Assert(offset >= FirstOffsetNumber && offset <= PageGetMaxOffsetNumber(page));
83 : 0 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offset));
84 : 0 : GinSetDownlink(itup, rightblkno);
85 : 0 : }
86 : :
87 [ # # ]: 0 : if (data->isDelete)
88 : : {
89 [ # # ]: 0 : Assert(GinPageIsLeaf(page));
90 [ # # ]: 0 : Assert(offset >= FirstOffsetNumber && offset <= PageGetMaxOffsetNumber(page));
91 : 0 : PageIndexTupleDelete(page, offset);
92 : 0 : }
93 : :
94 : 0 : itup = &data->tuple;
95 : :
96 [ # # ]: 0 : if (PageAddItem(page, itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
97 : : {
98 : 0 : RelFileLocator locator;
99 : 0 : ForkNumber forknum;
100 : 0 : BlockNumber blknum;
101 : :
102 : 0 : BufferGetTag(buffer, &locator, &forknum, &blknum);
103 [ # # # # ]: 0 : elog(ERROR, "failed to add item to index page in %u/%u/%u",
104 : : locator.spcOid, locator.dbOid, locator.relNumber);
105 : 0 : }
106 : 0 : }
107 : :
108 : : /*
109 : : * Redo recompression of posting list. Doing all the changes in-place is not
110 : : * always possible, because it might require more space than we've on the page.
111 : : * Instead, once modification is required we copy unprocessed tail of the page
112 : : * into separately allocated chunk of memory for further reading original
113 : : * versions of segments. Thanks to that we don't bother about moving page data
114 : : * in-place.
115 : : */
116 : : static void
117 : 0 : ginRedoRecompress(Page page, ginxlogRecompressDataLeaf *data)
118 : : {
119 : 0 : int actionno;
120 : 0 : int segno;
121 : 0 : GinPostingList *oldseg;
122 : 0 : char *segmentend;
123 : 0 : char *walbuf;
124 : 0 : int totalsize;
125 : 0 : void *tailCopy = NULL;
126 : 0 : char *writePtr;
127 : 0 : char *segptr;
128 : :
129 : : /*
130 : : * If the page is in pre-9.4 format, convert to new format first.
131 : : */
132 [ # # ]: 0 : if (!GinPageIsCompressed(page))
133 : : {
134 : 0 : ItemPointer uncompressed = (ItemPointer) GinDataPageGetData(page);
135 : 0 : int nuncompressed = GinPageGetOpaque(page)->maxoff;
136 : 0 : int npacked;
137 : :
138 : : /*
139 : : * Empty leaf pages are deleted as part of vacuum, but leftmost and
140 : : * rightmost pages are never deleted. So, pg_upgrade'd from pre-9.4
141 : : * instances might contain empty leaf pages, and we need to handle
142 : : * them correctly.
143 : : */
144 [ # # ]: 0 : if (nuncompressed > 0)
145 : : {
146 : 0 : GinPostingList *plist;
147 : :
148 : 0 : plist = ginCompressPostingList(uncompressed, nuncompressed,
149 : : BLCKSZ, &npacked);
150 : 0 : totalsize = SizeOfGinPostingList(plist);
151 : :
152 [ # # ]: 0 : Assert(npacked == nuncompressed);
153 : :
154 : 0 : memcpy(GinDataLeafPageGetPostingList(page), plist, totalsize);
155 : 0 : }
156 : : else
157 : : {
158 : 0 : totalsize = 0;
159 : : }
160 : :
161 [ # # ]: 0 : GinDataPageSetDataSize(page, totalsize);
162 : 0 : GinPageSetCompressed(page);
163 : 0 : GinPageGetOpaque(page)->maxoff = InvalidOffsetNumber;
164 : 0 : }
165 : :
166 : 0 : oldseg = GinDataLeafPageGetPostingList(page);
167 : 0 : writePtr = (char *) oldseg;
168 : 0 : segmentend = (char *) oldseg + GinDataLeafPageGetPostingListSize(page);
169 : 0 : segno = 0;
170 : :
171 : 0 : walbuf = ((char *) data) + sizeof(ginxlogRecompressDataLeaf);
172 [ # # ]: 0 : for (actionno = 0; actionno < data->nactions; actionno++)
173 : : {
174 : 0 : uint8 a_segno = *((uint8 *) (walbuf++));
175 : 0 : uint8 a_action = *((uint8 *) (walbuf++));
176 : 0 : GinPostingList *newseg = NULL;
177 : 0 : int newsegsize = 0;
178 : 0 : ItemPointerData *items = NULL;
179 : 0 : uint16 nitems = 0;
180 : 0 : ItemPointerData *olditems;
181 : 0 : int nolditems;
182 : 0 : ItemPointerData *newitems;
183 : 0 : int nnewitems;
184 : 0 : int segsize;
185 : :
186 : : /* Extract all the information we need from the WAL record */
187 [ # # # # ]: 0 : if (a_action == GIN_SEGMENT_INSERT ||
188 : 0 : a_action == GIN_SEGMENT_REPLACE)
189 : : {
190 : 0 : newseg = (GinPostingList *) walbuf;
191 : 0 : newsegsize = SizeOfGinPostingList(newseg);
192 : 0 : walbuf += SHORTALIGN(newsegsize);
193 : 0 : }
194 : :
195 [ # # ]: 0 : if (a_action == GIN_SEGMENT_ADDITEMS)
196 : : {
197 : 0 : memcpy(&nitems, walbuf, sizeof(uint16));
198 : 0 : walbuf += sizeof(uint16);
199 : 0 : items = (ItemPointerData *) walbuf;
200 : 0 : walbuf += nitems * sizeof(ItemPointerData);
201 : 0 : }
202 : :
203 : : /* Skip to the segment that this action concerns */
204 [ # # ]: 0 : Assert(segno <= a_segno);
205 [ # # ]: 0 : while (segno < a_segno)
206 : : {
207 : : /*
208 : : * Once modification is started and page tail is copied, we've to
209 : : * copy unmodified segments.
210 : : */
211 : 0 : segsize = SizeOfGinPostingList(oldseg);
212 [ # # ]: 0 : if (tailCopy)
213 : : {
214 [ # # ]: 0 : Assert(writePtr + segsize < PageGetSpecialPointer(page));
215 : 0 : memcpy(writePtr, oldseg, segsize);
216 : 0 : }
217 : 0 : writePtr += segsize;
218 : 0 : oldseg = GinNextPostingListSegment(oldseg);
219 : 0 : segno++;
220 : : }
221 : :
222 : : /*
223 : : * ADDITEMS action is handled like REPLACE, but the new segment to
224 : : * replace the old one is reconstructed using the old segment from
225 : : * disk and the new items from the WAL record.
226 : : */
227 [ # # ]: 0 : if (a_action == GIN_SEGMENT_ADDITEMS)
228 : : {
229 : 0 : int npacked;
230 : :
231 : 0 : olditems = ginPostingListDecode(oldseg, &nolditems);
232 : :
233 : 0 : newitems = ginMergeItemPointers(items, nitems,
234 : 0 : olditems, nolditems,
235 : : &nnewitems);
236 [ # # ]: 0 : Assert(nnewitems == nolditems + nitems);
237 : :
238 : 0 : newseg = ginCompressPostingList(newitems, nnewitems,
239 : : BLCKSZ, &npacked);
240 [ # # ]: 0 : Assert(npacked == nnewitems);
241 : :
242 : 0 : newsegsize = SizeOfGinPostingList(newseg);
243 : 0 : a_action = GIN_SEGMENT_REPLACE;
244 : 0 : }
245 : :
246 : 0 : segptr = (char *) oldseg;
247 [ # # ]: 0 : if (segptr != segmentend)
248 : 0 : segsize = SizeOfGinPostingList(oldseg);
249 : : else
250 : : {
251 : : /*
252 : : * Positioned after the last existing segment. Only INSERTs
253 : : * expected here.
254 : : */
255 [ # # ]: 0 : Assert(a_action == GIN_SEGMENT_INSERT);
256 : 0 : segsize = 0;
257 : : }
258 : :
259 : : /*
260 : : * We're about to start modification of the page. So, copy tail of
261 : : * the page if it's not done already.
262 : : */
263 [ # # # # ]: 0 : if (!tailCopy && segptr != segmentend)
264 : : {
265 : 0 : int tailSize = segmentend - segptr;
266 : :
267 : 0 : tailCopy = palloc(tailSize);
268 : 0 : memcpy(tailCopy, segptr, tailSize);
269 : 0 : segptr = tailCopy;
270 : 0 : oldseg = (GinPostingList *) segptr;
271 : 0 : segmentend = segptr + tailSize;
272 : 0 : }
273 : :
274 [ # # # # ]: 0 : switch (a_action)
275 : : {
276 : : case GIN_SEGMENT_DELETE:
277 : 0 : segptr += segsize;
278 : 0 : segno++;
279 : 0 : break;
280 : :
281 : : case GIN_SEGMENT_INSERT:
282 : : /* copy the new segment in place */
283 [ # # ]: 0 : Assert(writePtr + newsegsize <= PageGetSpecialPointer(page));
284 : 0 : memcpy(writePtr, newseg, newsegsize);
285 : 0 : writePtr += newsegsize;
286 : 0 : break;
287 : :
288 : : case GIN_SEGMENT_REPLACE:
289 : : /* copy the new version of segment in place */
290 [ # # ]: 0 : Assert(writePtr + newsegsize <= PageGetSpecialPointer(page));
291 : 0 : memcpy(writePtr, newseg, newsegsize);
292 : 0 : writePtr += newsegsize;
293 : 0 : segptr += segsize;
294 : 0 : segno++;
295 : 0 : break;
296 : :
297 : : default:
298 [ # # # # ]: 0 : elog(ERROR, "unexpected GIN leaf action: %u", a_action);
299 : 0 : }
300 : 0 : oldseg = (GinPostingList *) segptr;
301 : 0 : }
302 : :
303 : : /* Copy the rest of unmodified segments if any. */
304 : 0 : segptr = (char *) oldseg;
305 [ # # # # ]: 0 : if (segptr != segmentend && tailCopy)
306 : : {
307 : 0 : int restSize = segmentend - segptr;
308 : :
309 [ # # ]: 0 : Assert(writePtr + restSize <= PageGetSpecialPointer(page));
310 : 0 : memcpy(writePtr, segptr, restSize);
311 : 0 : writePtr += restSize;
312 : 0 : }
313 : :
314 : 0 : totalsize = writePtr - (char *) GinDataLeafPageGetPostingList(page);
315 [ # # ]: 0 : GinDataPageSetDataSize(page, totalsize);
316 : 0 : }
317 : :
318 : : static void
319 : 0 : ginRedoInsertData(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata)
320 : : {
321 : 0 : Page page = BufferGetPage(buffer);
322 : :
323 [ # # ]: 0 : if (isLeaf)
324 : : {
325 : 0 : ginxlogRecompressDataLeaf *data = (ginxlogRecompressDataLeaf *) rdata;
326 : :
327 [ # # ]: 0 : Assert(GinPageIsLeaf(page));
328 : :
329 : 0 : ginRedoRecompress(page, data);
330 : 0 : }
331 : : else
332 : : {
333 : 0 : ginxlogInsertDataInternal *data = (ginxlogInsertDataInternal *) rdata;
334 : 0 : PostingItem *oldpitem;
335 : :
336 [ # # ]: 0 : Assert(!GinPageIsLeaf(page));
337 : :
338 : : /* update link to right page after split */
339 : 0 : oldpitem = GinDataPageGetPostingItem(page, data->offset);
340 : 0 : PostingItemSetBlockNumber(oldpitem, rightblkno);
341 : :
342 : 0 : GinDataPageAddPostingItem(page, &data->newitem, data->offset);
343 : 0 : }
344 : 0 : }
345 : :
346 : : static void
347 : 0 : ginRedoInsert(XLogReaderState *record)
348 : : {
349 : 0 : XLogRecPtr lsn = record->EndRecPtr;
350 : 0 : ginxlogInsert *data = (ginxlogInsert *) XLogRecGetData(record);
351 : 0 : Buffer buffer;
352 : : #ifdef NOT_USED
353 : : BlockNumber leftChildBlkno = InvalidBlockNumber;
354 : : #endif
355 : 0 : BlockNumber rightChildBlkno = InvalidBlockNumber;
356 : 0 : bool isLeaf = (data->flags & GIN_INSERT_ISLEAF) != 0;
357 : :
358 : : /*
359 : : * First clear incomplete-split flag on child page if this finishes a
360 : : * split.
361 : : */
362 [ # # ]: 0 : if (!isLeaf)
363 : : {
364 : 0 : char *payload = XLogRecGetData(record) + sizeof(ginxlogInsert);
365 : :
366 : : #ifdef NOT_USED
367 : : leftChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
368 : : #endif
369 : 0 : payload += sizeof(BlockIdData);
370 : 0 : rightChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
371 : :
372 : 0 : ginRedoClearIncompleteSplit(record, 1);
373 : 0 : }
374 : :
375 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
376 : : {
377 : 0 : Page page = BufferGetPage(buffer);
378 : 0 : Size len;
379 : 0 : char *payload = XLogRecGetBlockData(record, 0, &len);
380 : :
381 : : /* How to insert the payload is tree-type specific */
382 [ # # ]: 0 : if (data->flags & GIN_INSERT_ISDATA)
383 : : {
384 [ # # ]: 0 : Assert(GinPageIsData(page));
385 : 0 : ginRedoInsertData(buffer, isLeaf, rightChildBlkno, payload);
386 : 0 : }
387 : : else
388 : : {
389 [ # # ]: 0 : Assert(!GinPageIsData(page));
390 : 0 : ginRedoInsertEntry(buffer, isLeaf, rightChildBlkno, payload);
391 : : }
392 : :
393 : 0 : PageSetLSN(page, lsn);
394 : 0 : MarkBufferDirty(buffer);
395 : 0 : }
396 [ # # ]: 0 : if (BufferIsValid(buffer))
397 : 0 : UnlockReleaseBuffer(buffer);
398 : 0 : }
399 : :
400 : : static void
401 : 0 : ginRedoSplit(XLogReaderState *record)
402 : : {
403 : 0 : ginxlogSplit *data = (ginxlogSplit *) XLogRecGetData(record);
404 : 0 : Buffer lbuffer,
405 : : rbuffer,
406 : : rootbuf;
407 : 0 : bool isLeaf = (data->flags & GIN_INSERT_ISLEAF) != 0;
408 : 0 : bool isRoot = (data->flags & GIN_SPLIT_ROOT) != 0;
409 : :
410 : : /*
411 : : * First clear incomplete-split flag on child page if this finishes a
412 : : * split
413 : : */
414 [ # # ]: 0 : if (!isLeaf)
415 : 0 : ginRedoClearIncompleteSplit(record, 3);
416 : :
417 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &lbuffer) != BLK_RESTORED)
418 [ # # # # ]: 0 : elog(ERROR, "GIN split record did not contain a full-page image of left page");
419 : :
420 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 1, &rbuffer) != BLK_RESTORED)
421 [ # # # # ]: 0 : elog(ERROR, "GIN split record did not contain a full-page image of right page");
422 : :
423 [ # # ]: 0 : if (isRoot)
424 : : {
425 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 2, &rootbuf) != BLK_RESTORED)
426 [ # # # # ]: 0 : elog(ERROR, "GIN split record did not contain a full-page image of root page");
427 : 0 : UnlockReleaseBuffer(rootbuf);
428 : 0 : }
429 : :
430 : 0 : UnlockReleaseBuffer(rbuffer);
431 : 0 : UnlockReleaseBuffer(lbuffer);
432 : 0 : }
433 : :
434 : : /*
435 : : * VACUUM_PAGE record contains simply a full image of the page, similar to
436 : : * an XLOG_FPI record.
437 : : */
438 : : static void
439 : 0 : ginRedoVacuumPage(XLogReaderState *record)
440 : : {
441 : 0 : Buffer buffer;
442 : :
443 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &buffer) != BLK_RESTORED)
444 : : {
445 [ # # # # ]: 0 : elog(ERROR, "replay of gin entry tree page vacuum did not restore the page");
446 : 0 : }
447 : 0 : UnlockReleaseBuffer(buffer);
448 : 0 : }
449 : :
450 : : static void
451 : 0 : ginRedoVacuumDataLeafPage(XLogReaderState *record)
452 : : {
453 : 0 : XLogRecPtr lsn = record->EndRecPtr;
454 : 0 : Buffer buffer;
455 : :
456 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
457 : : {
458 : 0 : Page page = BufferGetPage(buffer);
459 : 0 : Size len;
460 : 0 : ginxlogVacuumDataLeafPage *xlrec;
461 : :
462 : 0 : xlrec = (ginxlogVacuumDataLeafPage *) XLogRecGetBlockData(record, 0, &len);
463 : :
464 [ # # ]: 0 : Assert(GinPageIsLeaf(page));
465 [ # # ]: 0 : Assert(GinPageIsData(page));
466 : :
467 : 0 : ginRedoRecompress(page, &xlrec->data);
468 : 0 : PageSetLSN(page, lsn);
469 : 0 : MarkBufferDirty(buffer);
470 : 0 : }
471 [ # # ]: 0 : if (BufferIsValid(buffer))
472 : 0 : UnlockReleaseBuffer(buffer);
473 : 0 : }
474 : :
475 : : static void
476 : 0 : ginRedoDeletePage(XLogReaderState *record)
477 : : {
478 : 0 : XLogRecPtr lsn = record->EndRecPtr;
479 : 0 : ginxlogDeletePage *data = (ginxlogDeletePage *) XLogRecGetData(record);
480 : 0 : Buffer dbuffer;
481 : 0 : Buffer pbuffer;
482 : 0 : Buffer lbuffer;
483 : 0 : Page page;
484 : :
485 : : /*
486 : : * Lock left page first in order to prevent possible deadlock with
487 : : * ginStepRight().
488 : : */
489 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 2, &lbuffer) == BLK_NEEDS_REDO)
490 : : {
491 : 0 : page = BufferGetPage(lbuffer);
492 [ # # ]: 0 : Assert(GinPageIsData(page));
493 : 0 : GinPageGetOpaque(page)->rightlink = data->rightLink;
494 : 0 : PageSetLSN(page, lsn);
495 : 0 : MarkBufferDirty(lbuffer);
496 : 0 : }
497 : :
498 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 0, &dbuffer) == BLK_NEEDS_REDO)
499 : : {
500 : 0 : page = BufferGetPage(dbuffer);
501 [ # # ]: 0 : Assert(GinPageIsData(page));
502 : 0 : GinPageSetDeleted(page);
503 : 0 : GinPageSetDeleteXid(page, data->deleteXid);
504 : 0 : PageSetLSN(page, lsn);
505 : 0 : MarkBufferDirty(dbuffer);
506 : 0 : }
507 : :
508 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 1, &pbuffer) == BLK_NEEDS_REDO)
509 : : {
510 : 0 : page = BufferGetPage(pbuffer);
511 [ # # ]: 0 : Assert(GinPageIsData(page));
512 [ # # ]: 0 : Assert(!GinPageIsLeaf(page));
513 : 0 : GinPageDeletePostingItem(page, data->parentOffset);
514 : 0 : PageSetLSN(page, lsn);
515 : 0 : MarkBufferDirty(pbuffer);
516 : 0 : }
517 : :
518 [ # # ]: 0 : if (BufferIsValid(lbuffer))
519 : 0 : UnlockReleaseBuffer(lbuffer);
520 [ # # ]: 0 : if (BufferIsValid(pbuffer))
521 : 0 : UnlockReleaseBuffer(pbuffer);
522 [ # # ]: 0 : if (BufferIsValid(dbuffer))
523 : 0 : UnlockReleaseBuffer(dbuffer);
524 : 0 : }
525 : :
526 : : static void
527 : 0 : ginRedoUpdateMetapage(XLogReaderState *record)
528 : : {
529 : 0 : XLogRecPtr lsn = record->EndRecPtr;
530 : 0 : ginxlogUpdateMeta *data = (ginxlogUpdateMeta *) XLogRecGetData(record);
531 : 0 : Buffer metabuffer;
532 : 0 : Page metapage;
533 : 0 : Buffer buffer;
534 : :
535 : : /*
536 : : * Restore the metapage. This is essentially the same as a full-page
537 : : * image, so restore the metapage unconditionally without looking at the
538 : : * LSN, to avoid torn page hazards.
539 : : */
540 : 0 : metabuffer = XLogInitBufferForRedo(record, 0);
541 [ # # ]: 0 : Assert(BufferGetBlockNumber(metabuffer) == GIN_METAPAGE_BLKNO);
542 : 0 : metapage = BufferGetPage(metabuffer);
543 : :
544 : 0 : GinInitMetabuffer(metabuffer);
545 : 0 : memcpy(GinPageGetMeta(metapage), &data->metadata, sizeof(GinMetaPageData));
546 : 0 : PageSetLSN(metapage, lsn);
547 : 0 : MarkBufferDirty(metabuffer);
548 : :
549 [ # # ]: 0 : if (data->ntuples > 0)
550 : : {
551 : : /*
552 : : * insert into tail page
553 : : */
554 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
555 : : {
556 : 0 : Page page = BufferGetPage(buffer);
557 : 0 : OffsetNumber off;
558 : 0 : int i;
559 : 0 : Size tupsize;
560 : 0 : char *payload;
561 : 0 : IndexTuple tuples;
562 : 0 : Size totaltupsize;
563 : :
564 : 0 : payload = XLogRecGetBlockData(record, 1, &totaltupsize);
565 : 0 : tuples = (IndexTuple) payload;
566 : :
567 [ # # ]: 0 : if (PageIsEmpty(page))
568 : 0 : off = FirstOffsetNumber;
569 : : else
570 : 0 : off = OffsetNumberNext(PageGetMaxOffsetNumber(page));
571 : :
572 [ # # ]: 0 : for (i = 0; i < data->ntuples; i++)
573 : : {
574 : 0 : tupsize = IndexTupleSize(tuples);
575 : :
576 [ # # ]: 0 : if (PageAddItem(page, tuples, tupsize, off, false, false) == InvalidOffsetNumber)
577 [ # # # # ]: 0 : elog(ERROR, "failed to add item to index page");
578 : :
579 : 0 : tuples = (IndexTuple) (((char *) tuples) + tupsize);
580 : :
581 : 0 : off++;
582 : 0 : }
583 [ # # ]: 0 : Assert(payload + totaltupsize == (char *) tuples);
584 : :
585 : : /*
586 : : * Increase counter of heap tuples
587 : : */
588 : 0 : GinPageGetOpaque(page)->maxoff++;
589 : :
590 : 0 : PageSetLSN(page, lsn);
591 : 0 : MarkBufferDirty(buffer);
592 : 0 : }
593 [ # # ]: 0 : if (BufferIsValid(buffer))
594 : 0 : UnlockReleaseBuffer(buffer);
595 : 0 : }
596 [ # # ]: 0 : else if (data->prevTail != InvalidBlockNumber)
597 : : {
598 : : /*
599 : : * New tail
600 : : */
601 [ # # ]: 0 : if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
602 : : {
603 : 0 : Page page = BufferGetPage(buffer);
604 : :
605 : 0 : GinPageGetOpaque(page)->rightlink = data->newRightlink;
606 : :
607 : 0 : PageSetLSN(page, lsn);
608 : 0 : MarkBufferDirty(buffer);
609 : 0 : }
610 [ # # ]: 0 : if (BufferIsValid(buffer))
611 : 0 : UnlockReleaseBuffer(buffer);
612 : 0 : }
613 : :
614 : 0 : UnlockReleaseBuffer(metabuffer);
615 : 0 : }
616 : :
617 : : static void
618 : 0 : ginRedoInsertListPage(XLogReaderState *record)
619 : : {
620 : 0 : XLogRecPtr lsn = record->EndRecPtr;
621 : 0 : ginxlogInsertListPage *data = (ginxlogInsertListPage *) XLogRecGetData(record);
622 : 0 : Buffer buffer;
623 : 0 : Page page;
624 : 0 : OffsetNumber l,
625 : 0 : off = FirstOffsetNumber;
626 : 0 : int i,
627 : : tupsize;
628 : 0 : char *payload;
629 : 0 : IndexTuple tuples;
630 : 0 : Size totaltupsize;
631 : :
632 : : /* We always re-initialize the page. */
633 : 0 : buffer = XLogInitBufferForRedo(record, 0);
634 : 0 : page = BufferGetPage(buffer);
635 : :
636 : 0 : GinInitBuffer(buffer, GIN_LIST);
637 : 0 : GinPageGetOpaque(page)->rightlink = data->rightlink;
638 [ # # ]: 0 : if (data->rightlink == InvalidBlockNumber)
639 : : {
640 : : /* tail of sublist */
641 : 0 : GinPageSetFullRow(page);
642 : 0 : GinPageGetOpaque(page)->maxoff = 1;
643 : 0 : }
644 : : else
645 : : {
646 : 0 : GinPageGetOpaque(page)->maxoff = 0;
647 : : }
648 : :
649 : 0 : payload = XLogRecGetBlockData(record, 0, &totaltupsize);
650 : :
651 : 0 : tuples = (IndexTuple) payload;
652 [ # # ]: 0 : for (i = 0; i < data->ntuples; i++)
653 : : {
654 : 0 : tupsize = IndexTupleSize(tuples);
655 : :
656 : 0 : l = PageAddItem(page, tuples, tupsize, off, false, false);
657 : :
658 [ # # ]: 0 : if (l == InvalidOffsetNumber)
659 [ # # # # ]: 0 : elog(ERROR, "failed to add item to index page");
660 : :
661 : 0 : tuples = (IndexTuple) (((char *) tuples) + tupsize);
662 : 0 : off++;
663 : 0 : }
664 [ # # ]: 0 : Assert((char *) tuples == payload + totaltupsize);
665 : :
666 : 0 : PageSetLSN(page, lsn);
667 : 0 : MarkBufferDirty(buffer);
668 : :
669 : 0 : UnlockReleaseBuffer(buffer);
670 : 0 : }
671 : :
672 : : static void
673 : 0 : ginRedoDeleteListPages(XLogReaderState *record)
674 : : {
675 : 0 : XLogRecPtr lsn = record->EndRecPtr;
676 : 0 : ginxlogDeleteListPages *data = (ginxlogDeleteListPages *) XLogRecGetData(record);
677 : 0 : Buffer metabuffer;
678 : 0 : Page metapage;
679 : 0 : int i;
680 : :
681 : 0 : metabuffer = XLogInitBufferForRedo(record, 0);
682 [ # # ]: 0 : Assert(BufferGetBlockNumber(metabuffer) == GIN_METAPAGE_BLKNO);
683 : 0 : metapage = BufferGetPage(metabuffer);
684 : :
685 : 0 : GinInitMetabuffer(metabuffer);
686 : :
687 : 0 : memcpy(GinPageGetMeta(metapage), &data->metadata, sizeof(GinMetaPageData));
688 : 0 : PageSetLSN(metapage, lsn);
689 : 0 : MarkBufferDirty(metabuffer);
690 : :
691 : : /*
692 : : * In normal operation, shiftList() takes exclusive lock on all the
693 : : * pages-to-be-deleted simultaneously. During replay, however, it should
694 : : * be all right to lock them one at a time. This is dependent on the fact
695 : : * that we are deleting pages from the head of the list, and that readers
696 : : * share-lock the next page before releasing the one they are on. So we
697 : : * cannot get past a reader that is on, or due to visit, any page we are
698 : : * going to delete. New incoming readers will block behind our metapage
699 : : * lock and then see a fully updated page list.
700 : : *
701 : : * No full-page images are taken of the deleted pages. Instead, they are
702 : : * re-initialized as empty, deleted pages. Their right-links don't need to
703 : : * be preserved, because no new readers can see the pages, as explained
704 : : * above.
705 : : */
706 [ # # ]: 0 : for (i = 0; i < data->ndeleted; i++)
707 : : {
708 : 0 : Buffer buffer;
709 : 0 : Page page;
710 : :
711 : 0 : buffer = XLogInitBufferForRedo(record, i + 1);
712 : 0 : page = BufferGetPage(buffer);
713 : 0 : GinInitBuffer(buffer, GIN_DELETED);
714 : :
715 : 0 : PageSetLSN(page, lsn);
716 : 0 : MarkBufferDirty(buffer);
717 : :
718 : 0 : UnlockReleaseBuffer(buffer);
719 : 0 : }
720 : 0 : UnlockReleaseBuffer(metabuffer);
721 : 0 : }
722 : :
723 : : void
724 : 0 : gin_redo(XLogReaderState *record)
725 : : {
726 : 0 : uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
727 : 0 : MemoryContext oldCtx;
728 : :
729 : : /*
730 : : * GIN indexes do not require any conflict processing. NB: If we ever
731 : : * implement a similar optimization as we have in b-tree, and remove
732 : : * killed tuples outside VACUUM, we'll need to handle that here.
733 : : */
734 : :
735 : 0 : oldCtx = MemoryContextSwitchTo(opCtx);
736 [ # # # # : 0 : switch (info)
# # # # #
# ]
737 : : {
738 : : case XLOG_GIN_CREATE_PTREE:
739 : 0 : ginRedoCreatePTree(record);
740 : 0 : break;
741 : : case XLOG_GIN_INSERT:
742 : 0 : ginRedoInsert(record);
743 : 0 : break;
744 : : case XLOG_GIN_SPLIT:
745 : 0 : ginRedoSplit(record);
746 : 0 : break;
747 : : case XLOG_GIN_VACUUM_PAGE:
748 : 0 : ginRedoVacuumPage(record);
749 : 0 : break;
750 : : case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
751 : 0 : ginRedoVacuumDataLeafPage(record);
752 : 0 : break;
753 : : case XLOG_GIN_DELETE_PAGE:
754 : 0 : ginRedoDeletePage(record);
755 : 0 : break;
756 : : case XLOG_GIN_UPDATE_META_PAGE:
757 : 0 : ginRedoUpdateMetapage(record);
758 : 0 : break;
759 : : case XLOG_GIN_INSERT_LISTPAGE:
760 : 0 : ginRedoInsertListPage(record);
761 : 0 : break;
762 : : case XLOG_GIN_DELETE_LISTPAGE:
763 : 0 : ginRedoDeleteListPages(record);
764 : 0 : break;
765 : : default:
766 [ # # # # ]: 0 : elog(PANIC, "gin_redo: unknown op code %u", info);
767 : 0 : }
768 : 0 : MemoryContextSwitchTo(oldCtx);
769 : 0 : MemoryContextReset(opCtx);
770 : 0 : }
771 : :
772 : : void
773 : 0 : gin_xlog_startup(void)
774 : : {
775 : 0 : opCtx = AllocSetContextCreate(CurrentMemoryContext,
776 : : "GIN recovery temporary context",
777 : : ALLOCSET_DEFAULT_SIZES);
778 : 0 : }
779 : :
780 : : void
781 : 0 : gin_xlog_cleanup(void)
782 : : {
783 : 0 : MemoryContextDelete(opCtx);
784 : 0 : opCtx = NULL;
785 : 0 : }
786 : :
787 : : /*
788 : : * Mask a GIN page before running consistency checks on it.
789 : : */
790 : : void
791 : 0 : gin_mask(char *pagedata, BlockNumber blkno)
792 : : {
793 : 0 : Page page = (Page) pagedata;
794 : 0 : PageHeader pagehdr = (PageHeader) page;
795 : 0 : GinPageOpaque opaque;
796 : :
797 : 0 : mask_page_lsn_and_checksum(page);
798 : 0 : opaque = GinPageGetOpaque(page);
799 : :
800 : 0 : mask_page_hint_bits(page);
801 : :
802 : : /*
803 : : * For a GIN_DELETED page, the page is initialized to empty. Hence, mask
804 : : * the whole page content. For other pages, mask the hole if pd_lower
805 : : * appears to have been set correctly.
806 : : */
807 [ # # ]: 0 : if (opaque->flags & GIN_DELETED)
808 : 0 : mask_page_content(page);
809 [ # # ]: 0 : else if (pagehdr->pd_lower > SizeOfPageHeaderData)
810 : 0 : mask_unused_space(page);
811 : 0 : }
|