/* * visibilitymap_clear - clear a bit in visibility map * * Clear a bit in the visibility map, marking that not all tuples are * visible to all transactions anymore. */ void visibilitymap_clear(Relation rel, BlockNumber heapBlk) { BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); int mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); int mapBit = HEAPBLK_TO_MAPBIT(heapBlk); uint8 mask = 1 << mapBit; Buffer mapBuffer; char *map; #ifdef TRACE_VISIBILITYMAP elog(DEBUG1, "vm_clear %s %d", RelationGetRelationName(rel), heapBlk); #endif mapBuffer = vm_readbuf(rel, mapBlock, false); if (!BufferIsValid(mapBuffer)) return; /* nothing to do */ LockBuffer(mapBuffer, BUFFER_LOCK_EXCLUSIVE); map = PageGetContents(BufferGetPage(mapBuffer)); if (map[mapByte] & mask) { map[mapByte] &= ~mask; MarkBufferDirty(mapBuffer); } UnlockReleaseBuffer(mapBuffer); }
/* * visibilitymap_pin - pin a map page for setting a bit * * Setting a bit in the visibility map is a two-phase operation. First, call * visibilitymap_pin, to pin the visibility map page containing the bit for * the heap page. Because that can require I/O to read the map page, you * shouldn't hold a lock on the heap page while doing that. Then, call * visibilitymap_set to actually set the bit. * * On entry, *buf should be InvalidBuffer or a valid buffer returned by * an earlier call to visibilitymap_pin or visibilitymap_test on the same * relation. On return, *buf is a valid buffer with the map page containing * the bit for heapBlk. * * If the page doesn't exist in the map file yet, it is extended. */ void visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *buf) { BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); /* Reuse the old pinned buffer if possible */ if (BufferIsValid(*buf)) { if (BufferGetBlockNumber(*buf) == mapBlock) return; ReleaseBuffer(*buf); } *buf = vm_readbuf(rel, mapBlock, true); }
/* * visibilitymap_test - test if a bit is set * * Are all tuples on heapBlk visible to all, according to the visibility map? * * On entry, *buf should be InvalidBuffer or a valid buffer returned by an * earlier call to visibilitymap_pin or visibilitymap_test on the same * relation. On return, *buf is a valid buffer with the map page containing * the bit for heapBlk, or InvalidBuffer. The caller is responsible for * releasing *buf after it's done testing and setting bits. * * NOTE: This function is typically called without a lock on the heap page, * so somebody else could change the bit just after we look at it. In fact, * since we don't lock the visibility map page either, it's even possible that * someone else could have changed the bit just before we look at it, but yet * we might see the old value. It is the caller's responsibility to deal with * all concurrency issues! */ bool visibilitymap_test(Relation rel, BlockNumber heapBlk, Buffer *buf) { BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); uint32 mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); uint8 mapBit = HEAPBLK_TO_MAPBIT(heapBlk); bool result; char *map; #ifdef TRACE_VISIBILITYMAP elog(DEBUG1, "vm_test %s %d", RelationGetRelationName(rel), heapBlk); #endif /* Reuse the old pinned buffer if possible */ if (BufferIsValid(*buf)) { if (BufferGetBlockNumber(*buf) != mapBlock) { ReleaseBuffer(*buf); *buf = InvalidBuffer; } } if (!BufferIsValid(*buf)) { *buf = vm_readbuf(rel, mapBlock, false); if (!BufferIsValid(*buf)) return false; } map = PageGetContents(BufferGetPage(*buf)); /* * A single-bit read is atomic. There could be memory-ordering effects * here, but for performance reasons we make it the caller's job to worry * about that. */ result = (map[mapByte] & (1 << mapBit)) ? true : false; return result; }
/* * visibilitymap_test - test if a bit is set * * Are all tuples on heapBlk visible to all, according to the visibility map? * * On entry, *buf should be InvalidBuffer or a valid buffer returned by an * earlier call to visibilitymap_pin or visibilitymap_test on the same * relation. On return, *buf is a valid buffer with the map page containing * the bit for heapBlk, or InvalidBuffer. The caller is responsible for * releasing *buf after it's done testing and setting bits. */ bool visibilitymap_test(Relation rel, BlockNumber heapBlk, Buffer *buf) { BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); uint32 mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); uint8 mapBit = HEAPBLK_TO_MAPBIT(heapBlk); bool result; char *map; #ifdef TRACE_VISIBILITYMAP elog(DEBUG1, "vm_test %s %d", RelationGetRelationName(rel), heapBlk); #endif /* Reuse the old pinned buffer if possible */ if (BufferIsValid(*buf)) { if (BufferGetBlockNumber(*buf) != mapBlock) { ReleaseBuffer(*buf); *buf = InvalidBuffer; } } if (!BufferIsValid(*buf)) { *buf = vm_readbuf(rel, mapBlock, false); if (!BufferIsValid(*buf)) return false; } map = PageGetContents(BufferGetPage(*buf)); /* * We don't need to lock the page, as we're only looking at a single bit. */ result = (map[mapByte] & (1 << mapBit)) ? true : false; return result; }
/* * visibilitymap_count - count number of bits set in visibility map * * Note: we ignore the possibility of race conditions when the table is being * extended concurrently with the call. New pages added to the table aren't * going to be marked all-visible, so they won't affect the result. */ BlockNumber visibilitymap_count(Relation rel) { BlockNumber result = 0; BlockNumber mapBlock; for (mapBlock = 0; ; mapBlock++) { Buffer mapBuffer; unsigned char *map; int i; /* * Read till we fall off the end of the map. We assume that any * extra bytes in the last page are zeroed, so we don't bother * excluding them from the count. */ mapBuffer = vm_readbuf(rel, mapBlock, false); if (!BufferIsValid(mapBuffer)) break; /* * We choose not to lock the page, since the result is going to be * immediately stale anyway if anyone is concurrently setting or * clearing bits, and we only really need an approximate value. */ map = (unsigned char *) PageGetContents(BufferGetPage(mapBuffer)); for (i = 0; i < MAPSIZE; i++) { result += number_of_ones[map[i]]; } ReleaseBuffer(mapBuffer); } return result; }
/* * visibilitymap_truncate - truncate the visibility map * * The caller must hold AccessExclusiveLock on the relation, to ensure that * other backends receive the smgr invalidation event that this function sends * before they access the VM again. * * nheapblocks is the new size of the heap. */ void visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) { BlockNumber newnblocks; /* last remaining block, byte, and bit */ BlockNumber truncBlock = HEAPBLK_TO_MAPBLOCK(nheapblocks); uint32 truncByte = HEAPBLK_TO_MAPBYTE(nheapblocks); uint8 truncBit = HEAPBLK_TO_MAPBIT(nheapblocks); #ifdef TRACE_VISIBILITYMAP elog(DEBUG1, "vm_truncate %s %d", RelationGetRelationName(rel), nheapblocks); #endif RelationOpenSmgr(rel); /* * If no visibility map has been created yet for this relation, there's * nothing to truncate. */ if (!smgrexists(rel->rd_smgr, VISIBILITYMAP_FORKNUM)) return; /* * Unless the new size is exactly at a visibility map page boundary, the * tail bits in the last remaining map page, representing truncated heap * blocks, need to be cleared. This is not only tidy, but also necessary * because we don't get a chance to clear the bits if the heap is extended * again. */ if (truncByte != 0 || truncBit != 0) { Buffer mapBuffer; Page page; char *map; newnblocks = truncBlock + 1; mapBuffer = vm_readbuf(rel, truncBlock, false); if (!BufferIsValid(mapBuffer)) { /* nothing to do, the file was already smaller */ return; } page = BufferGetPage(mapBuffer); map = PageGetContents(page); LockBuffer(mapBuffer, BUFFER_LOCK_EXCLUSIVE); /* Clear out the unwanted bytes. */ MemSet(&map[truncByte + 1], 0, MAPSIZE - (truncByte + 1)); /* * Mask out the unwanted bits of the last remaining byte. * * ((1 << 0) - 1) = 00000000 ((1 << 1) - 1) = 00000001 ... ((1 << 6) - * 1) = 00111111 ((1 << 7) - 1) = 01111111 */ map[truncByte] &= (1 << truncBit) - 1; MarkBufferDirty(mapBuffer); UnlockReleaseBuffer(mapBuffer); } else newnblocks = truncBlock; if (smgrnblocks(rel->rd_smgr, VISIBILITYMAP_FORKNUM) <= newnblocks) { /* nothing to do, the file was already smaller than requested size */ return; } /* Truncate the unused VM pages, and send smgr inval message */ smgrtruncate(rel->rd_smgr, VISIBILITYMAP_FORKNUM, newnblocks); /* * We might as well update the local smgr_vm_nblocks setting. smgrtruncate * sent an smgr cache inval message, which will cause other backends to * invalidate their copy of smgr_vm_nblocks, and this one too at the next * command boundary. But this ensures it isn't outright wrong until then. */ if (rel->rd_smgr) rel->rd_smgr->smgr_vm_nblocks = newnblocks; }
/* * visibilitymap_test - truncate the visibility map */ void visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) { BlockNumber newnblocks; /* last remaining block, byte, and bit */ BlockNumber truncBlock = HEAPBLK_TO_MAPBLOCK(nheapblocks); uint32 truncByte = HEAPBLK_TO_MAPBYTE(nheapblocks); uint8 truncBit = HEAPBLK_TO_MAPBIT(nheapblocks); #ifdef TRACE_VISIBILITYMAP elog(DEBUG1, "vm_truncate %s %d", RelationGetRelationName(rel), nheapblocks); #endif /* * If no visibility map has been created yet for this relation, there's * nothing to truncate. */ if (!smgrexists(rel->rd_smgr, VISIBILITYMAP_FORKNUM)) return; /* * Unless the new size is exactly at a visibility map page boundary, the * tail bits in the last remaining map page, representing truncated heap * blocks, need to be cleared. This is not only tidy, but also necessary * because we don't get a chance to clear the bits if the heap is extended * again. */ if (truncByte != 0 || truncBit != 0) { Buffer mapBuffer; Page page; char *map; newnblocks = truncBlock + 1; mapBuffer = vm_readbuf(rel, truncBlock, false); if (!BufferIsValid(mapBuffer)) { /* nothing to do, the file was already smaller */ return; } page = BufferGetPage(mapBuffer); map = PageGetContents(page); LockBuffer(mapBuffer, BUFFER_LOCK_EXCLUSIVE); /* Clear out the unwanted bytes. */ MemSet(&map[truncByte + 1], 0, MAPSIZE - (truncByte + 1)); /* * Mask out the unwanted bits of the last remaining byte. * * ((1 << 0) - 1) = 00000000 ((1 << 1) - 1) = 00000001 ... ((1 << 6) - * 1) = 00111111 ((1 << 7) - 1) = 01111111 */ map[truncByte] &= (1 << truncBit) - 1; MarkBufferDirty(mapBuffer); UnlockReleaseBuffer(mapBuffer); } else newnblocks = truncBlock; if (smgrnblocks(rel->rd_smgr, VISIBILITYMAP_FORKNUM) < newnblocks) { /* nothing to do, the file was already smaller than requested size */ return; } smgrtruncate(rel->rd_smgr, VISIBILITYMAP_FORKNUM, newnblocks, rel->rd_istemp); /* * Need to invalidate the relcache entry, because rd_vm_nblocks seen by * other backends is no longer valid. */ if (!InRecovery) CacheInvalidateRelcache(rel); rel->rd_vm_nblocks = newnblocks; }