if (tranId != null)
peekAmount += LogRecord.maxTransactionIdStoredSize(tranId);
int readAmount; // the number of bytes actually read
LogRecord lr;
long curpos = scan.getFilePointer();
do
{
// this log record is a candidate unless proven otherwise
candidate = true;
lr = null;
readAmount = -1;
if (curpos == LogToFile.LOG_FILE_HEADER_SIZE)
{
// don't go thru the trouble of switching log file if we
// will have gone past stopAt
if (stopAt != LogCounter.INVALID_LOG_INSTANT &&
LogCounter.getLogFileNumber(stopAt) == currentLogFileNumber)
{
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"stopping at " + currentLogFileNumber);
}
}
return null; // no more log record
}
// figure out where the last log record is in the previous
// log file
scan.seek(LogToFile.LOG_FILE_HEADER_PREVIOUS_LOG_INSTANT_OFFSET);
long previousLogInstant = scan.readLong();
scan.close();
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(previousLogInstant != LogCounter.INVALID_LOG_INSTANT,
"scanning backward beyond the first log file");
if (currentLogFileNumber !=
LogCounter.getLogFileNumber(previousLogInstant) + 1)
SanityManager.THROWASSERT(
"scanning backward but get incorrect log file number " +
"expected " + (currentLogFileNumber -1) +
"get " +
LogCounter.getLogFileNumber(previousLogInstant));
SanityManager.ASSERT(LogCounter.getLogFilePosition(previousLogInstant) >
LogToFile.LOG_FILE_HEADER_SIZE,
"scanning backward encounter completely empty log file");
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"scanning backwards from log file " +
currentLogFileNumber + ", switch to (" +
LogCounter.getLogFileNumber(previousLogInstant) + "," +
LogCounter.getLogFilePosition(previousLogInstant) + ")"
);
}
// log file switch, set this.currentLogFileNumber
currentLogFileNumber = LogCounter.getLogFileNumber(previousLogInstant);
scan = logFactory.getLogFileAtPosition(previousLogInstant);
// scan is located right past the last byte of the last log
// record in the previous log file. currentLogFileNumber is
// set. We asserted that the scan is not located right at the
// end of the file header, in other words, there is at least
// one log record in this log file.
curpos = scan.getFilePointer();
// if the log file happens to be empty skip and proceed.
// ideally this case should never occur because log switch is
// not suppose to happen on an empty log file.
// But it is safer to put following check incase if it ever
// happens to avoid any recovery issues.
if (curpos == LogToFile.LOG_FILE_HEADER_SIZE)
continue;
}
scan.seek(curpos - 4);
int recordLength = scan.readInt(); // get the length after the log record
// calculate where this log record started.
// include the eight bytes for the long log instant at the front
// the four bytes of length in the front and the four bytes we just read
long recordStartPosition = curpos - recordLength -
LogToFile.LOG_RECORD_OVERHEAD;
if (SanityManager.DEBUG)
{
if (recordStartPosition < LogToFile.LOG_FILE_HEADER_SIZE)
SanityManager.THROWASSERT(
"next position " + recordStartPosition +
" recordLength " + recordLength +
" current file position " + scan.getFilePointer());
scan.seek(recordStartPosition);
// read the length before the log record and check it against the
// length after the log record
int checkLength = scan.readInt();
if (checkLength != recordLength)
{
long inst = LogCounter.makeLogInstantAsLong(currentLogFileNumber, recordStartPosition);
throw logFactory.markCorrupt(
StandardException.newException(
SQLState.LOG_RECORD_CORRUPTED,
new Long(checkLength),
new Long(recordLength),
new Long(inst),
new Long(currentLogFileNumber)));
}
}
else
{
// skip over the length in insane
scan.seek(recordStartPosition+4);
}
// scan is positioned just before the log instant
// read the current log instant - this is the currentInstant if we have not
// exceeded the scan limit
currentInstant = scan.readLong();
if (SanityManager.DEBUG)
{
// sanity check the current instant against the scan position
if (LogCounter.getLogFileNumber(currentInstant) !=
currentLogFileNumber ||
LogCounter.getLogFilePosition(currentInstant) !=
recordStartPosition)
SanityManager.THROWASSERT(
"Wrong LogInstant on log record " +
LogCounter.toDebugString(currentInstant) +
" version real position (" +
currentLogFileNumber + "," +
recordStartPosition + ")");
}
// if stopAt == INVALID_LOG_INSTANT, no stop instant, read till
// nothing more can be read. Else check scan limit
if (currentInstant < stopAt && stopAt != LogCounter.INVALID_LOG_INSTANT)
{
currentInstant = LogCounter.INVALID_LOG_INSTANT;
return null; // we went past the stopAt
}
byte[] data = input.getData();
if (data.length < recordLength)
{
// make a new array of sufficient size and reset the arrary
// in the input stream
data = new byte[recordLength];
input.setData(data);
}
// If the log is encrypted, we must do the filtering after reading
// and decrypting the record.
if (logFactory.databaseEncrypted())
{
scan.readFully(data, 0, recordLength);
int len = logFactory.decrypt(data, 0, recordLength, data, 0);
if (SanityManager.DEBUG)
SanityManager.ASSERT(len == recordLength);
input.setLimit(0, recordLength);
}
else // no need to decrypt, only get the group and tid if we filter
{
if (groupmask == 0 && tranId == null)
{
// no filter, get the whole thing
scan.readFully(data, 0, recordLength);
input.setLimit(0, recordLength);
}
else
{
// Read only enough so that group and the tran id is in
// the data buffer. Group is stored as compressed int
// and tran id is stored as who knows what. read min
// of peekAmount or recordLength
readAmount = (recordLength > peekAmount) ?
peekAmount : recordLength;
// in the data buffer, we now have enough to peek
scan.readFully(data, 0, readAmount);
input.setLimit(0, readAmount);
}
}
lr = (LogRecord) input.readObject();
if (groupmask != 0 || tranId != null)
{
if (groupmask != 0 && (groupmask & lr.group()) == 0)
candidate = false; // no match, throw this log record out
if (candidate && tranId != null)
{
TransactionId tid = lr.getTransactionId();
if (!tid.equals(tranId)) // nomatch
candidate = false; // throw this log record out
}
// if this log record is not filtered out, we need to read