示例#1
0
 // was:sqlite3PagerUnref
 public static void Unref(DbPage pPg)
 {
     if (pPg != null)
     {
         var pPager = pPg.Pager;
         PCache.ReleasePage(pPg);
         pPager.pagerUnlockIfUnused();
     }
 }
示例#2
0
 internal static MemPage btreePageFromDbPage(DbPage pDbPage, Pgno pgno, BtShared pBt)
 {
     var pPage = (MemPage)Pager.sqlite3PagerGetExtra<MemPage>(pDbPage);
     pPage.Data = Pager.sqlite3PagerGetData(pDbPage);
     pPage.DbPage = pDbPage;
     pPage.Shared = pBt;
     pPage.ID = pgno;
     pPage.HeaderOffset = (byte)(pPage.ID == 1 ? 100 : 0);
     return pPage;
 }
示例#3
0
        internal static MemPage btreePageFromDbPage(DbPage pDbPage, Pgno pgno, BtShared pBt)
        {
            var pPage = (MemPage)Pager.sqlite3PagerGetExtra <MemPage>(pDbPage);

            pPage.Data         = Pager.sqlite3PagerGetData(pDbPage);
            pPage.DbPage       = pDbPage;
            pPage.Shared       = pBt;
            pPage.ID           = pgno;
            pPage.HeaderOffset = (byte)(pPage.ID == 1 ? 100 : 0);
            return(pPage);
        }
示例#4
0
        internal RC btreeGetPage(Pgno pgno, ref MemPage ppPage, int noContent)
        {
            Debug.Assert(MutexEx.Held(this.Mutex));
            DbPage pDbPage = null;
            var    rc      = this.Pager.Get(pgno, ref pDbPage, (byte)noContent);

            if (rc != RC.OK)
            {
                return(rc);
            }
            ppPage = MemPage.btreePageFromDbPage(pDbPage, pgno, this);
            return(RC.OK);
        }
 // was:fetchPayload
 private static RC CopyPayload(DbPage dbPage, byte[] payload, uint payloadOffset, byte[] b, uint bOffset, uint count, bool writeOperation)
 {
     if (writeOperation)
     {
         // Copy data from buffer to page (a write operation)
         var rc = Pager.Write(dbPage);
         if (rc != RC.OK)
             return rc;
         Buffer.BlockCopy(b, (int)bOffset, payload, (int)payloadOffset, (int)count);
     }
     else
         // Copy data from page to buffer (a read operation)
         Buffer.BlockCopy(payload, (int)payloadOffset, b, (int)bOffset, (int)count);
     return RC.OK;
 }
示例#6
0
 internal static void pageReinit(DbPage pData)
 {
     var pPage = Pager.sqlite3PagerGetExtra<MemPage>(pData);
     Debug.Assert(Pager.GetPageRefCount(pData) > 0);
     if (pPage.HasInit)
     {
         Debug.Assert(MutexEx.Held(pPage.Shared.Mutex));
         pPage.HasInit = false;
         if (Pager.GetPageRefCount(pData) > 1)
             // pPage might not be a btree page;  it might be an overflow page or ptrmap page or a free page.  In those cases, the following
             // call to btreeInitPage() will likely return SQLITE_CORRUPT. But no harm is done by this.  And it is very important that
             // btreeInitPage() be called on every btree page so we make the call for every page that comes in for re-initing.
             pPage.btreeInitPage();
     }
 }
示例#7
0
        internal static void pageReinit(DbPage pData)
        {
            var pPage = Pager.sqlite3PagerGetExtra <MemPage>(pData);

            Debug.Assert(Pager.GetPageRefCount(pData) > 0);
            if (pPage.HasInit)
            {
                Debug.Assert(MutexEx.Held(pPage.Shared.Mutex));
                pPage.HasInit = false;
                if (Pager.GetPageRefCount(pData) > 1)
                {
                    // pPage might not be a btree page;  it might be an overflow page or ptrmap page or a free page.  In those cases, the following
                    // call to btreeInitPage() will likely return SQLITE_CORRUPT. But no harm is done by this.  And it is very important that
                    // btreeInitPage() be called on every btree page so we make the call for every page that comes in for re-initing.
                    pPage.btreeInitPage();
                }
            }
        }
