*** a/src/backend/access/hash/hash.c --- b/src/backend/access/hash/hash.c *************** *** 282,288 **** hashgettuple(PG_FUNCTION_ARGS) --- 282,290 ---- /* * Yes, so mark it by setting the LP_DEAD state in the item flags. */ + LockBufferForHints(buf); ItemIdMarkDead(PageGetItemId(page, offnum)); + UnlockBufferForHints(buf); /* * Since this can be redone later if needed, it's treated the same *** a/src/backend/access/heap/pruneheap.c --- b/src/backend/access/heap/pruneheap.c *************** *** 259,266 **** heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin, --- 259,268 ---- if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid || PageIsFull(page)) { + LockBufferForHints(buffer); ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid; PageClearFull(page); + UnlockBufferForHints(buffer); SetBufferCommitInfoNeedsSave(buffer); } } *** a/src/backend/access/nbtree/nbtinsert.c --- b/src/backend/access/nbtree/nbtinsert.c *************** *** 403,415 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, --- 403,427 ---- * everyone, so we may as well mark the index entry * killed. */ + if (nbuf != InvalidBuffer) + LockBufferForHints(nbuf); + else + LockBufferForHints(buf); + ItemIdMarkDead(curitemid); opaque->btpo_flags |= BTP_HAS_GARBAGE; + /* be sure to mark the proper buffer dirty... */ if (nbuf != InvalidBuffer) + { + UnlockBufferForHints(nbuf); SetBufferCommitInfoNeedsSave(nbuf); + } else + { + UnlockBufferForHints(buf); SetBufferCommitInfoNeedsSave(buf); + } } } } *** a/src/backend/access/nbtree/nbtree.c --- b/src/backend/access/nbtree/nbtree.c *************** *** 1040,1046 **** restart: --- 1040,1048 ---- if (vstate->cycleid != 0 && opaque->btpo_cycleid == vstate->cycleid) { + LockBufferForHints(buf); opaque->btpo_cycleid = 0; + UnlockBufferForHints(buf); SetBufferCommitInfoNeedsSave(buf); } } *** a/src/backend/commands/sequence.c --- b/src/backend/commands/sequence.c *************** *** 1092,1101 **** read_info(SeqTable elm, Relation rel, Buffer *buf) --- 1092,1103 ---- */ if (HeapTupleHeaderGetXmax(tuple.t_data) != InvalidTransactionId) { + LockBufferForHints(*buf); HeapTupleHeaderSetXmax(tuple.t_data, InvalidTransactionId); tuple.t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; tuple.t_data->t_infomask |= HEAP_XMAX_INVALID; SetBufferCommitInfoNeedsSave(*buf); + UnlockBufferForHints(*buf); } seq = (Form_pg_sequence) GETSTRUCT(&tuple); *** a/src/backend/storage/buffer/bufmgr.c --- b/src/backend/storage/buffer/bufmgr.c *************** *** 440,446 **** ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, smgrread(smgr, forkNum, blockNum, (char *) bufBlock); /* check for garbage data */ ! if (!PageHeaderIsValid((PageHeader) bufBlock)) { if (mode == RBM_ZERO_ON_ERROR || zero_damaged_pages) { --- 440,446 ---- smgrread(smgr, forkNum, blockNum, (char *) bufBlock); /* check for garbage data */ ! if (!PageIsVerified((Page) bufBlock)) { if (mode == RBM_ZERO_ON_ERROR || zero_damaged_pages) { *************** *** 1860,1865 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) --- 1860,1866 ---- { XLogRecPtr recptr; ErrorContextCallback errcontext; + Block bufBlock; /* * Acquire the buffer's io_in_progress lock. If StartBufferIO returns *************** *** 1907,1912 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) --- 1908,1928 ---- buf->flags &= ~BM_JUST_DIRTIED; UnlockBufHdr(buf); + /* + * Set page verification info immediately before we write the buffer to disk. + * Once we have flushed the buffer is marked clean again, meaning it can + * be replaced quickly and silently with another data block, so we must + * write verification info now. For efficiency, the process of cleaning + * and page replacement is asynchronous, so we can't do this *only* when + * we are about to replace the buffer, we need to do this for every flush. + * + * Note that the buffer must not be written to while we set verification, + * otherwise it would invalidate the page on-disk. So we must ensure + * writes are prevented, see comments for LockBufferForHints(). + */ + bufBlock = buf->buf_id < 0 ? LocalBufHdrGetBlock(buf) : BufHdrGetBlock(buf); + PageSetVerificationInfo((Page) bufBlock); + smgrwrite(reln, buf->tag.forkNum, buf->tag.blockNum, *************** *** 1921,1926 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) --- 1937,1960 ---- */ TerminateBufferIO(buf, true, 0); + #ifdef USE_ASSERT_CHECKING + /* + * Confirm that all AMs have protected their hints with LockBufferForHints() + */ + if (page_checksums) + { + bool just_dirtied; + + LockBufHdr(buf); + just_dirtied = ((buf->flags & BM_JUST_DIRTIED) == BM_JUST_DIRTIED); + UnlockBufHdr(buf); + + Assert(!just_dirtied); + } + #endif + + /* XXX Assert(buf is not BM_JUST_DIRTIED) */ + TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(buf->tag.forkNum, buf->tag.blockNum, reln->smgr_rnode.node.spcNode, *************** *** 2628,2633 **** WaitIO(volatile BufferDesc *buf) --- 2662,2742 ---- } /* + * Lock buffer to allow hints to be set, for use by all AMs. + * + * Many writes to PostgreSQL data pages are referred to as hints because + * those changes are not critical and we have elected to avoid WAL-logging + * the changes. As an optimisation, PostgreSQL allows hints to be set on + * pages locked in shared mode. When we are adding checksums prior to flush + * the page musn't change, so we hold the io_in_progress lock exclusively + * for the buffer prior to the smgrwrite. Thus setting hints must wait + * when we are flushing the buffer. The alternative would be to lock the + * whole buffer in exclusive mode when we wanted to set hints, which would + * cause more contention that simply holding the io_in_progress lock. + * + * It might seem possible to handle this optimistically and flush the + * buffer and then check to see if the buffer was just dirtied. However, + * that might result in the on-disk block having page checksums that don't + * match its contents and thus we would need to train ourselves to ignore + * "some" page errors. If you feel that is a path to take, then turn off + * checksums and be happy. + */ + void + LockBufferForHints(Buffer buffer) + { + volatile BufferDesc *buf; + + Assert(BufferIsValid(buffer)); + + /* + * If we aren't writing a checksum for buffers then the answer is true. + */ + if (!page_checksums) + return; + + /* + * If its a local buffer then it can't be IO in progress, since we'd be the + * one writing it and not checking to see if it was in progress. + */ + if (BufferIsLocal(buffer)) + return; + + buf = &BufferDescriptors[buffer - 1]; + + LWLockAcquire(buf->io_in_progress_lock, LW_SHARED); + } + + /* + * UnLock buffer to prevent hints being set. + * + * Don't do a CHECK_INTERRUPTS between BufferLockForHints() and BufferUnlockForHints() + */ + void + UnlockBufferForHints(Buffer buffer) + { + volatile BufferDesc *buf; + + Assert(BufferIsValid(buffer)); + + /* + * If we aren't writing a checksum for buffers, ignore. + */ + if (!page_checksums) + return; + + /* + * If its a local buffer then it can't be IO in progress, since we'd be the + * one writing it and not checking to see if it was in progress. + */ + if (BufferIsLocal(buffer)) + return; + + buf = &BufferDescriptors[buffer - 1]; + + LWLockRelease(buf->io_in_progress_lock); + } + + /* * StartBufferIO: begin I/O on this buffer * (Assumptions) * My process is executing no IO *** a/src/backend/storage/page/bufpage.c --- b/src/backend/storage/page/bufpage.c *************** *** 16,21 **** --- 16,25 ---- #include "access/htup.h" + bool page_checksums = false; + + static bool PageVerificationInfoOK(Page page); + static uint16 PageCalcChecksum16(Page page); /* ---------------------------------------------------------------- * Page support functions *************** *** 25,30 **** --- 29,38 ---- /* * PageInit * Initializes the contents of a page. + * Note that we don't automatically add a checksum, or flag that the + * page has a checksum field. We start with a normal page layout and defer + * the decision on what page verification will be written just before + * we writethe block to disk later. */ void PageInit(Page page, Size pageSize, Size specialSize) *************** *** 67,86 **** PageInit(Page page, Size pageSize, Size specialSize) * will clean up such a page and make it usable. */ bool ! PageHeaderIsValid(PageHeader page) { char *pagebytes; int i; /* Check normal case */ ! if (PageGetPageSize(page) == BLCKSZ && ! PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION && ! (page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 && ! page->pd_lower >= SizeOfPageHeaderData && ! page->pd_lower <= page->pd_upper && ! page->pd_upper <= page->pd_special && ! page->pd_special <= BLCKSZ && ! page->pd_special == MAXALIGN(page->pd_special)) return true; /* Check all-zeroes case */ --- 75,94 ---- * will clean up such a page and make it usable. */ bool ! PageIsVerified(Page page) { + PageHeader p = (PageHeader) page; char *pagebytes; int i; /* Check normal case */ ! if (PageVerificationInfoOK(page) && ! (p->pd_flags & ~PD_VALID_FLAG_BITS) == 0 && ! p->pd_lower >= SizeOfPageHeaderData && ! p->pd_lower <= p->pd_upper && ! p->pd_upper <= p->pd_special && ! p->pd_special <= BLCKSZ && ! p->pd_special == MAXALIGN(p->pd_special)) return true; /* Check all-zeroes case */ *************** *** 827,829 **** PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems) --- 835,979 ---- pfree(itemidbase); } + + /* + * Test whether the page verification information is correct or not. + * + * IMPORTANT NOTE - + * Verification info is not valid at all times on a data page. We set + * verification info before we flush page/buffer, and implicitly invalidate + * verification info when we write to the page. A heavily accessed buffer + * might then spend most of its life with invalid page verification info, + * so testing verification info on random pages in the buffer pool will tell + * you nothing. The reason for this is that page verification info protects + * Postgres data from errors on the filesystems on which we rely. We do not + * protect buffers against uncorrectable memory errors, since these have a + * very low measured incidence according to research on large server farms, + * http://www.google.com/research/pubs/archive/35162.pdf, discussed 2010/12/22. + * + * To confirm your understanding that means that WAL-logged changes to a page + * do NOT update the page verification info, so full page images may not have + * correct verification information on them. But those page images have the + * WAL CRC covering them and so are verified separately from this mechanism. + * WAL replay ignores page verification info unless it writes out or reads in + * blocks from disk; restoring full page writes does not check verification + * info via this function. + * + * The best way to understand this is that WAL CRCs protect records entering + * the WAL stream, and page verification protects blocks entering and leaving + * the buffer pool. They are similar in purpose, yet completely separate. + * Together they ensure we are able to detect errors in data leaving and + * re-entering PostgreSQL controlled memory. + * + * Note also that the verification mechanism can vary from page to page. + * All we do here is look at what the page itself says is the verification + * mechanism and then apply that test. This allows us to run without the CPU + * cost of verification if we choose, as well as to provide an upgrade path + * for anyone doing direct upgrades using pg_upgrade. + * + * There is some concern that trusting page data to say how to check page + * data is dangerously self-referential. To ensure no mistakes we set two + * non-adjacent bits to signify that the page has a checksum and + * should be verified when that block is read back into a buffer. + * We use two bits in case a multiple bit error removes one of the checksum + * flags *and* destroys data, which would lead to skipping the checksum check + * and silently accepting bad data. + * + * Note also that this returns a boolean, not a full damage assessment. + */ + static bool + PageVerificationInfoOK(Page page) + { + PageHeader p = (PageHeader) page; + + /* + * We set two non-adjacent bits to signify that the page has a checksum and + * should be verified against that block is read back into a buffer. + * We use two bits in case a multiple bit error removes one of the checksum + * flags and destroys data, which would lead to skipping the checksum check + * and silently accepting bad data. + */ + if (PageHasChecksumFlag1(p) && PageHasChecksumFlag2(p)) + { + if (PageCalcChecksum16(page) == p->pd_verify.pd_checksum16) + return true; + } + else if (!PageHasChecksumFlag1(p) && !PageHasChecksumFlag2(p)) + { + if (PageGetPageLayoutVersion(p) == PG_PAGE_LAYOUT_VERSION && + PageGetPageSize(p) == BLCKSZ) + return true; + } + + return false; + } + + /* + * Set verification info for page. + * + * Either we set a new checksum, or we set the standard watermark. We must + * not leave an old checksum in place. Note that the verification info is + * not WAL logged, whereas the data changes to pages are, so data is safe + * whether or not we have page_checksums enabled. The purpose of checksums + * is to detect page corruption to allow replacement from backup. + */ + void + PageSetVerificationInfo(Page page) + { + PageHeader p = (PageHeader) page; + + if (page_checksums) + { + p->pd_flags |= PD_CHECKSUM; + p->pd_verify.pd_checksum16 = PageCalcChecksum16(page); + } + else if (PageHasChecksumFlag1(p) || PageHasChecksumFlag2(p)) + { + /* ensure any older checksum info is overwritten with watermark */ + p->pd_flags &= ~PD_CHECKSUM; + PageSetPageSizeAndVersion(p, BLCKSZ, PG_PAGE_LAYOUT_VERSION); + } + } + + /* + * Calculate checksum for a PostgreSQL Page. We do this in 3 steps, first + * we calculate the checksum for the header, avoiding the verification + * info, which will be added afterwards. Next, we add the line pointers up to + * the hole in the middle of the block at pd_lower. Last, we add the tail + * of the page from pd_upper to the end of page. + */ + static uint16 + PageCalcChecksum16(Page page) + { + #define PAGE_VERIFICATION_USES_FLETCHER16 (true) + #ifdef PAGE_VERIFICATION_USES_FLETCHER16 + /* + * Following calculation is a Flecther's 16 checksum. The calc is isolated + * here and tuning and/or replacement algorithms are possible. + * + * XXX present implementation is raw, untuned calculation, please tweak + */ + PageHeader p = (PageHeader) page; + uint16 sum1 = 0; + uint16 sum2 = 0; + int i; + + #define COMP_F16(from, to) \ + for (i = from; i < to; i++) \ + { \ + sum1 = (sum1 + page[i]) % 255; \ + sum2 = (sum1 + sum2) % 255; \ + } while (0) + + COMP_F16(0, + offsetof(PageHeaderData, pd_special) + sizeof(LocationIndex)); + + COMP_F16(offsetof(PageHeaderData, pd_prune_xid), + p->pd_upper); + + COMP_F16(p->pd_upper, + (int) BLCKSZ); + + return ((sum2 << 8) | sum1); + #endif + } *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 830,835 **** static struct config_bool ConfigureNamesBool[] = --- 830,849 ---- NULL, NULL, NULL }, { + {"page_checksums", PGC_SIGHUP, WAL_SETTINGS, + gettext_noop("Marks database blocks with a checksum before writing them to disk. "), + gettext_noop("When enabled all database blocks will be marked with a checksums before writing to disk. " + "When we read a database block from disk the checksum is checked, if it exists. " + "If there is no checksum marked yet then no check is performed, though a " + "checksum will be added later when we re-write the database block. " + "When disabled checksums will be ignored, even if the block was marked " + "with checksum. When disabled checksums will not be added to database blocks.") + }, + &page_checksums, + true, + NULL, NULL, NULL + }, + { {"full_page_writes", PGC_SIGHUP, WAL_SETTINGS, gettext_noop("Writes full pages to WAL when first modified after a checkpoint."), gettext_noop("A page write in process during an operating system crash might be " *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 150,164 **** #------------------------------------------------------------------------------ ! # WRITE AHEAD LOG #------------------------------------------------------------------------------ ! # - Settings - ! #wal_level = minimal # minimal, archive, or hot_standby ! # (change requires restart) #fsync = on # turns forced synchronization on or off #synchronous_commit = on # synchronization level; on, off, or local #wal_sync_method = fsync # the default is the first option # supported by the operating system: # open_datasync --- 150,170 ---- #------------------------------------------------------------------------------ ! # WRITE AHEAD LOG & RELIABILITY #------------------------------------------------------------------------------ ! # - Reliability - ! #page_checksums = off # calculate checksum before database I/O ! #full_page_writes = on # recover from partial page writes #fsync = on # turns forced synchronization on or off + #synchronous_commit = on # synchronization level; on, off, or local + + # - Write Ahead Log - + + #wal_level = minimal # minimal, archive, or hot_standby + # (change requires restart) #wal_sync_method = fsync # the default is the first option # supported by the operating system: # open_datasync *************** *** 166,172 **** # fsync # fsync_writethrough # open_sync - #full_page_writes = on # recover from partial page writes #wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers # (change requires restart) #wal_writer_delay = 200ms # 1-10000 milliseconds --- 172,177 ---- *** a/src/backend/utils/time/tqual.c --- b/src/backend/utils/time/tqual.c *************** *** 119,125 **** SetHintBits(HeapTupleHeader tuple, Buffer buffer, --- 119,127 ---- return; /* not flushed yet, so don't set hint */ } + LockBufferForHints(buffer); tuple->t_infomask |= infomask; + UnlockBufferForHints(buffer); SetBufferCommitInfoNeedsSave(buffer); } *** a/src/include/storage/bufmgr.h --- b/src/include/storage/bufmgr.h *************** *** 209,214 **** extern bool ConditionalLockBuffer(Buffer buffer); --- 209,216 ---- extern void LockBufferForCleanup(Buffer buffer); extern bool ConditionalLockBufferForCleanup(Buffer buffer); extern bool HoldingBufferPinThatDelaysRecovery(void); + extern void LockBufferForHints(Buffer buffer); + extern void UnlockBufferForHints(Buffer buffer); extern void AbortBufferIO(void); *** a/src/include/storage/bufpage.h --- b/src/include/storage/bufpage.h *************** *** 18,23 **** --- 18,25 ---- #include "storage/item.h" #include "storage/off.h" + extern bool page_checksums; + /* * A postgres disk page is an abstraction layered on top of a postgres * disk block (which is simply a unit of i/o, see block.h). *************** *** 130,136 **** typedef struct PageHeaderData LocationIndex pd_lower; /* offset to start of free space */ LocationIndex pd_upper; /* offset to end of free space */ LocationIndex pd_special; /* offset to start of special space */ ! uint16 pd_pagesize_version; TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */ ItemIdData pd_linp[1]; /* beginning of line pointer array */ } PageHeaderData; --- 132,144 ---- LocationIndex pd_lower; /* offset to start of free space */ LocationIndex pd_upper; /* offset to end of free space */ LocationIndex pd_special; /* offset to start of special space */ ! ! union ! { ! uint16 pd_pagesize_version; ! uint16 pd_checksum16; ! } pd_verify; /* page verification data */ ! TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */ ItemIdData pd_linp[1]; /* beginning of line pointer array */ } PageHeaderData; *************** *** 155,161 **** typedef PageHeaderData *PageHeader; #define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to * everyone */ ! #define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */ /* * Page layout version number 0 is for pre-7.3 Postgres releases. --- 163,178 ---- #define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to * everyone */ ! #define PD_VALID_FLAG_BITS 0x800F /* OR of all non-checksum pd_flags bits */ ! ! #define PD_CHECKSUM1 0x0008 /* First checksum bit */ ! #define PD_CHECKSUM2 0x8000 /* Second checksum bit */ ! #define PD_CHECKSUM 0x8008 /* OR of both checksum flags */ ! ! #define PageHasChecksumFlag1(page) \ ! ((((PageHeader) (page))->pd_flags & PD_CHECKSUM1) == PD_CHECKSUM1) ! #define PageHasChecksumFlag2(page) \ ! ((((PageHeader) (page))->pd_flags & PD_CHECKSUM2) == PD_CHECKSUM2) /* * Page layout version number 0 is for pre-7.3 Postgres releases. *************** *** 165,170 **** typedef PageHeaderData *PageHeader; --- 182,189 ---- * Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and * added the pd_flags field (by stealing some bits from pd_tli), * as well as adding the pd_prune_xid field (which enlarges the header). + * Release 9.2 uses 4 as well, though with changed meaning of verification bits. + * We deliberately don't bump the page version for that, to allow upgrades. */ #define PG_PAGE_LAYOUT_VERSION 4 *************** *** 231,249 **** typedef PageHeaderData *PageHeader; * PageGetPageSize * Returns the page size of a page. * ! * this can only be called on a formatted page (unlike ! * BufferGetPageSize, which can be called on an unformatted page). ! * however, it can be called on a page that is not stored in a buffer. */ ! #define PageGetPageSize(page) \ ! ((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00)) /* * PageGetPageLayoutVersion * Returns the page layout version of a page. */ #define PageGetPageLayoutVersion(page) \ ! (((PageHeader) (page))->pd_pagesize_version & 0x00FF) /* * PageSetPageSizeAndVersion --- 250,271 ---- * PageGetPageSize * Returns the page size of a page. * ! * Since PageSizeIsValid() when pagesize == BLCKSZ, just written BLCKSZ. ! * This can be called on any page, initialised or not, in or out of buffers. ! * You might think this can vary at runtime but you'd be wrong, since pages ! * frequently need to occupy buffers and pages are copied from one to another ! * so there are many hidden assumptions that this simple definition is true. */ ! #define PageGetPageSize(page) (BLCKSZ) /* * PageGetPageLayoutVersion * Returns the page layout version of a page. + * + * Must not be used on a page that is flagged for checksums. */ #define PageGetPageLayoutVersion(page) \ ! (((PageHeader) (page))->pd_verify.pd_pagesize_version & 0x00FF) /* * PageSetPageSizeAndVersion *************** *** 251,262 **** typedef PageHeaderData *PageHeader; * * We could support setting these two values separately, but there's * no real need for it at the moment. */ #define PageSetPageSizeAndVersion(page, size, version) \ ( \ AssertMacro(((size) & 0xFF00) == (size)), \ AssertMacro(((version) & 0x00FF) == (version)), \ ! ((PageHeader) (page))->pd_pagesize_version = (size) | (version) \ ) /* ---------------- --- 273,286 ---- * * We could support setting these two values separately, but there's * no real need for it at the moment. + * + * Must not be used on a page that is flagged for checksums. */ #define PageSetPageSizeAndVersion(page, size, version) \ ( \ AssertMacro(((size) & 0xFF00) == (size)), \ AssertMacro(((version) & 0x00FF) == (version)), \ ! ((PageHeader) (page))->pd_verify.pd_pagesize_version = (size) | (version) \ ) /* ---------------- *************** *** 368,374 **** do { \ */ extern void PageInit(Page page, Size pageSize, Size specialSize); ! extern bool PageHeaderIsValid(PageHeader page); extern OffsetNumber PageAddItem(Page page, Item item, Size size, OffsetNumber offsetNumber, bool overwrite, bool is_heap); extern Page PageGetTempPage(Page page); --- 392,398 ---- */ extern void PageInit(Page page, Size pageSize, Size specialSize); ! extern bool PageIsVerified(Page page); extern OffsetNumber PageAddItem(Page page, Item item, Size size, OffsetNumber offsetNumber, bool overwrite, bool is_heap); extern Page PageGetTempPage(Page page); *************** *** 381,385 **** extern Size PageGetExactFreeSpace(Page page); --- 405,410 ---- extern Size PageGetHeapFreeSpace(Page page); extern void PageIndexTupleDelete(Page page, OffsetNumber offset); extern void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems); + extern void PageSetVerificationInfo(Page page); #endif /* BUFPAGE_H */