*
*/
private long playbackOnePage(boolean isMainJrnl, long pOffset, boolean isSavepnt, BitSet pDone)
throws SqlJetException {
ISqlJetPage pPg; /* An existing page in the cache */
int pgno; /* The page number of a page in journal */
long cksum; /* Checksum used for sanity checking */
ISqlJetMemoryPointer aData; /* Temporary storage for the page */
ISqlJetFile jfd; /* The file descriptor for the journal file */
assert (isMainJrnl || pDone != null); /*
* pDone always used on
* sub-journals
*/
assert (isSavepnt || pDone == null); /*
* pDone never used on
* non-savepoint
*/
aData = tmpSpace;
assert (aData != null); /* Temp storage must have already been allocated */
jfd = (isMainJrnl ? this.jfd : this.sjfd);
pgno = read32bits(jfd, pOffset);
jfd.read(aData, pageSize, pOffset + 4);
pOffset += pageSize + 4 + (isMainJrnl ? 4 : 0);
/*
* Sanity checking on the page. This is more important that I originally
* thought. If a power failure occurs while the journal is being
* written, it could cause invalid data to be written into the journal.
* We need to detect this invalid data (with high probability) and
* ignore it.
*/
if (pgno == 0 || pgno == PAGER_MJ_PGNO()) {
throw new SqlJetException(SqlJetErrorCode.DONE);
}
if (pgno > dbSize || SqlJetUtility.bitSetTest(pDone, pgno)) {
return pOffset;
}
if (isMainJrnl) {
cksum = read32bitsUnsigned(jfd, pOffset - 4);
if (!isSavepnt && cksum(aData) != cksum) {
throw new SqlJetException(SqlJetErrorCode.DONE);
}
}
if (pDone != null) {
pDone.set(pgno);
}
assert (state == SqlJetPagerState.RESERVED || state.compareTo(SqlJetPagerState.EXCLUSIVE) >= 0);
/*
* If the pager is in RESERVED state, then there must be a copy of this
* page in the pager cache. In this case just update the pager cache,
* not the database file. The page is left marked dirty in this case.
*
* An exception to the above rule: If the database is in no-sync mode
* and a page is moved during an incremental vacuum then the page may
* not be in the pager cache. Later: if a malloc() or IO error occurs
* during a Movepage() call, then the page may not be in the cache
* either. So the condition described in the above paragraph is not
* assert()able.
*
* If in EXCLUSIVE state, then we update the pager cache if it exists
* and the main file. The page is then marked not dirty.
*
* Ticket #1171: The statement journal might contain page content that
* is different from the page content at the start of the transaction.
* This occurs when a page is changed prior to the start of a statement
* then changed again within the statement. When rolling back such a
* statement we must not write to the original database unless we know
* for certain that original page contents are synced into the main
* rollback journal. Otherwise, a power loss might leave modified data
* in the database file without an entry in the rollback journal that
* can restore the database to its original form. Two conditions must be
* met before writing to the database files. (1) the database must be
* locked. (2) we know that the original page content is fully synced in
* the main journal either because the page is not in cache or else the
* page is marked as needSync==0.
*
* 2008-04-14: When attempting to vacuum a corrupt database file, it is
* possible to fail a statement on a database that does not yet exist.
* Do not attempt to write if database file has never been opened.
*/
pPg = lookup(pgno);
PAGERTRACE("PLAYBACK %s page %d hash(%08x) %s\n", PAGERID(), pgno, dataHash(pageSize, aData),
(isMainJrnl ? "main-journal" : "sub-journal"));
if (state.compareTo(SqlJetPagerState.EXCLUSIVE) >= 0
&& (pPg == null || !pPg.getFlags().contains(SqlJetPageFlags.NEED_SYNC)) && null != fd) {
long ofst = (pgno - 1) * pageSize;
fd.write(aData, pageSize, ofst);
if (pgno > dbFileSize) {
dbFileSize = pgno;
}
} else if (!isMainJrnl && pPg == null) {
/*
* If this is a rollback of a savepoint and data was not written to
* the database and the page is not in-memory, there is a potential
* problem. When the page is next fetched by the b-tree layer, it
* will be read from the database file, which may or may not be
* current.
*
* There are a couple of different ways this can happen. All are
* quite obscure. When running in synchronous mode, this can only
* happen if the page is on the free-list at the start of the
* transaction, then populated, then moved using
* sqlite3PagerMovepage().
*
* The solution is to add an in-memory page to the cache containing
* the data just read from the sub-journal. Mark the page as dirty
* and if the pager requires a journal-sync, then mark the page as
* requiring a journal-sync before it is written.
*/
assert (isSavepnt);
pPg = acquirePage(pgno, true);
pPg.getFlags().remove(SqlJetPageFlags.NEED_READ);
pageCache.makeDirty(pPg);
}
if (null != pPg) {
/*
* No page should ever be explicitly rolled back that is in use,
* except for page 1 which is held in use in order to keep the lock
* on the database active. However such a page may be rolled back as
* a result of an internal error resulting in an automatic call to
* sqlite3PagerRollback().
*/
final ISqlJetMemoryPointer pData = pPg.getData();
SqlJetUtility.memcpy(pData, aData, pageSize);
if (null != reiniter) {
reiniter.pageCallback(pPg);
}
if (isMainJrnl && (!isSavepnt || journalOff <= journalHdr)) {
/*
* If the contents of this page were just restored from the main
* journal file, then its content must be as they were when the
* transaction was first opened. In this case we can mark the
* page as clean, since there will be no need to write it out to
* the.
*
* There is one exception to this rule. If the page is being
* rolled back as part of a savepoint (or statement) rollback
* from an unsynced portion of the main journal file, then it is
* not safe to mark the page as clean. This is because marking
* the page as clean will clear the PGHDR_NEED_SYNC flag. Since
* the page is already in the journal file (recorded in
* Pager.pInJournal) and the PGHDR_NEED_SYNC flag is cleared, if
* the page is written to again within this transaction, it will
* be marked as dirty but the PGHDR_NEED_SYNC flag will not be
* set. It could then potentially be written out into the
* database file before its journal file segment is synced. If a
* crash occurs during or following this, database corruption
* may ensue.
*/
pageCache.makeClean(pPg);
}
pPg.setHash(pageHash(pPg));
/*
* If this was page 1, then restore the value of Pager.dbFileVers.
* Do this before any decoding.
*/
if (pgno == 1) {