示例#8
0
 // was:fetchPayload
 private static RC CopyPayload(DbPage dbPage, byte[] payload, uint payloadOffset, byte[] b, uint bOffset, uint count, bool writeOperation)
 {
     if (writeOperation)
     {
         // Copy data from buffer to page (a write operation)
         var rc = Pager.Write(dbPage);
         if (rc != RC.OK)
         {
             return(rc);
         }
         Buffer.BlockCopy(b, (int)bOffset, payload, (int)payloadOffset, (int)count);
     }
     else
     {
         // Copy data from page to buffer (a read operation)
         Buffer.BlockCopy(payload, (int)payloadOffset, b, (int)bOffset, (int)count);
     }
     return(RC.OK);
 }
示例#9
0
 // was:sqlite3PagerPageRefcount
 public static int GetPageRefCount(DbPage pPage)
 {
     return(PCache.sqlite3PcachePageRefcount(pPage));
 }
示例#10
0
 public static bool IsPageWriteable(DbPage pPg)
 {
     return((pPg.flags & PgHdr.PGHDR.DIRTY) != 0);
 }
示例#11
0
 // was:sqlite3PagerIswriteable
 public static bool IsPageWriteable(DbPage pPg)
 {
     return(true);
 }
示例#12
0
 // was:sqlite3PagerRef
 public static void AddPageRef(DbPage pPg)
 {
     PCache.AddPageRef(pPg);
 }
示例#13
0
 // was:sqlite3PagerPagenumber
 public static Pgno GetPageID(DbPage pPg)
 {
     return(pPg.ID);
 }
示例#14
0
 // was:sqlite3PagerGet,sqlite3PagerAcquire
 public RC Get(Pgno pgno, ref DbPage ppPage)
 {
     return(Get(pgno, ref ppPage, 0));
 }
示例#15
0
 // was:sqlite3PagerGet,sqlite3PagerAcquire
 public RC Get(Pgno pgno, ref DbPage ppPage)
 {
     return Get(pgno, ref ppPage, 0);
 }
示例#16
0
        // was:sqlite3PagerWrite
        public static RC Write(DbPage pDbPage)
        {
            var rc = RC.OK;
            var pPg = pDbPage;
            var pPager = pPg.Pager;
            var nPagePerSector = (uint)(pPager.sectorSize / pPager.pageSize);
            Debug.Assert(pPager.eState >= PAGER.WRITER_LOCKED);
            Debug.Assert(pPager.eState != PAGER.ERROR);
            Debug.Assert(pPager.assert_pager_state());
            if (nPagePerSector > 1)
            {
                Pgno nPageCount = 0;     // Total number of pages in database file
                Pgno pg1;                // First page of the sector pPg is located on.
                Pgno nPage = 0;          // Number of pages starting at pg1 to journal
                bool needSync = false;   // True if any page has PGHDR_NEED_SYNC

                // Set the doNotSyncSpill flag to 1. This is because we cannot allow a journal header to be written between the pages journaled by
                // this function.
                Debug.Assert(
            #if SQLITE_OMIT_MEMORYDB
            0==MEMDB
            #else
            0 == pPager.memDb
            #endif
            );
                Debug.Assert(pPager.doNotSyncSpill == 0);
                pPager.doNotSyncSpill++;
                // This trick assumes that both the page-size and sector-size are an integer power of 2. It sets variable pg1 to the identifier
                // of the first page of the sector pPg is located on.
                pg1 = (Pgno)((pPg.ID - 1) & ~(nPagePerSector - 1)) + 1;
                nPageCount = pPager.dbSize;
                if (pPg.ID > nPageCount)
                    nPage = (pPg.ID - pg1) + 1;
                else if ((pg1 + nPagePerSector - 1) > nPageCount)
                    nPage = nPageCount + 1 - pg1;
                else
                    nPage = nPagePerSector;
                Debug.Assert(nPage > 0);
                Debug.Assert(pg1 <= pPg.ID);
                Debug.Assert((pg1 + nPage) > pPg.ID);
                for (var ii = 0; ii < nPage && rc == RC.OK; ii++)
                {
                    var pg = (Pgno)(pg1 + ii);
                    var pPage = new PgHdr();
                    if (pg == pPg.ID || pPager.pInJournal.Get(pg) == 0)
                    {
                        if (pg != ((VirtualFile.PENDING_BYTE / (pPager.pageSize)) + 1))
                        {
                            rc = pPager.Get(pg, ref pPage);
                            if (rc == RC.OK)
                            {
                                rc = pager_write(pPage);
                                if ((pPage.Flags & PgHdr.PGHDR.NEED_SYNC) != 0)
                                    needSync = true;
                                Unref(pPage);
                            }
                        }
                    }
                    else if ((pPage = pPager.pager_lookup(pg)) != null)
                    {
                        if ((pPage.Flags & PgHdr.PGHDR.NEED_SYNC) != 0)
                            needSync = true;
                        Unref(pPage);
                    }
                }
                // If the PGHDR_NEED_SYNC flag is set for any of the nPage pages starting at pg1, then it needs to be set for all of them. Because
                // writing to any of these nPage pages may damage the others, the journal file must contain sync()ed copies of all of them
                // before any of them can be written out to the database file.
                if (rc == RC.OK && needSync)
                {
                    Debug.Assert(
            #if SQLITE_OMIT_MEMORYDB
            0==MEMDB
            #else
            0 == pPager.memDb
            #endif
            );
                    for (var ii = 0; ii < nPage; ii++)
                    {
                        var pPage = pPager.pager_lookup((Pgno)(pg1 + ii));
                        if (pPage != null)
                        {
                            pPage.Flags |= PgHdr.PGHDR.NEED_SYNC;
                            Unref(pPage);
                        }
                    }
                }
                Debug.Assert(pPager.doNotSyncSpill == 1);
                pPager.doNotSyncSpill--;
            }
            else
                rc = pager_write(pDbPage);
            return rc;
        }
