/src/postgres/src/backend/access/gin/ginxlog.c
Line | Count | Source |
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 | | ginRedoClearIncompleteSplit(XLogReaderState *record, uint8 block_id) |
26 | 0 | { |
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 | 0 | { |
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 | | ginRedoCreatePTree(XLogReaderState *record) |
45 | 0 | { |
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 | | ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata) |
72 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | 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 | | ginRedoRecompress(Page page, ginxlogRecompressDataLeaf *data) |
118 | 0 | { |
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 | 0 | { |
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 | 0 | { |
146 | 0 | GinPostingList *plist; |
147 | |
|
148 | 0 | plist = ginCompressPostingList(uncompressed, nuncompressed, |
149 | 0 | BLCKSZ, &npacked); |
150 | 0 | totalsize = SizeOfGinPostingList(plist); |
151 | |
|
152 | 0 | Assert(npacked == nuncompressed); |
153 | |
|
154 | 0 | memcpy(GinDataLeafPageGetPostingList(page), plist, totalsize); |
155 | 0 | } |
156 | 0 | else |
157 | 0 | { |
158 | 0 | totalsize = 0; |
159 | 0 | } |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | } |
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 | 0 | { |
229 | 0 | int npacked; |
230 | |
|
231 | 0 | olditems = ginPostingListDecode(oldseg, &nolditems); |
232 | |
|
233 | 0 | newitems = ginMergeItemPointers(items, nitems, |
234 | 0 | olditems, nolditems, |
235 | 0 | &nnewitems); |
236 | 0 | Assert(nnewitems == nolditems + nitems); |
237 | |
|
238 | 0 | newseg = ginCompressPostingList(newitems, nnewitems, |
239 | 0 | 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 | 0 | else |
250 | 0 | { |
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 | 0 | } |
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 | 0 | { |
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 | 0 | { |
276 | 0 | case GIN_SEGMENT_DELETE: |
277 | 0 | segptr += segsize; |
278 | 0 | segno++; |
279 | 0 | break; |
280 | | |
281 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | { |
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 | | ginRedoInsertData(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata) |
320 | 0 | { |
321 | 0 | Page page = BufferGetPage(buffer); |
322 | |
|
323 | 0 | if (isLeaf) |
324 | 0 | { |
325 | 0 | ginxlogRecompressDataLeaf *data = (ginxlogRecompressDataLeaf *) rdata; |
326 | |
|
327 | 0 | Assert(GinPageIsLeaf(page)); |
328 | |
|
329 | 0 | ginRedoRecompress(page, data); |
330 | 0 | } |
331 | 0 | else |
332 | 0 | { |
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 | | ginRedoInsert(XLogReaderState *record) |
348 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
384 | 0 | Assert(GinPageIsData(page)); |
385 | 0 | ginRedoInsertData(buffer, isLeaf, rightChildBlkno, payload); |
386 | 0 | } |
387 | 0 | else |
388 | 0 | { |
389 | 0 | Assert(!GinPageIsData(page)); |
390 | 0 | ginRedoInsertEntry(buffer, isLeaf, rightChildBlkno, payload); |
391 | 0 | } |
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 | | ginRedoSplit(XLogReaderState *record) |
402 | 0 | { |
403 | 0 | ginxlogSplit *data = (ginxlogSplit *) XLogRecGetData(record); |
404 | 0 | Buffer lbuffer, |
405 | 0 | rbuffer, |
406 | 0 | 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 | 0 | { |
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 | | ginRedoVacuumPage(XLogReaderState *record) |
440 | 0 | { |
441 | 0 | Buffer buffer; |
442 | |
|
443 | 0 | if (XLogReadBufferForRedo(record, 0, &buffer) != BLK_RESTORED) |
444 | 0 | { |
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 | | ginRedoVacuumDataLeafPage(XLogReaderState *record) |
452 | 0 | { |
453 | 0 | XLogRecPtr lsn = record->EndRecPtr; |
454 | 0 | Buffer buffer; |
455 | |
|
456 | 0 | if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO) |
457 | 0 | { |
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 | | ginRedoDeletePage(XLogReaderState *record) |
477 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | 0 | { |
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 | | ginRedoUpdateMetapage(XLogReaderState *record) |
528 | 0 | { |
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 | 0 | { |
551 | | /* |
552 | | * insert into tail page |
553 | | */ |
554 | 0 | if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO) |
555 | 0 | { |
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 | 0 | else |
570 | 0 | off = OffsetNumberNext(PageGetMaxOffsetNumber(page)); |
571 | |
|
572 | 0 | for (i = 0; i < data->ntuples; i++) |
573 | 0 | { |
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 | 0 | { |
598 | | /* |
599 | | * New tail |
600 | | */ |
601 | 0 | if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO) |
602 | 0 | { |
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 | | ginRedoInsertListPage(XLogReaderState *record) |
619 | 0 | { |
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 | 0 | 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 | 0 | { |
640 | | /* tail of sublist */ |
641 | 0 | GinPageSetFullRow(page); |
642 | 0 | GinPageGetOpaque(page)->maxoff = 1; |
643 | 0 | } |
644 | 0 | else |
645 | 0 | { |
646 | 0 | GinPageGetOpaque(page)->maxoff = 0; |
647 | 0 | } |
648 | |
|
649 | 0 | payload = XLogRecGetBlockData(record, 0, &totaltupsize); |
650 | |
|
651 | 0 | tuples = (IndexTuple) payload; |
652 | 0 | for (i = 0; i < data->ntuples; i++) |
653 | 0 | { |
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 | | ginRedoDeleteListPages(XLogReaderState *record) |
674 | 0 | { |
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 | 0 | { |
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 | | gin_redo(XLogReaderState *record) |
725 | 0 | { |
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 | 0 | { |
738 | 0 | case XLOG_GIN_CREATE_PTREE: |
739 | 0 | ginRedoCreatePTree(record); |
740 | 0 | break; |
741 | 0 | case XLOG_GIN_INSERT: |
742 | 0 | ginRedoInsert(record); |
743 | 0 | break; |
744 | 0 | case XLOG_GIN_SPLIT: |
745 | 0 | ginRedoSplit(record); |
746 | 0 | break; |
747 | 0 | case XLOG_GIN_VACUUM_PAGE: |
748 | 0 | ginRedoVacuumPage(record); |
749 | 0 | break; |
750 | 0 | case XLOG_GIN_VACUUM_DATA_LEAF_PAGE: |
751 | 0 | ginRedoVacuumDataLeafPage(record); |
752 | 0 | break; |
753 | 0 | case XLOG_GIN_DELETE_PAGE: |
754 | 0 | ginRedoDeletePage(record); |
755 | 0 | break; |
756 | 0 | case XLOG_GIN_UPDATE_META_PAGE: |
757 | 0 | ginRedoUpdateMetapage(record); |
758 | 0 | break; |
759 | 0 | case XLOG_GIN_INSERT_LISTPAGE: |
760 | 0 | ginRedoInsertListPage(record); |
761 | 0 | break; |
762 | 0 | case XLOG_GIN_DELETE_LISTPAGE: |
763 | 0 | ginRedoDeleteListPages(record); |
764 | 0 | break; |
765 | 0 | 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 | | gin_xlog_startup(void) |
774 | 0 | { |
775 | 0 | opCtx = AllocSetContextCreate(CurrentMemoryContext, |
776 | 0 | "GIN recovery temporary context", |
777 | 0 | ALLOCSET_DEFAULT_SIZES); |
778 | 0 | } |
779 | | |
780 | | void |
781 | | gin_xlog_cleanup(void) |
782 | 0 | { |
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 | | gin_mask(char *pagedata, BlockNumber blkno) |
792 | 0 | { |
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 | } |