/* ** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback ** the entire master journal file. The case pSavepoint==NULL occurs when ** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction ** savepoint. ** ** When pSavepoint is not NULL (meaning a non-transaction savepoint is ** being rolled back), then the rollback consists of up to three stages, ** performed in the order specified: ** ** * Pages are played back from the main journal starting at byte ** offset PagerSavepoint.iOffset and continuing to ** PagerSavepoint.iHdrOffset, or to the end of the main journal ** file if PagerSavepoint.iHdrOffset is zero. ** ** * If PagerSavepoint.iHdrOffset is not zero, then pages are played ** back starting from the journal header immediately following ** PagerSavepoint.iHdrOffset to the end of the main journal file. ** ** * Pages are then played back from the sub-journal file, starting ** with the PagerSavepoint.iSubRec and continuing to the end of ** the journal file. ** ** Throughout the rollback process, each time a page is rolled back, the ** corresponding bit is set in a bitvec structure (variable pDone in the ** implementation below). This is used to ensure that a page is only ** rolled back the first time it is encountered in either journal. ** ** If pSavepoint is NULL, then pages are only played back from the main ** journal file. There is no need for a bitvec in this case. ** ** In either case, before playback commences the Pager.dbSize variable ** is reset to the value that it held at the start of the savepoint ** (or transaction). No page with a page-number greater than this value ** is played back. If one is encountered it is simply skipped. */ static int pagerPlaybackSavepoint( Pager pPager, PagerSavepoint pSavepoint ) { i64 szJ; /* Effective size of the main journal */ i64 iHdrOff; /* End of first segment of main-journal records */ int rc = SQLITE_OK; /* Return code */ Bitvec pDone = null; /* Bitvec to ensure pages played back only once */ Debug.Assert( pPager.state >= PAGER_SHARED ); /* Allocate a bitvec to use to store the set of pages rolled back */ if ( pSavepoint != null ) { pDone = sqlite3BitvecCreate( pSavepoint.nOrig ); if ( null == pDone ) { return SQLITE_NOMEM; } } /* Set the database size back to the value it was before the savepoint ** being reverted was opened. */ pPager.dbSize = pSavepoint != null ? pSavepoint.nOrig : pPager.dbOrigSize; /* Use pPager.journalOff as the effective size of the main rollback ** journal. The actual file might be larger than this in ** PAGER_JOURNALMODE_TRUNCATE or PAGER_JOURNALMODE_PERSIST. But anything ** past pPager.journalOff is off-limits to us. */ szJ = pPager.journalOff; /* Begin by rolling back records from the main journal starting at ** PagerSavepoint.iOffset and continuing to the next journal header. ** There might be records in the main journal that have a page number ** greater than the current database size (pPager.dbSize) but those ** will be skipped automatically. Pages are added to pDone as they ** are played back. */ if ( pSavepoint != null ) { iHdrOff = pSavepoint.iHdrOffset != 0 ? pSavepoint.iHdrOffset : szJ; pPager.journalOff = pSavepoint.iOffset; while ( rc == SQLITE_OK && pPager.journalOff < iHdrOff ) { rc = pager_playback_one_page( pPager, 1, 0, ref pPager.journalOff, 1, pDone ); } Debug.Assert( rc != SQLITE_DONE ); } else { pPager.journalOff = 0; } /* Continue rolling back records out of the main journal starting at ** the first journal header seen and continuing until the effective end ** of the main journal file. Continue to skip out-of-range pages and ** continue adding pages rolled back to pDone. */ while ( rc == SQLITE_OK && pPager.journalOff < szJ ) { u32 ii; /* Loop counter */ u32 nJRec = 0; /* Number of Journal Records */ u32 dummy = 0; rc = readJournalHdr( pPager, 0, (int)szJ, ref nJRec, ref dummy ); Debug.Assert( rc != SQLITE_DONE ); /* ** The "pPager.journalHdr+JOURNAL_HDR_SZ(pPager)==pPager.journalOff" ** test is related to ticket #2565. See the discussion in the ** pager_playback() function for additional information. */ if ( nJRec == 0 && pPager.journalHdr + JOURNAL_HDR_SZ( pPager ) == pPager.journalOff ) { nJRec = (u32)( ( szJ - pPager.journalOff ) / JOURNAL_PG_SZ( pPager ) ); } for ( ii = 0; rc == SQLITE_OK && ii < nJRec && pPager.journalOff < szJ; ii++ ) { rc = pager_playback_one_page( pPager, 1, 0, ref pPager.journalOff, 1, pDone ); } Debug.Assert( rc != SQLITE_DONE ); } Debug.Assert( rc != SQLITE_OK || pPager.journalOff == szJ ); /* Finally, rollback pages from the sub-journal. Page that were ** previously rolled back out of the main journal (and are hence in pDone) ** will be skipped. Out-of-range pages are also skipped. */ if ( pSavepoint != null ) { u32 ii; /* Loop counter */ i64 offset = pSavepoint.iSubRec * ( 4 + pPager.pageSize ); for ( ii = pSavepoint.iSubRec; rc == SQLITE_OK && ii < pPager.nSubRec; ii++ ) { Debug.Assert( offset == ii * ( 4 + pPager.pageSize ) ); rc = pager_playback_one_page( pPager, 0, 0, ref offset, 1, pDone ); } Debug.Assert( rc != SQLITE_DONE ); } sqlite3BitvecDestroy( ref pDone ); if ( rc == SQLITE_OK ) { pPager.journalOff = (int)szJ; } return rc; }
/* ** Check that there are at least nSavepoint savepoints open. If there are ** currently less than nSavepoints open, then open one or more savepoints ** to make up the difference. If the number of savepoints is already ** equal to nSavepoint, then this function is a no-op. ** ** If a memory allocation fails, SQLITE_NOMEM is returned. If an error ** occurs while opening the sub-journal file, then an IO error code is ** returned. Otherwise, SQLITE_OK. */ static int sqlite3PagerOpenSavepoint( Pager pPager, int nSavepoint ) { int rc = SQLITE_OK; /* Return code */ int nCurrent = pPager.nSavepoint; /* Current number of savepoints */ if ( nSavepoint > nCurrent && pPager.useJournal != 0 ) { int ii; /* Iterator variable */ PagerSavepoint[] aNew; /* New Pager.aSavepoint array */ /* Either there is no active journal or the sub-journal is open or ** the journal is always stored in memory */ Debug.Assert( pPager.nSavepoint == 0 || isOpen( pPager.sjfd ) || pPager.journalMode == PAGER_JOURNALMODE_MEMORY ); /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM ** if the allocation fails. Otherwise, zero the new portion in case a ** malloc failure occurs while populating it in the for(...) loop below. */ //aNew = (PagerSavepoint *)sqlite3Realloc( // pPager->aSavepoint, sizeof(PagerSavepoint)*nSavepoint //); Array.Resize( ref pPager.aSavepoint, nSavepoint ); aNew = pPager.aSavepoint; //if( null==aNew ){ // return SQLITE_NOMEM; //} // memset(&aNew[nCurrent], 0, (nSavepoint-nCurrent) * sizeof(PagerSavepoint)); // pPager.aSavepoint = aNew; pPager.nSavepoint = nSavepoint; /* Populate the PagerSavepoint structures just allocated. */ for ( ii = nCurrent; ii < nSavepoint; ii++ ) { Debug.Assert( pPager.dbSizeValid ); aNew[ii] = new PagerSavepoint(); aNew[ii].nOrig = pPager.dbSize; if ( isOpen( pPager.jfd ) && ALWAYS( pPager.journalOff > 0 ) ) { aNew[ii].iOffset = pPager.journalOff; } else { aNew[ii].iOffset = (int)JOURNAL_HDR_SZ( pPager ); } aNew[ii].iSubRec = pPager.nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate( pPager.dbSize ); if ( null == aNew[ii].pInSavepoint ) { return SQLITE_NOMEM; } } /* Open the sub-journal, if it is not already opened. */ rc = openSubJournal( pPager ); assertTruncateConstraint( pPager ); } return rc; }
/* ** Check that there are at least nSavepoint savepoints open. If there are ** currently less than nSavepoints open, then open one or more savepoints ** to make up the difference. If the number of savepoints is already ** equal to nSavepoint, then this function is a no-op. ** ** If a memory allocation fails, SQLITE_NOMEM is returned. If an error ** occurs while opening the sub-journal file, then an IO error code is ** returned. Otherwise, SQLITE_OK. */ static int sqlite3PagerOpenSavepoint( Pager pPager, int nSavepoint ) { int rc = SQLITE_OK; /* Return code */ int nCurrent = pPager.nSavepoint; /* Current number of savepoints */ Debug.Assert( pPager.eState >= PAGER_WRITER_LOCKED ); Debug.Assert( assert_pager_state( pPager ) ); if ( nSavepoint > nCurrent && pPager.useJournal != 0 ) { int ii; /* Iterator variable */ PagerSavepoint[] aNew; /* New Pager.aSavepoint array */ /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM ** if the allocation fails. Otherwise, zero the new portion in case a ** malloc failure occurs while populating it in the for(...) loop below. */ //aNew = (PagerSavepoint *)sqlite3Realloc( // pPager.aSavepoint, sizeof(PagerSavepoint)*nSavepoint //); Array.Resize( ref pPager.aSavepoint, nSavepoint ); aNew = pPager.aSavepoint; //if( null==aNew ){ // return SQLITE_NOMEM; //} // memset(&aNew[nCurrent], 0, (nSavepoint-nCurrent) * sizeof(PagerSavepoint)); // pPager.aSavepoint = aNew; /* Populate the PagerSavepoint structures just allocated. */ for ( ii = nCurrent; ii < nSavepoint; ii++ ) { aNew[ii] = new PagerSavepoint(); aNew[ii].nOrig = pPager.dbSize; if ( isOpen( pPager.jfd ) && pPager.journalOff > 0 ) { aNew[ii].iOffset = pPager.journalOff; } else { aNew[ii].iOffset = (int)JOURNAL_HDR_SZ( pPager ); } aNew[ii].iSubRec = pPager.nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate( pPager.dbSize ); if ( null == aNew[ii].pInSavepoint ) { return SQLITE_NOMEM; } if ( pagerUseWal( pPager ) ) { sqlite3WalSavepoint( pPager.pWal, aNew[ii].aWalData ); } pPager.nSavepoint = ii + 1; } Debug.Assert( pPager.nSavepoint == nSavepoint ); assertTruncateConstraint( pPager ); } return rc; }