示例#17
0
 public RC sqlite3PagerMovepage(DbPage pPg, Pgno pgno, int isCommit)
 {
     Debug.Assert(pPg.Refs > 0);
     Debug.Assert(this.eState == PAGER.WRITER_CACHEMOD || this.eState == PAGER.WRITER_DBMOD);
     Debug.Assert(assert_pager_state());
     // In order to be able to rollback, an in-memory database must journal the page we are moving from.
     var rc = RC.OK;
     if (
     #if SQLITE_OMIT_MEMORYDB
     1==MEMDB
     #else
     this.memDb != 0
     #endif
     )
     {
         rc = Write(pPg);
         if (rc != RC.OK)
             return rc;
     }
     PgHdr pPgOld;                // The page being overwritten.
     uint needSyncPgno = 0;       // Old value of pPg.pgno, if sync is required
     Pgno origPgno;               // The original page number
     // If the page being moved is dirty and has not been saved by the latest savepoint, then save the current contents of the page into the
     // sub-journal now. This is required to handle the following scenario:
     //   BEGIN;
     //     <journal page X, then modify it in memory>
     //     SAVEPOINT one;
     //       <Move page X to location Y>
     //     ROLLBACK TO one;
     // If page X were not written to the sub-journal here, it would not be possible to restore its contents when the "ROLLBACK TO one"
     // statement were is processed.
     // subjournalPage() may need to allocate space to store pPg.pgno into one or more savepoint bitvecs. This is the reason this function
     // may return SQLITE_NOMEM.
     if ((pPg.Flags & PgHdr.PGHDR.DIRTY) != 0 && subjRequiresPage(pPg) && RC.OK != (rc = subjournalPage(pPg)))
         return rc;
     PAGERTRACE("MOVE {0} page {1} (needSync={2}) moves to {3}",
     PAGERID(this), pPg.ID, (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 ? 1 : 0, pgno);
     SysEx.IOTRACE("MOVE {0} {1} {2}", this.GetHashCode(), pPg.ID, pgno);
     // If the journal needs to be sync()ed before page pPg.pgno can be written to, store pPg.pgno in local variable needSyncPgno.
     // If the isCommit flag is set, there is no need to remember that the journal needs to be sync()ed before database page pPg.pgno
     // can be written to. The caller has already promised not to write to it.
     if (((pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0) && 0 == isCommit)
     {
         needSyncPgno = pPg.ID;
         Debug.Assert(pageInJournal(pPg) || pPg.ID > this.dbOrigSize);
         Debug.Assert((pPg.Flags & PgHdr.PGHDR.DIRTY) != 0);
     }
     // If the cache contains a page with page-number pgno, remove it from its hash chain. Also, if the PGHDR_NEED_SYNC was set for
     // page pgno before the 'move' operation, it needs to be retained for the page moved there.
     pPg.Flags &= ~PgHdr.PGHDR.NEED_SYNC;
     pPgOld = pager_lookup(pgno);
     Debug.Assert(null == pPgOld || pPgOld.Refs == 1);
     if (pPgOld != null)
     {
         pPg.Flags |= (pPgOld.Flags & PgHdr.PGHDR.NEED_SYNC);
         if (
     #if SQLITE_OMIT_MEMORYDB
     1==MEMDB
     #else
     this.memDb != 0
     #endif
     )
             // Do not discard pages from an in-memory database since we might need to rollback later.  Just move the page out of the way.
             PCache.MovePage(pPgOld, this.dbSize + 1);
         else
             PCache.DropPage(pPgOld);
     }
     origPgno = pPg.ID;
     PCache.MovePage(pPg, pgno);
     PCache.MakePageDirty(pPg);
     // For an in-memory database, make sure the original page continues to exist, in case the transaction needs to roll back.  Use pPgOld
     // as the original page since it has already been allocated.
     if (
     #if SQLITE_OMIT_MEMORYDB
     0!=MEMDB
     #else
     0 != this.memDb
     #endif
     )
     {
         Debug.Assert(pPgOld);
         PCache.MovePage(pPgOld, origPgno);
         Unref(pPgOld);
     }
     if (needSyncPgno != 0)
     {
         // If needSyncPgno is non-zero, then the journal file needs to be sync()ed before any data is written to database file page needSyncPgno.
         // Currently, no such page exists in the page-cache and the "is journaled" bitvec flag has been set. This needs to be remedied by
         // loading the page into the pager-cache and setting the PGHDR_NEED_SYNC flag.
         // If the attempt to load the page into the page-cache fails, (due to a malloc() or IO failure), clear the bit in the pInJournal[]
         // array. Otherwise, if the page is loaded and written again in this transaction, it may be written to the database file before
         // it is synced into the journal file. This way, it may end up in the journal file twice, but that is not a problem.
         PgHdr pPgHdr = null;
         rc = Get(needSyncPgno, ref pPgHdr);
         if (rc != RC.OK)
         {
             if (needSyncPgno <= this.dbOrigSize)
             {
                 Debug.Assert(this.pTmpSpace != null);
                 var pTemp = new uint[this.pTmpSpace.Length];
                 this.pInJournal.Clear(needSyncPgno, pTemp);
             }
             return rc;
         }
         pPgHdr.Flags |= PgHdr.PGHDR.NEED_SYNC;
         PCache.MakePageDirty(pPgHdr);
         Unref(pPgHdr);
     }
     return RC.OK;
 }
示例#18
0
        // was:sqlite3PagerWrite
        public static RC Write(DbPage pDbPage)
        {
            var rc             = RC.OK;
            var pPg            = pDbPage;
            var pPager         = pPg.Pager;
            var nPagePerSector = (uint)(pPager.sectorSize / pPager.pageSize);

            Debug.Assert(pPager.eState >= PAGER.WRITER_LOCKED);
            Debug.Assert(pPager.eState != PAGER.ERROR);
            Debug.Assert(pPager.assert_pager_state());
            if (nPagePerSector > 1)
            {
                Pgno nPageCount = 0;     // Total number of pages in database file
                Pgno pg1;                // First page of the sector pPg is located on.
                Pgno nPage    = 0;       // Number of pages starting at pg1 to journal
                bool needSync = false;   // True if any page has PGHDR_NEED_SYNC

                // Set the doNotSyncSpill flag to 1. This is because we cannot allow a journal header to be written between the pages journaled by
                // this function.
                Debug.Assert(
#if SQLITE_OMIT_MEMORYDB
                    0 == MEMDB
#else
                    0 == pPager.memDb
#endif
                    );
                Debug.Assert(pPager.doNotSyncSpill == 0);
                pPager.doNotSyncSpill++;
                // This trick assumes that both the page-size and sector-size are an integer power of 2. It sets variable pg1 to the identifier
                // of the first page of the sector pPg is located on.
                pg1        = (Pgno)((pPg.ID - 1) & ~(nPagePerSector - 1)) + 1;
                nPageCount = pPager.dbSize;
                if (pPg.ID > nPageCount)
                {
                    nPage = (pPg.ID - pg1) + 1;
                }
                else if ((pg1 + nPagePerSector - 1) > nPageCount)
                {
                    nPage = nPageCount + 1 - pg1;
                }
                else
                {
                    nPage = nPagePerSector;
                }
                Debug.Assert(nPage > 0);
                Debug.Assert(pg1 <= pPg.ID);
                Debug.Assert((pg1 + nPage) > pPg.ID);
                for (var ii = 0; ii < nPage && rc == RC.OK; ii++)
                {
                    var pg    = (Pgno)(pg1 + ii);
                    var pPage = new PgHdr();
                    if (pg == pPg.ID || pPager.pInJournal.Get(pg) == 0)
                    {
                        if (pg != ((VirtualFile.PENDING_BYTE / (pPager.pageSize)) + 1))
                        {
                            rc = pPager.Get(pg, ref pPage);
                            if (rc == RC.OK)
                            {
                                rc = pager_write(pPage);
                                if ((pPage.Flags & PgHdr.PGHDR.NEED_SYNC) != 0)
                                {
                                    needSync = true;
                                }
                                Unref(pPage);
                            }
                        }
                    }
                    else if ((pPage = pPager.pager_lookup(pg)) != null)
                    {
                        if ((pPage.Flags & PgHdr.PGHDR.NEED_SYNC) != 0)
                        {
                            needSync = true;
                        }
                        Unref(pPage);
                    }
                }
                // If the PGHDR_NEED_SYNC flag is set for any of the nPage pages starting at pg1, then it needs to be set for all of them. Because
                // writing to any of these nPage pages may damage the others, the journal file must contain sync()ed copies of all of them
                // before any of them can be written out to the database file.
                if (rc == RC.OK && needSync)
                {
                    Debug.Assert(
#if SQLITE_OMIT_MEMORYDB
                        0 == MEMDB
#else
                        0 == pPager.memDb
#endif
                        );
                    for (var ii = 0; ii < nPage; ii++)
                    {
                        var pPage = pPager.pager_lookup((Pgno)(pg1 + ii));
                        if (pPage != null)
                        {
                            pPage.Flags |= PgHdr.PGHDR.NEED_SYNC;
                            Unref(pPage);
                        }
                    }
                }
                Debug.Assert(pPager.doNotSyncSpill == 1);
                pPager.doNotSyncSpill--;
            }
            else
            {
                rc = pager_write(pDbPage);
            }
            return(rc);
        }
示例#19
0
 public RC Get(Pgno pgno, ref DbPage ppPage, byte noContent)
 {
     Debug.Assert(eState >= PAGER.READER);
     Debug.Assert(assert_pager_state());
     if (pgno == 0)
         return SysEx.SQLITE_CORRUPT_BKPT();
     // If the pager is in the error state, return an error immediately.  Otherwise, request the page from the PCache layer.
     var rc = (errCode != RC.OK ? errCode : pPCache.FetchPage(pgno, 1, ref ppPage));
     PgHdr pPg = null;
     if (rc != RC.OK)
     {
         // Either the call to sqlite3PcacheFetch() returned an error or the pager was already in the error-state when this function was called.
         // Set pPg to 0 and jump to the exception handler.  */
         pPg = null;
         goto pager_get_err;
     }
     Debug.Assert((ppPage).ID == pgno);
     Debug.Assert((ppPage).Pager == this || (ppPage).Pager == null);
     if ((ppPage).Pager != null && 0 == noContent)
     {
         // In this case the pcache already contains an initialized copy of the page. Return without further ado.
         Debug.Assert(pgno <= PAGER_MAX_PGNO && pgno != PAGER_MJ_PGNO(this));
         return RC.OK;
     }
     else
     {
         // The pager cache has created a new page. Its content needs to be initialized.
         pPg = ppPage;
         pPg.Pager = this;
         pPg.Extra = _memPageBuilder;
         // The maximum page number is 2^31. Return SQLITE_CORRUPT if a page number greater than this, or the unused locking-page, is requested.
         if (pgno > PAGER_MAX_PGNO || pgno == PAGER_MJ_PGNO(this))
         {
             rc = SysEx.SQLITE_CORRUPT_BKPT();
             goto pager_get_err;
         }
         if (
     #if SQLITE_OMIT_MEMORYDB
     1==MEMDB
     #else
     memDb != 0
     #endif
      || dbSize < pgno || noContent != 0 || !fd.IsOpen)
         {
             if (pgno > mxPgno)
             {
                 rc = RC.FULL;
                 goto pager_get_err;
             }
             if (noContent != 0)
             {
                 // Failure to set the bits in the InJournal bit-vectors is benign. It merely means that we might do some extra work to journal a
                 // page that does not need to be journaled.  Nevertheless, be sure to test the case where a malloc error occurs while trying to set
                 // a bit in a bit vector.
                 MallocEx.sqlite3BeginBenignMalloc();
                 if (pgno <= dbOrigSize)
                     pInJournal.Set(pgno);
                 addToSavepointBitvecs(pgno);
                 MallocEx.sqlite3EndBenignMalloc();
             }
             Array.Clear(pPg.Data, 0, pageSize);
             SysEx.IOTRACE("ZERO {0:x} {1}\n", this.GetHashCode(), pgno);
         }
         else
         {
             Debug.Assert(pPg.Pager == this);
             rc = readDbPage(pPg);
             if (rc != RC.OK)
                 goto pager_get_err;
         }
         pager_set_pagehash(pPg);
     }
     return RC.OK;
     pager_get_err:
     Debug.Assert(rc != RC.OK);
     if (pPg != null)
         PCache.DropPage(pPg);
     pagerUnlockIfUnused();
     ppPage = null;
     return rc;
 }
示例#20
0
        public RC Get(Pgno pgno, ref DbPage ppPage, byte noContent)
        {
            Debug.Assert(eState >= PAGER.READER);
            Debug.Assert(assert_pager_state());
            if (pgno == 0)
            {
                return(SysEx.SQLITE_CORRUPT_BKPT());
            }
            // If the pager is in the error state, return an error immediately.  Otherwise, request the page from the PCache layer.
            var   rc  = (errCode != RC.OK ? errCode : pPCache.FetchPage(pgno, 1, ref ppPage));
            PgHdr pPg = null;

            if (rc != RC.OK)
            {
                // Either the call to sqlite3PcacheFetch() returned an error or the pager was already in the error-state when this function was called.
                // Set pPg to 0 and jump to the exception handler.  */
                pPg = null;
                goto pager_get_err;
            }
            Debug.Assert((ppPage).ID == pgno);
            Debug.Assert((ppPage).Pager == this || (ppPage).Pager == null);
            if ((ppPage).Pager != null && 0 == noContent)
            {
                // In this case the pcache already contains an initialized copy of the page. Return without further ado.
                Debug.Assert(pgno <= PAGER_MAX_PGNO && pgno != PAGER_MJ_PGNO(this));
                return(RC.OK);
            }
            else
            {
                // The pager cache has created a new page. Its content needs to be initialized.
                pPg       = ppPage;
                pPg.Pager = this;
                pPg.Extra = _memPageBuilder;
                // The maximum page number is 2^31. Return SQLITE_CORRUPT if a page number greater than this, or the unused locking-page, is requested.
                if (pgno > PAGER_MAX_PGNO || pgno == PAGER_MJ_PGNO(this))
                {
                    rc = SysEx.SQLITE_CORRUPT_BKPT();
                    goto pager_get_err;
                }
                if (
#if SQLITE_OMIT_MEMORYDB
                    1 == MEMDB
#else
                    memDb != 0
#endif
                    || dbSize < pgno || noContent != 0 || !fd.IsOpen)
                {
                    if (pgno > mxPgno)
                    {
                        rc = RC.FULL;
                        goto pager_get_err;
                    }
                    if (noContent != 0)
                    {
                        // Failure to set the bits in the InJournal bit-vectors is benign. It merely means that we might do some extra work to journal a
                        // page that does not need to be journaled.  Nevertheless, be sure to test the case where a malloc error occurs while trying to set
                        // a bit in a bit vector.
                        MallocEx.sqlite3BeginBenignMalloc();
                        if (pgno <= dbOrigSize)
                        {
                            pInJournal.Set(pgno);
                        }
                        addToSavepointBitvecs(pgno);
                        MallocEx.sqlite3EndBenignMalloc();
                    }
                    Array.Clear(pPg.Data, 0, pageSize);
                    SysEx.IOTRACE("ZERO {0:x} {1}\n", this.GetHashCode(), pgno);
                }