Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_visibility.c
4 : : * display visibility map information and page-level visibility bits
5 : : *
6 : : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 : : *
8 : : * contrib/pg_visibility/pg_visibility.c
9 : : *-------------------------------------------------------------------------
10 : : */
11 : : #include "postgres.h"
12 : :
13 : : #include "access/heapam.h"
14 : : #include "access/htup_details.h"
15 : : #include "access/visibilitymap.h"
16 : : #include "access/xlog.h"
17 : : #include "access/xloginsert.h"
18 : : #include "catalog/pg_type.h"
19 : : #include "catalog/storage_xlog.h"
20 : : #include "funcapi.h"
21 : : #include "miscadmin.h"
22 : : #include "storage/bufmgr.h"
23 : : #include "storage/proc.h"
24 : : #include "storage/procarray.h"
25 : : #include "storage/read_stream.h"
26 : : #include "storage/smgr.h"
27 : : #include "utils/rel.h"
28 : :
29 : 37 : PG_MODULE_MAGIC_EXT(
30 : : .name = "pg_visibility",
31 : : .version = PG_VERSION
32 : : );
33 : :
34 : : typedef struct vbits
35 : : {
36 : : BlockNumber next;
37 : : BlockNumber count;
38 : : pg_attribute_counted_by(count) uint8 bits[FLEXIBLE_ARRAY_MEMBER];
39 : : } vbits;
40 : :
41 : : typedef struct corrupt_items
42 : : {
43 : : BlockNumber next;
44 : : BlockNumber count;
45 : : ItemPointer tids;
46 : : } corrupt_items;
47 : :
48 : : /* for collect_corrupt_items_read_stream_next_block */
49 : : struct collect_corrupt_items_read_stream_private
50 : : {
51 : : bool all_frozen;
52 : : bool all_visible;
53 : : BlockNumber current_blocknum;
54 : : BlockNumber last_exclusive;
55 : : Relation rel;
56 : : Buffer vmbuffer;
57 : : };
58 : :
59 : 8 : PG_FUNCTION_INFO_V1(pg_visibility_map);
60 : 6 : PG_FUNCTION_INFO_V1(pg_visibility_map_rel);
61 : 6 : PG_FUNCTION_INFO_V1(pg_visibility);
62 : 6 : PG_FUNCTION_INFO_V1(pg_visibility_rel);
63 : 26 : PG_FUNCTION_INFO_V1(pg_visibility_map_summary);
64 : 7 : PG_FUNCTION_INFO_V1(pg_check_frozen);
65 : 14 : PG_FUNCTION_INFO_V1(pg_check_visible);
66 : 6 : PG_FUNCTION_INFO_V1(pg_truncate_visibility_map);
67 : :
68 : : static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd);
69 : : static vbits *collect_visibility_data(Oid relid, bool include_pd);
70 : : static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
71 : : bool all_frozen);
72 : : static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
73 : : static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
74 : : Buffer buffer);
75 : : static void check_relation_supported(Relation rel);
76 : :
77 : : /*
78 : : * Visibility map information for a single block of a relation.
79 : : *
80 : : * Note: the VM code will silently return zeroes for pages past the end
81 : : * of the map, so we allow probes up to MaxBlockNumber regardless of the
82 : : * actual relation size.
83 : : */
84 : : Datum
85 : 24 : pg_visibility_map(PG_FUNCTION_ARGS)
86 : : {
87 : 24 : Oid relid = PG_GETARG_OID(0);
88 : 24 : int64 blkno = PG_GETARG_INT64(1);
89 : : int32 mapbits;
90 : : Relation rel;
91 : 24 : Buffer vmbuffer = InvalidBuffer;
92 : : TupleDesc tupdesc;
93 : : Datum values[2];
94 : 24 : bool nulls[2] = {0};
95 : :
96 : 24 : rel = relation_open(relid, AccessShareLock);
97 : :
98 : : /* Only some relkinds have a visibility map */
99 : 24 : check_relation_supported(rel);
100 : :
101 [ + - - + ]: 24 : if (blkno < 0 || blkno > MaxBlockNumber)
102 [ # # ]: 0 : ereport(ERROR,
103 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
104 : : errmsg("invalid block number")));
105 : :
106 : 24 : tupdesc = pg_visibility_tupdesc(false, false);
107 : :
108 : 24 : mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
109 [ + - ]: 24 : if (vmbuffer != InvalidBuffer)
110 : 24 : ReleaseBuffer(vmbuffer);
111 : 24 : values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
112 : 24 : values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);
113 : :
114 : 24 : relation_close(rel, AccessShareLock);
115 : :
116 : 24 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
117 : : }
118 : :
119 : : /*
120 : : * Visibility map information for a single block of a relation, plus the
121 : : * page-level information for the same block.
122 : : */
123 : : Datum
124 : 6 : pg_visibility(PG_FUNCTION_ARGS)
125 : : {
126 : 6 : Oid relid = PG_GETARG_OID(0);
127 : 6 : int64 blkno = PG_GETARG_INT64(1);
128 : : int32 mapbits;
129 : : Relation rel;
130 : 6 : Buffer vmbuffer = InvalidBuffer;
131 : : Buffer buffer;
132 : : Page page;
133 : : TupleDesc tupdesc;
134 : : Datum values[3];
135 : 6 : bool nulls[3] = {0};
136 : :
137 : 6 : rel = relation_open(relid, AccessShareLock);
138 : :
139 : : /* Only some relkinds have a visibility map */
140 : 6 : check_relation_supported(rel);
141 : :
142 [ + - - + ]: 1 : if (blkno < 0 || blkno > MaxBlockNumber)
143 [ # # ]: 0 : ereport(ERROR,
144 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
145 : : errmsg("invalid block number")));
146 : :
147 : 1 : tupdesc = pg_visibility_tupdesc(false, true);
148 : :
149 : 1 : mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
150 [ + - ]: 1 : if (vmbuffer != InvalidBuffer)
151 : 1 : ReleaseBuffer(vmbuffer);
152 : 1 : values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
153 : 1 : values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);
154 : :
155 : : /* Here we have to explicitly check rel size ... */
156 [ + - ]: 1 : if (blkno < RelationGetNumberOfBlocks(rel))
157 : : {
158 : 1 : buffer = ReadBuffer(rel, blkno);
159 : 1 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
160 : :
161 : 1 : page = BufferGetPage(buffer);
162 : 1 : values[2] = BoolGetDatum(PageIsAllVisible(page));
163 : :
164 : 1 : UnlockReleaseBuffer(buffer);
165 : : }
166 : : else
167 : : {
168 : : /* As with the vismap, silently return 0 for pages past EOF */
169 : 0 : values[2] = BoolGetDatum(false);
170 : : }
171 : :
172 : 1 : relation_close(rel, AccessShareLock);
173 : :
174 : 1 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
175 : : }
176 : :
177 : : /*
178 : : * Visibility map information for every block in a relation.
179 : : */
180 : : Datum
181 : 20 : pg_visibility_map_rel(PG_FUNCTION_ARGS)
182 : : {
183 : : FuncCallContext *funcctx;
184 : : vbits *info;
185 : :
186 [ + + ]: 20 : if (SRF_IS_FIRSTCALL())
187 : : {
188 : 11 : Oid relid = PG_GETARG_OID(0);
189 : : MemoryContext oldcontext;
190 : :
191 : 11 : funcctx = SRF_FIRSTCALL_INIT();
192 : 11 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
193 : 11 : funcctx->tuple_desc = pg_visibility_tupdesc(true, false);
194 : : /* collect_visibility_data will verify the relkind */
195 : 11 : funcctx->user_fctx = collect_visibility_data(relid, false);
196 : 4 : MemoryContextSwitchTo(oldcontext);
197 : : }
198 : :
199 : 13 : funcctx = SRF_PERCALL_SETUP();
200 : 13 : info = (vbits *) funcctx->user_fctx;
201 : :
202 [ + + ]: 13 : if (info->next < info->count)
203 : : {
204 : : Datum values[3];
205 : 9 : bool nulls[3] = {0};
206 : : HeapTuple tuple;
207 : :
208 : 9 : values[0] = Int64GetDatum(info->next);
209 : 9 : values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0);
210 : 9 : values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0);
211 : 9 : info->next++;
212 : :
213 : 9 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
214 : 9 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
215 : : }
216 : :
217 : 4 : SRF_RETURN_DONE(funcctx);
218 : : }
219 : :
220 : : /*
221 : : * Visibility map information for every block in a relation, plus the page
222 : : * level information for each block.
223 : : */
224 : : Datum
225 : 9 : pg_visibility_rel(PG_FUNCTION_ARGS)
226 : : {
227 : : FuncCallContext *funcctx;
228 : : vbits *info;
229 : :
230 [ + + ]: 9 : if (SRF_IS_FIRSTCALL())
231 : : {
232 : 6 : Oid relid = PG_GETARG_OID(0);
233 : : MemoryContext oldcontext;
234 : :
235 : 6 : funcctx = SRF_FIRSTCALL_INIT();
236 : 6 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
237 : 6 : funcctx->tuple_desc = pg_visibility_tupdesc(true, true);
238 : : /* collect_visibility_data will verify the relkind */
239 : 6 : funcctx->user_fctx = collect_visibility_data(relid, true);
240 : 6 : MemoryContextSwitchTo(oldcontext);
241 : : }
242 : :
243 : 9 : funcctx = SRF_PERCALL_SETUP();
244 : 9 : info = (vbits *) funcctx->user_fctx;
245 : :
246 [ + + ]: 9 : if (info->next < info->count)
247 : : {
248 : : Datum values[4];
249 : 3 : bool nulls[4] = {0};
250 : : HeapTuple tuple;
251 : :
252 : 3 : values[0] = Int64GetDatum(info->next);
253 : 3 : values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0);
254 : 3 : values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0);
255 : 3 : values[3] = BoolGetDatum((info->bits[info->next] & (1 << 2)) != 0);
256 : 3 : info->next++;
257 : :
258 : 3 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
259 : 3 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
260 : : }
261 : :
262 : 6 : SRF_RETURN_DONE(funcctx);
263 : : }
264 : :
265 : : /*
266 : : * Count the number of all-visible and all-frozen pages in the visibility
267 : : * map for a particular relation.
268 : : */
269 : : Datum
270 : 34 : pg_visibility_map_summary(PG_FUNCTION_ARGS)
271 : : {
272 : 34 : Oid relid = PG_GETARG_OID(0);
273 : : Relation rel;
274 : 34 : BlockNumber all_visible = 0;
275 : 34 : BlockNumber all_frozen = 0;
276 : : TupleDesc tupdesc;
277 : : Datum values[2];
278 : 34 : bool nulls[2] = {0};
279 : :
280 : 34 : rel = relation_open(relid, AccessShareLock);
281 : :
282 : : /* Only some relkinds have a visibility map */
283 : 34 : check_relation_supported(rel);
284 : :
285 : 29 : visibilitymap_count(rel, &all_visible, &all_frozen);
286 : :
287 : 29 : relation_close(rel, AccessShareLock);
288 : :
289 [ - + ]: 29 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
290 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
291 : :
292 : 29 : values[0] = Int64GetDatum((int64) all_visible);
293 : 29 : values[1] = Int64GetDatum((int64) all_frozen);
294 : :
295 : 29 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
296 : : }
297 : :
298 : : /*
299 : : * Return the TIDs of non-frozen tuples present in pages marked all-frozen
300 : : * in the visibility map. We hope no one will ever find any, but there could
301 : : * be bugs, database corruption, etc.
302 : : */
303 : : Datum
304 : 15 : pg_check_frozen(PG_FUNCTION_ARGS)
305 : : {
306 : : FuncCallContext *funcctx;
307 : : corrupt_items *items;
308 : :
309 [ + + ]: 15 : if (SRF_IS_FIRSTCALL())
310 : : {
311 : 10 : Oid relid = PG_GETARG_OID(0);
312 : : MemoryContext oldcontext;
313 : :
314 : 10 : funcctx = SRF_FIRSTCALL_INIT();
315 : 10 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
316 : : /* collect_corrupt_items will verify the relkind */
317 : 10 : funcctx->user_fctx = collect_corrupt_items(relid, false, true);
318 : 5 : MemoryContextSwitchTo(oldcontext);
319 : : }
320 : :
321 : 10 : funcctx = SRF_PERCALL_SETUP();
322 : 10 : items = (corrupt_items *) funcctx->user_fctx;
323 : :
324 [ + + ]: 10 : if (items->next < items->count)
325 : 5 : SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++]));
326 : :
327 : 5 : SRF_RETURN_DONE(funcctx);
328 : : }
329 : :
330 : : /*
331 : : * Return the TIDs of not-all-visible tuples in pages marked all-visible
332 : : * in the visibility map. We hope no one will ever find any, but there could
333 : : * be bugs, database corruption, etc.
334 : : */
335 : : Datum
336 : 15 : pg_check_visible(PG_FUNCTION_ARGS)
337 : : {
338 : : FuncCallContext *funcctx;
339 : : corrupt_items *items;
340 : :
341 [ + + ]: 15 : if (SRF_IS_FIRSTCALL())
342 : : {
343 : 10 : Oid relid = PG_GETARG_OID(0);
344 : : MemoryContext oldcontext;
345 : :
346 : 10 : funcctx = SRF_FIRSTCALL_INIT();
347 : 10 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
348 : : /* collect_corrupt_items will verify the relkind */
349 : 10 : funcctx->user_fctx = collect_corrupt_items(relid, true, false);
350 : 10 : MemoryContextSwitchTo(oldcontext);
351 : : }
352 : :
353 : 15 : funcctx = SRF_PERCALL_SETUP();
354 : 15 : items = (corrupt_items *) funcctx->user_fctx;
355 : :
356 [ + + ]: 15 : if (items->next < items->count)
357 : 5 : SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++]));
358 : :
359 : 10 : SRF_RETURN_DONE(funcctx);
360 : : }
361 : :
362 : : /*
363 : : * Remove the visibility map fork for a relation. If there turn out to be
364 : : * any bugs in the visibility map code that require rebuilding the VM, this
365 : : * provides users with a way to do it that is cleaner than shutting down the
366 : : * server and removing files by hand.
367 : : *
368 : : * This is a cut-down version of RelationTruncate.
369 : : */
370 : : Datum
371 : 7 : pg_truncate_visibility_map(PG_FUNCTION_ARGS)
372 : : {
373 : 7 : Oid relid = PG_GETARG_OID(0);
374 : : Relation rel;
375 : : ForkNumber fork;
376 : : BlockNumber block;
377 : : BlockNumber old_block;
378 : :
379 : 7 : rel = relation_open(relid, AccessExclusiveLock);
380 : :
381 : : /* Only some relkinds have a visibility map */
382 : 7 : check_relation_supported(rel);
383 : :
384 : : /* Forcibly reset cached file size */
385 : 2 : RelationGetSmgr(rel)->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = InvalidBlockNumber;
386 : :
387 : : /* Compute new and old size before entering critical section. */
388 : 2 : fork = VISIBILITYMAP_FORKNUM;
389 : 2 : block = visibilitymap_prepare_truncate(rel, 0);
390 [ + - ]: 2 : old_block = BlockNumberIsValid(block) ? smgrnblocks(RelationGetSmgr(rel), fork) : 0;
391 : :
392 : : /*
393 : : * WAL-logging, buffer dropping, file truncation must be atomic and all on
394 : : * one side of a checkpoint. See RelationTruncate() for discussion.
395 : : */
396 : : Assert((MyProc->delayChkptFlags & (DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE)) == 0);
397 : 2 : MyProc->delayChkptFlags |= DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE;
398 : 2 : START_CRIT_SECTION();
399 : :
400 [ + + - + : 2 : if (RelationNeedsWAL(rel))
- - - - ]
401 : : {
402 : : XLogRecPtr lsn;
403 : : xl_smgr_truncate xlrec;
404 : :
405 : 1 : xlrec.blkno = 0;
406 : 1 : xlrec.rlocator = rel->rd_locator;
407 : 1 : xlrec.flags = SMGR_TRUNCATE_VM;
408 : :
409 : 1 : XLogBeginInsert();
410 : 1 : XLogRegisterData(&xlrec, sizeof(xlrec));
411 : :
412 : 1 : lsn = XLogInsert(RM_SMGR_ID,
413 : : XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
414 : 1 : XLogFlush(lsn);
415 : : }
416 : :
417 [ + - ]: 2 : if (BlockNumberIsValid(block))
418 : 2 : smgrtruncate(RelationGetSmgr(rel), &fork, 1, &old_block, &block);
419 : :
420 : 2 : END_CRIT_SECTION();
421 : 2 : MyProc->delayChkptFlags &= ~(DELAY_CHKPT_START | DELAY_CHKPT_COMPLETE);
422 : :
423 : : /*
424 : : * Release the lock right away, not at commit time.
425 : : *
426 : : * It would be a problem to release the lock prior to commit if this
427 : : * truncate operation sends any transactional invalidation messages. Other
428 : : * backends would potentially be able to lock the relation without
429 : : * processing them in the window of time between when we release the lock
430 : : * here and when we sent the messages at our eventual commit. However,
431 : : * we're currently only sending a non-transactional smgr invalidation,
432 : : * which will have been posted to shared memory immediately from within
433 : : * smgr_truncate. Therefore, there should be no race here.
434 : : *
435 : : * The reason why it's desirable to release the lock early here is because
436 : : * of the possibility that someone will need to use this to blow away many
437 : : * visibility map forks at once. If we can't release the lock until
438 : : * commit time, the transaction doing this will accumulate
439 : : * AccessExclusiveLocks on all of those relations at the same time, which
440 : : * is undesirable. However, if this turns out to be unsafe we may have no
441 : : * choice...
442 : : */
443 : 2 : relation_close(rel, AccessExclusiveLock);
444 : :
445 : : /* Nothing to return. */
446 : 2 : PG_RETURN_VOID();
447 : : }
448 : :
449 : : /*
450 : : * Helper function to construct whichever TupleDesc we need for a particular
451 : : * call.
452 : : */
453 : : static TupleDesc
454 : 42 : pg_visibility_tupdesc(bool include_blkno, bool include_pd)
455 : : {
456 : : TupleDesc tupdesc;
457 : 42 : AttrNumber maxattr = 2;
458 : 42 : AttrNumber a = 0;
459 : :
460 [ + + ]: 42 : if (include_blkno)
461 : 17 : ++maxattr;
462 [ + + ]: 42 : if (include_pd)
463 : 7 : ++maxattr;
464 : 42 : tupdesc = CreateTemplateTupleDesc(maxattr);
465 [ + + ]: 42 : if (include_blkno)
466 : 17 : TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
467 : 42 : TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1, 0);
468 : 42 : TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1, 0);
469 [ + + ]: 42 : if (include_pd)
470 : 7 : TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0);
471 : : Assert(a == maxattr);
472 : :
473 : 42 : TupleDescFinalize(tupdesc);
474 : :
475 : 42 : return BlessTupleDesc(tupdesc);
476 : : }
477 : :
478 : : /*
479 : : * Collect visibility data about a relation.
480 : : *
481 : : * Checks relkind of relid and will throw an error if the relation does not
482 : : * have a VM.
483 : : */
484 : : static vbits *
485 : 17 : collect_visibility_data(Oid relid, bool include_pd)
486 : : {
487 : : Relation rel;
488 : : BlockNumber nblocks;
489 : : vbits *info;
490 : : BlockNumber blkno;
491 : 17 : Buffer vmbuffer = InvalidBuffer;
492 : 17 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
493 : : BlockRangeReadStreamPrivate p;
494 : 17 : ReadStream *stream = NULL;
495 : :
496 : 17 : rel = relation_open(relid, AccessShareLock);
497 : :
498 : : /* Only some relkinds have a visibility map */
499 : 15 : check_relation_supported(rel);
500 : :
501 : 10 : nblocks = RelationGetNumberOfBlocks(rel);
502 : 10 : info = palloc0(offsetof(vbits, bits) + nblocks);
503 : 10 : info->next = 0;
504 : 10 : info->count = nblocks;
505 : :
506 : : /* Create a stream if reading main fork. */
507 [ + + ]: 10 : if (include_pd)
508 : : {
509 : 6 : p.current_blocknum = 0;
510 : 6 : p.last_exclusive = nblocks;
511 : :
512 : : /*
513 : : * It is safe to use batchmode as block_range_read_stream_cb takes no
514 : : * locks.
515 : : */
516 : 6 : stream = read_stream_begin_relation(READ_STREAM_FULL |
517 : : READ_STREAM_USE_BATCHING,
518 : : bstrategy,
519 : : rel,
520 : : MAIN_FORKNUM,
521 : : block_range_read_stream_cb,
522 : : &p,
523 : : 0);
524 : : }
525 : :
526 [ + + ]: 22 : for (blkno = 0; blkno < nblocks; ++blkno)
527 : : {
528 : : int32 mapbits;
529 : :
530 : : /* Make sure we are interruptible. */
531 [ - + ]: 12 : CHECK_FOR_INTERRUPTS();
532 : :
533 : : /* Get map info. */
534 : 12 : mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
535 [ + + ]: 12 : if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
536 : 8 : info->bits[blkno] |= (1 << 0);
537 [ + + ]: 12 : if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
538 : 5 : info->bits[blkno] |= (1 << 1);
539 : :
540 : : /*
541 : : * Page-level data requires reading every block, so only get it if the
542 : : * caller needs it. Use a buffer access strategy, too, to prevent
543 : : * cache-trashing.
544 : : */
545 [ + + ]: 12 : if (include_pd)
546 : : {
547 : : Buffer buffer;
548 : : Page page;
549 : :
550 : 3 : buffer = read_stream_next_buffer(stream, NULL);
551 : 3 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
552 : :
553 : 3 : page = BufferGetPage(buffer);
554 [ + + ]: 3 : if (PageIsAllVisible(page))
555 : 2 : info->bits[blkno] |= (1 << 2);
556 : :
557 : 3 : UnlockReleaseBuffer(buffer);
558 : : }
559 : : }
560 : :
561 [ + + ]: 10 : if (include_pd)
562 : : {
563 : : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
564 : 6 : read_stream_end(stream);
565 : : }
566 : :
567 : : /* Clean up. */
568 [ + + ]: 10 : if (vmbuffer != InvalidBuffer)
569 : 7 : ReleaseBuffer(vmbuffer);
570 : 10 : relation_close(rel, AccessShareLock);
571 : :
572 : 10 : return info;
573 : : }
574 : :
575 : : /*
576 : : * The "strict" version of GetOldestNonRemovableTransactionId(). The
577 : : * pg_visibility check can tolerate false positives (don't report some of the
578 : : * errors), but can't tolerate false negatives (report false errors). Normally,
579 : : * horizons move forwards, but there are cases when it could move backward
580 : : * (see comment for ComputeXidHorizons()).
581 : : *
582 : : * This is why we have to implement our own function for xid horizon, which
583 : : * would be guaranteed to be newer or equal to any xid horizon computed before.
584 : : * We have to do the following to achieve this.
585 : : *
586 : : * 1. Ignore processes xmin's, because they consider connection to other
587 : : * databases that were ignored before.
588 : : * 2. Ignore KnownAssignedXids, as they are not database-aware. Although we
589 : : * now perform minimal checking on a standby by always using nextXid, this
590 : : * approach is better than nothing and will at least catch extremely broken
591 : : * cases where a xid is in the future.
592 : : * 3. Ignore walsender xmin, because it could go backward if some replication
593 : : * connections don't use replication slots.
594 : : *
595 : : * While it might seem like we could use KnownAssignedXids for shared
596 : : * catalogs, since shared catalogs rely on a global horizon rather than a
597 : : * database-specific one - there are potential edge cases. For example, a
598 : : * transaction may crash on the primary without writing a commit/abort record.
599 : : * This would lead to a situation where it appears to still be running on the
600 : : * standby, even though it has already ended on the primary. For this reason,
601 : : * it's safer to ignore KnownAssignedXids, even for shared catalogs.
602 : : *
603 : : * As a result, we're using only currently running xids to compute the horizon.
604 : : * Surely these would significantly sacrifice accuracy. But we have to do so
605 : : * to avoid reporting false errors.
606 : : */
607 : : static TransactionId
608 : 15 : GetStrictOldestNonRemovableTransactionId(Relation rel)
609 : : {
610 : : RunningTransactions runningTransactions;
611 : :
612 [ + + ]: 15 : if (RecoveryInProgress())
613 : : {
614 : : TransactionId result;
615 : :
616 : : /* As we ignore KnownAssignedXids on standby, just pick nextXid */
617 : 1 : LWLockAcquire(XidGenLock, LW_SHARED);
618 : 1 : result = XidFromFullTransactionId(TransamVariables->nextXid);
619 : 1 : LWLockRelease(XidGenLock);
620 : 1 : return result;
621 : : }
622 [ + - - + ]: 14 : else if (rel == NULL || rel->rd_rel->relisshared)
623 : : {
624 : : /* Shared relation: take into account all running xids */
625 : 0 : runningTransactions = GetRunningTransactionData();
626 : 0 : LWLockRelease(ProcArrayLock);
627 : 0 : LWLockRelease(XidGenLock);
628 : 0 : return runningTransactions->oldestRunningXid;
629 : : }
630 [ + + + - ]: 14 : else if (!RELATION_IS_LOCAL(rel))
631 : : {
632 : : /*
633 : : * Normal relation: take into account xids running within the current
634 : : * database
635 : : */
636 : 12 : runningTransactions = GetRunningTransactionData();
637 : 12 : LWLockRelease(ProcArrayLock);
638 : 12 : LWLockRelease(XidGenLock);
639 : 12 : return runningTransactions->oldestDatabaseRunningXid;
640 : : }
641 : : else
642 : : {
643 : : /*
644 : : * For temporary relations, ComputeXidHorizons() uses only
645 : : * TransamVariables->latestCompletedXid and MyProc->xid. These two
646 : : * shouldn't go backwards. So we're fine with this horizon.
647 : : */
648 : 2 : return GetOldestNonRemovableTransactionId(rel);
649 : : }
650 : : }
651 : :
652 : : /*
653 : : * Callback function to get next block for read stream object used in
654 : : * collect_corrupt_items() function.
655 : : */
656 : : static BlockNumber
657 : 129 : collect_corrupt_items_read_stream_next_block(ReadStream *stream,
658 : : void *callback_private_data,
659 : : void *per_buffer_data)
660 : : {
661 : 129 : struct collect_corrupt_items_read_stream_private *p = callback_private_data;
662 : :
663 [ + + ]: 142 : for (; p->current_blocknum < p->last_exclusive; p->current_blocknum++)
664 : : {
665 : 127 : bool check_frozen = false;
666 : 127 : bool check_visible = false;
667 : :
668 : : /* Make sure we are interruptible. */
669 [ - + ]: 127 : CHECK_FOR_INTERRUPTS();
670 : :
671 [ + + + + ]: 127 : if (p->all_frozen && VM_ALL_FROZEN(p->rel, p->current_blocknum, &p->vmbuffer))
672 : 49 : check_frozen = true;
673 [ + + + + ]: 127 : if (p->all_visible && VM_ALL_VISIBLE(p->rel, p->current_blocknum, &p->vmbuffer))
674 : 65 : check_visible = true;
675 [ + + + + ]: 127 : if (!check_visible && !check_frozen)
676 : 13 : continue;
677 : :
678 : 114 : return p->current_blocknum++;
679 : : }
680 : :
681 : 15 : return InvalidBlockNumber;
682 : : }
683 : :
684 : : /*
685 : : * Returns a list of items whose visibility map information does not match
686 : : * the status of the tuples on the page.
687 : : *
688 : : * If all_visible is passed as true, this will include all items which are
689 : : * on pages marked as all-visible in the visibility map but which do not
690 : : * seem to in fact be all-visible.
691 : : *
692 : : * If all_frozen is passed as true, this will include all items which are
693 : : * on pages marked as all-frozen but which do not seem to in fact be frozen.
694 : : *
695 : : * Checks relkind of relid and will throw an error if the relation does not
696 : : * have a VM.
697 : : */
698 : : static corrupt_items *
699 : 20 : collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
700 : : {
701 : : Relation rel;
702 : : corrupt_items *items;
703 : 20 : Buffer vmbuffer = InvalidBuffer;
704 : 20 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
705 : 20 : TransactionId OldestXmin = InvalidTransactionId;
706 : : struct collect_corrupt_items_read_stream_private p;
707 : : ReadStream *stream;
708 : : Buffer buffer;
709 : :
710 : 20 : rel = relation_open(relid, AccessShareLock);
711 : :
712 : : /* Only some relkinds have a visibility map */
713 : 20 : check_relation_supported(rel);
714 : :
715 [ + + ]: 15 : if (all_visible)
716 : 10 : OldestXmin = GetStrictOldestNonRemovableTransactionId(rel);
717 : :
718 : : /*
719 : : * Guess an initial array size. We don't expect many corrupted tuples, so
720 : : * start with a small array. This function uses the "next" field to track
721 : : * the next offset where we can store an item (which is the same thing as
722 : : * the number of items found so far) and the "count" field to track the
723 : : * number of entries allocated. We'll repurpose these fields before
724 : : * returning.
725 : : */
726 : 15 : items = palloc0_object(corrupt_items);
727 : 15 : items->next = 0;
728 : 15 : items->count = 64;
729 : 15 : items->tids = palloc_array(ItemPointerData, items->count);
730 : :
731 : 15 : p.current_blocknum = 0;
732 : 15 : p.last_exclusive = RelationGetNumberOfBlocks(rel);
733 : 15 : p.rel = rel;
734 : 15 : p.vmbuffer = InvalidBuffer;
735 : 15 : p.all_frozen = all_frozen;
736 : 15 : p.all_visible = all_visible;
737 : 15 : stream = read_stream_begin_relation(READ_STREAM_FULL,
738 : : bstrategy,
739 : : rel,
740 : : MAIN_FORKNUM,
741 : : collect_corrupt_items_read_stream_next_block,
742 : : &p,
743 : : 0);
744 : :
745 : : /* Loop over every block in the relation. */
746 [ + + ]: 129 : while ((buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
747 : : {
748 : 114 : bool check_frozen = all_frozen;
749 : 114 : bool check_visible = all_visible;
750 : : Page page;
751 : : OffsetNumber offnum,
752 : : maxoff;
753 : : BlockNumber blkno;
754 : :
755 : : /* Make sure we are interruptible. */
756 [ - + ]: 114 : CHECK_FOR_INTERRUPTS();
757 : :
758 : 114 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
759 : :
760 : 114 : page = BufferGetPage(buffer);
761 : 114 : maxoff = PageGetMaxOffsetNumber(page);
762 : 114 : blkno = BufferGetBlockNumber(buffer);
763 : :
764 : : /*
765 : : * The visibility map bits might have changed while we were acquiring
766 : : * the page lock. Recheck to avoid returning spurious results.
767 : : */
768 [ + + - + ]: 114 : if (check_frozen && !VM_ALL_FROZEN(rel, blkno, &vmbuffer))
769 : 0 : check_frozen = false;
770 [ + + - + ]: 114 : if (check_visible && !VM_ALL_VISIBLE(rel, blkno, &vmbuffer))
771 : 0 : check_visible = false;
772 [ + + - + ]: 114 : if (!check_visible && !check_frozen)
773 : : {
774 : 0 : UnlockReleaseBuffer(buffer);
775 : 0 : continue;
776 : : }
777 : :
778 : : /* Iterate over each tuple on the page. */
779 : 114 : for (offnum = FirstOffsetNumber;
780 [ + + ]: 17188 : offnum <= maxoff;
781 : 17074 : offnum = OffsetNumberNext(offnum))
782 : : {
783 : : HeapTupleData tuple;
784 : : ItemId itemid;
785 : :
786 : 17074 : itemid = PageGetItemId(page, offnum);
787 : :
788 : : /* Unused or redirect line pointers are of no interest. */
789 [ + - + + ]: 17074 : if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid))
790 : 24 : continue;
791 : :
792 : : /* Dead line pointers are neither all-visible nor frozen. */
793 [ - + ]: 17050 : if (ItemIdIsDead(itemid))
794 : : {
795 : 0 : ItemPointerSet(&(tuple.t_self), blkno, offnum);
796 : 0 : record_corrupt_item(items, &tuple.t_self);
797 : 0 : continue;
798 : : }
799 : :
800 : : /* Initialize a HeapTupleData structure for checks below. */
801 : 17050 : ItemPointerSet(&(tuple.t_self), blkno, offnum);
802 : 17050 : tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
803 : 17050 : tuple.t_len = ItemIdGetLength(itemid);
804 : 17050 : tuple.t_tableOid = relid;
805 : :
806 : : /*
807 : : * If we're checking whether the page is all-visible, we expect
808 : : * the tuple to be all-visible.
809 : : */
810 [ + + ]: 17050 : if (check_visible &&
811 [ + + ]: 9028 : !tuple_all_visible(&tuple, OldestXmin, buffer))
812 : : {
813 : : TransactionId RecomputedOldestXmin;
814 : :
815 : : /*
816 : : * Time has passed since we computed OldestXmin, so it's
817 : : * possible that this tuple is all-visible in reality even
818 : : * though it doesn't appear so based on our
819 : : * previously-computed value. Let's compute a new value so we
820 : : * can be certain whether there is a problem.
821 : : *
822 : : * From a concurrency point of view, it sort of sucks to
823 : : * retake ProcArrayLock here while we're holding the buffer
824 : : * locked in shared mode, but it should be safe against
825 : : * deadlocks, because surely
826 : : * GetStrictOldestNonRemovableTransactionId() should never
827 : : * take a buffer lock. And this shouldn't happen often, so
828 : : * it's worth being careful so as to avoid false positives.
829 : : */
830 : 5 : RecomputedOldestXmin = GetStrictOldestNonRemovableTransactionId(rel);
831 : :
832 [ + - ]: 5 : if (!TransactionIdPrecedes(OldestXmin, RecomputedOldestXmin))
833 : 5 : record_corrupt_item(items, &tuple.t_self);
834 : : else
835 : : {
836 : 0 : OldestXmin = RecomputedOldestXmin;
837 [ # # ]: 0 : if (!tuple_all_visible(&tuple, OldestXmin, buffer))
838 : 0 : record_corrupt_item(items, &tuple.t_self);
839 : : }
840 : : }
841 : :
842 : : /*
843 : : * If we're checking whether the page is all-frozen, we expect the
844 : : * tuple to be in a state where it will never need freezing.
845 : : */
846 [ + + ]: 17050 : if (check_frozen)
847 : : {
848 [ + + ]: 8022 : if (heap_tuple_needs_eventual_freeze(tuple.t_data))
849 : 5 : record_corrupt_item(items, &tuple.t_self);
850 : : }
851 : : }
852 : :
853 : 114 : UnlockReleaseBuffer(buffer);
854 : : }
855 : 15 : read_stream_end(stream);
856 : :
857 : : /* Clean up. */
858 [ + + ]: 15 : if (vmbuffer != InvalidBuffer)
859 : 11 : ReleaseBuffer(vmbuffer);
860 [ + - ]: 15 : if (p.vmbuffer != InvalidBuffer)
861 : 15 : ReleaseBuffer(p.vmbuffer);
862 : 15 : relation_close(rel, AccessShareLock);
863 : :
864 : : /*
865 : : * Before returning, repurpose the fields to match caller's expectations.
866 : : * next is now the next item that should be read (rather than written) and
867 : : * count is now the number of items we wrote (rather than the number we
868 : : * allocated).
869 : : */
870 : 15 : items->count = items->next;
871 : 15 : items->next = 0;
872 : :
873 : 15 : return items;
874 : : }
875 : :
876 : : /*
877 : : * Remember one corrupt item.
878 : : */
879 : : static void
880 : 10 : record_corrupt_item(corrupt_items *items, ItemPointer tid)
881 : : {
882 : : /* enlarge output array if needed. */
883 [ - + ]: 10 : if (items->next >= items->count)
884 : : {
885 : 0 : items->count *= 2;
886 : 0 : items->tids = repalloc_array(items->tids, ItemPointerData, items->count);
887 : : }
888 : : /* and add the new item */
889 : 10 : items->tids[items->next++] = *tid;
890 : 10 : }
891 : :
892 : : /*
893 : : * Check whether a tuple is all-visible relative to a given OldestXmin value.
894 : : * The buffer should contain the tuple and should be locked and pinned.
895 : : */
896 : : static bool
897 : 9028 : tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
898 : : {
899 : : HTSV_Result state;
900 : : TransactionId xmin;
901 : :
902 : 9028 : state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
903 [ + + ]: 9028 : if (state != HEAPTUPLE_LIVE)
904 : 5 : return false; /* all-visible implies live */
905 : :
906 : : /*
907 : : * Neither lazy_scan_heap nor heap_page_is_all_visible will mark a page
908 : : * all-visible unless every tuple is hinted committed. However, those hint
909 : : * bits could be lost after a crash, so we can't be certain that they'll
910 : : * be set here. So just check the xmin.
911 : : */
912 : :
913 : 9023 : xmin = HeapTupleHeaderGetXmin(tup->t_data);
914 [ - + ]: 9023 : if (!TransactionIdPrecedes(xmin, OldestXmin))
915 : 0 : return false; /* xmin not old enough for all to see */
916 : :
917 : 9023 : return true;
918 : : }
919 : :
920 : : /*
921 : : * check_relation_supported - convenience routine to check that relation
922 : : * is supported by the callers.
923 : : */
924 : : static void
925 : 106 : check_relation_supported(Relation rel)
926 : : {
927 [ + + + + : 106 : if (!RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
+ + ]
928 [ + - ]: 25 : ereport(ERROR,
929 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
930 : : errmsg("relation \"%s\" is of wrong relation kind",
931 : : RelationGetRelationName(rel)),
932 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
933 : :
934 [ + + - + ]: 81 : if (RELATION_IS_OTHER_TEMP(rel))
935 [ # # ]: 0 : ereport(ERROR,
936 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
937 : : errmsg("cannot access temporary tables of other sessions")));
938 : :
939 [ + + - + ]: 81 : if (!RelationIsPermanent(rel) && RecoveryInProgress())
940 [ # # ]: 0 : ereport(ERROR,
941 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
942 : : errmsg("cannot access temporary or unlogged relations during recovery")));
943 : 81 : }
|