private LogRecord getNextRecordBackward()

in java/org.apache.derby.engine/org/apache/derby/impl/store/raw/log/Scan.java [248:526]


	private LogRecord getNextRecordBackward(ArrayInputStream input, 
									  TransactionId tranId,  
									  int groupmask) 
		 throws StandardException, IOException, ClassNotFoundException
	{
		if (SanityManager.DEBUG)
			SanityManager.ASSERT(scanDirection == BACKWARD, "can only called by backward scan");

		// scan is positioned just past the last byte of the record, or
		// right at the beginning of the file (end of the file header)
		// may need to switch log file

		boolean candidate;
		// if we have filtering, peek at the group and/or the transaction id,
		// do them in one read rather than 2 reads.
		int peekAmount = LogRecord.formatOverhead() + LogRecord.maxGroupStoredSize();
		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, 
                            checkLength,
                            recordLength,
                            inst,
                            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();

			// skip the checksum log records, there is no need to look at them 
			// during backward scans. They are used only in forwardscan during recovery. 
			if(lr.isChecksum())
			{
				candidate = false; 
			}else if (groupmask != 0 || tranId != null)
			{

				// skip the checksum log records  
				if(lr.isChecksum())
					candidate = false; 

				if (candidate && 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
				// in the rest of the log record to the input buffer.
				// Except if it is an encrypted database, in which case the
				// entire log record have already be read in for
				// decryption.
				if (candidate && !logFactory.databaseEncrypted())
				{
					// read the rest of the log into the buffer
					if (SanityManager.DEBUG)
						SanityManager.ASSERT(readAmount > 0);

					if (readAmount < recordLength)
					{
						// Need to remember where we are because the log
						// record may have read part of it off the input
						// stream already and that position is lost when we
						// set limit again.
						int inputPosition = input.getPosition();

						scan.readFully(data, readAmount,
									   recordLength-readAmount); 

						input.setLimit(0, recordLength);
						input.setPosition(inputPosition);
					}
				}
			}

			// go back to the start of the log record so that the next time
			// this method is called, it is positioned right past the last byte
			// of the record.
			curpos = recordStartPosition;
			scan.seek(curpos);

		} while (candidate == false);

		return lr;

	}