package com.sleepycat.je.log;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.cleaner.TrackedFileSummary;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.entry.INContainingEntry;
import com.sleepycat.je.log.entry.INLogEntry;
import com.sleepycat.je.log.entry.LNLogEntry;
import com.sleepycat.je.log.entry.LogEntry;
import com.sleepycat.je.log.entry.NodeLogEntry;
import com.sleepycat.je.tree.FileSummaryLN;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.INDeleteInfo;
import com.sleepycat.je.tree.INDupDeleteInfo;
import com.sleepycat.je.tree.MapLN;
import com.sleepycat.je.utilint.DbLsn;
import de.ovgu.cide.jakutil.*;
/**
* INFileReader supports recovery by scanning log files during the IN rebuild
* pass. It looks for internal nodes (all types), segregated by whether they
* belong to the main tree or the duplicate trees.
* <p>This file reader can also be run in tracking mode to keep track of the
* maximum node id, database id and txn id seen so those sequences can be
* updated properly at recovery. In this mode it also performs utilization
* counting. It is only run once in tracking mode per recovery, in the
* first phase of recovery.</p>
*/
public class INFileReader extends FileReader {
private boolean lastEntryWasDelete;
private boolean lastEntryWasDupDelete;
private LogEntryType fromLogType;
private boolean isProvisional;
private Map targetEntryMap;
private LogEntry targetLogEntry;
private Map dbIdTrackingMap;
private LNLogEntry dbIdTrackingEntry;
private Map txnIdTrackingMap;
private LNLogEntry txnIdTrackingEntry;
private Map otherNodeTrackingMap;
private NodeLogEntry nodeTrackingEntry;
private INLogEntry inTrackingEntry;
private LNLogEntry fsTrackingEntry;
private boolean trackIds;
private long maxNodeId;
private int maxDbId;
private long maxTxnId;
private boolean mapDbOnly;
private long partialCkptStart;
private UtilizationTracker tracker;
private Map fileSummaryLsns;
/**
* Create this reader to start at a given LSN.
*/
public INFileReader( EnvironmentImpl env, int readBufferSize, long startLsn, long finishLsn, boolean trackIds, boolean mapDbOnly, long partialCkptStart, Map fileSummaryLsns) throws IOException, DatabaseException {
super(env,readBufferSize,true,startLsn,null,DbLsn.NULL_LSN,finishLsn);
this.trackIds=trackIds;
this.mapDbOnly=mapDbOnly;
targetEntryMap=new HashMap();
if (trackIds) {
maxNodeId=0;
maxDbId=0;
tracker=env.getUtilizationTracker();
this.partialCkptStart=partialCkptStart;
this.fileSummaryLsns=fileSummaryLsns;
fsTrackingEntry=(LNLogEntry)LogEntryType.LOG_FILESUMMARYLN.getNewLogEntry();
dbIdTrackingMap=new HashMap();
txnIdTrackingMap=new HashMap();
otherNodeTrackingMap=new HashMap();
dbIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL,LogEntryType.LOG_MAPLN_TRANSACTIONAL.getNewLogEntry());
dbIdTrackingMap.put(LogEntryType.LOG_MAPLN,LogEntryType.LOG_MAPLN.getNewLogEntry());
txnIdTrackingMap.put(LogEntryType.LOG_LN_TRANSACTIONAL,LogEntryType.LOG_LN_TRANSACTIONAL.getNewLogEntry());
txnIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL,LogEntryType.LOG_MAPLN_TRANSACTIONAL.getNewLogEntry());
txnIdTrackingMap.put(LogEntryType.LOG_NAMELN_TRANSACTIONAL,LogEntryType.LOG_NAMELN_TRANSACTIONAL.getNewLogEntry());
txnIdTrackingMap.put(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL,LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL.getNewLogEntry());
txnIdTrackingMap.put(LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL,LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL.getNewLogEntry());
}
}
/**
* Configure this reader to target this kind of entry.
*/
public void addTargetType( LogEntryType entryType) throws DatabaseException {
targetEntryMap.put(entryType,entryType.getNewLogEntry());
}
/**
* If we're tracking node, database and txn ids, we want to see all node
* log entries. If not, we only want to see IN entries.
* @return true if this is an IN entry.
*/
protected boolean isTargetEntry( byte entryTypeNum, byte entryTypeVersion) throws DatabaseException {
lastEntryWasDelete=false;
lastEntryWasDupDelete=false;
targetLogEntry=null;
dbIdTrackingEntry=null;
txnIdTrackingEntry=null;
nodeTrackingEntry=null;
inTrackingEntry=null;
fsTrackingEntry=null;
isProvisional=LogEntryType.isProvisional(entryTypeVersion);
fromLogType=LogEntryType.findType(entryTypeNum,entryTypeVersion);
LogEntry possibleTarget=(LogEntry)targetEntryMap.get(fromLogType);
if (!isProvisional) {
targetLogEntry=possibleTarget;
}
if (LogEntryType.LOG_IN_DELETE_INFO.equals(fromLogType)) {
lastEntryWasDelete=true;
}
if (LogEntryType.LOG_IN_DUPDELETE_INFO.equals(fromLogType)) {
lastEntryWasDupDelete=true;
}
if (trackIds) {
if (!isProvisional) {
dbIdTrackingEntry=(LNLogEntry)dbIdTrackingMap.get(fromLogType);
txnIdTrackingEntry=(LNLogEntry)txnIdTrackingMap.get(fromLogType);
}
if (fromLogType.isNodeType()) {
if (possibleTarget != null) {
nodeTrackingEntry=(NodeLogEntry)possibleTarget;
}
else if (dbIdTrackingEntry != null) {
nodeTrackingEntry=dbIdTrackingEntry;
}
else if (txnIdTrackingEntry != null) {
nodeTrackingEntry=txnIdTrackingEntry;
}
else {
nodeTrackingEntry=(NodeLogEntry)otherNodeTrackingMap.get(fromLogType);
if (nodeTrackingEntry == null) {
nodeTrackingEntry=(NodeLogEntry)fromLogType.getNewLogEntry();
otherNodeTrackingMap.put(fromLogType,nodeTrackingEntry);
}
}
if (nodeTrackingEntry instanceof INLogEntry) {
inTrackingEntry=(INLogEntry)nodeTrackingEntry;
}
if (LogEntryType.LOG_FILESUMMARYLN.equals(fromLogType)) {
fsTrackingEntry=(LNLogEntry)nodeTrackingEntry;
}
}
tracker.countNewLogEntry(getLastLsn(),fromLogType,LogManager.HEADER_BYTES + currentEntrySize);
return (targetLogEntry != null) || (dbIdTrackingEntry != null) || (txnIdTrackingEntry != null)|| (nodeTrackingEntry != null);
}
else {
return (targetLogEntry != null);
}
}
/**
* This reader looks at all nodes for the max node id and database id. It
* only returns non-provisional INs and IN delete entries.
*/
protected boolean processEntry( ByteBuffer entryBuffer) throws DatabaseException {
boolean useEntry=false;
boolean entryLoaded=false;
if (targetLogEntry != null) {
targetLogEntry.readEntry(entryBuffer,currentEntrySize,currentEntryTypeVersion,true);
DatabaseId dbId=getDatabaseId();
boolean isMapDb=dbId.equals(DbTree.ID_DB_ID);
useEntry=(!mapDbOnly || isMapDb);
entryLoaded=true;
}
if (trackIds) {
LNLogEntry lnEntry=null;
if (dbIdTrackingEntry != null) {
lnEntry=dbIdTrackingEntry;
lnEntry.readEntry(entryBuffer,currentEntrySize,currentEntryTypeVersion,true);
entryLoaded=true;
MapLN mapLN=(MapLN)lnEntry.getMainItem();
int dbId=mapLN.getDatabase().getId().getId();
if (dbId > maxDbId) {
maxDbId=dbId;
}
}
if (txnIdTrackingEntry != null) {
if (lnEntry == null) {
lnEntry=txnIdTrackingEntry;
lnEntry.readEntry(entryBuffer,currentEntrySize,currentEntryTypeVersion,true);
entryLoaded=true;
}
long txnId=lnEntry.getTxnId().longValue();
if (txnId > maxTxnId) {
maxTxnId=txnId;
}
}
if (fsTrackingEntry != null) {
if (!entryLoaded) {
nodeTrackingEntry.readEntry(entryBuffer,currentEntrySize,currentEntryTypeVersion,true);
entryLoaded=true;
}
byte[] keyBytes=fsTrackingEntry.getKey();
FileSummaryLN fsln=(FileSummaryLN)fsTrackingEntry.getMainItem();
long fileNum=fsln.getFileNumber(keyBytes);
TrackedFileSummary trackedLN=tracker.getTrackedFile(fileNum);
if (trackedLN != null) {
trackedLN.reset();
}
fileSummaryLsns.put(new Long(fileNum),new Long(getLastLsn()));
}
if (nodeTrackingEntry != null) {
if (!entryLoaded) {
nodeTrackingEntry.readEntry(entryBuffer,currentEntrySize,currentEntryTypeVersion,false);
entryLoaded=true;
}
long nodeId=nodeTrackingEntry.getNodeId();
maxNodeId=(nodeId > maxNodeId) ? nodeId : maxNodeId;
}
if (inTrackingEntry != null) {
assert entryLoaded : "All nodes should have been loaded";
long oldLsn=inTrackingEntry.getObsoleteLsn();
if (oldLsn != DbLsn.NULL_LSN) {
long newLsn=getLastLsn();
if (!isObsoleteLsnAlreadyCounted(oldLsn,newLsn)) {
tracker.countObsoleteNodeInexact(oldLsn,fromLogType);
}
}
if (isProvisional && partialCkptStart != DbLsn.NULL_LSN) {
oldLsn=getLastLsn();
if (DbLsn.compareTo(partialCkptStart,oldLsn) < 0) {
tracker.countObsoleteNodeInexact(oldLsn,fromLogType);
}
}
}
}
return useEntry;
}
/**
* Returns whether a given obsolete LSN has already been counted in the
* utilization profile. If true is returned, it should not be counted
* again, to prevent double-counting.
*/
private boolean isObsoleteLsnAlreadyCounted( long oldLsn, long newLsn){
Long fileNum=new Long(DbLsn.getFileNumber(oldLsn));
long fileSummaryLsn=DbLsn.longToLsn((Long)fileSummaryLsns.get(fileNum));
int cmpFsLsnToNewLsn=(fileSummaryLsn != DbLsn.NULL_LSN) ? DbLsn.compareTo(fileSummaryLsn,newLsn) : -1;
return (cmpFsLsnToNewLsn >= 0);
}
/**
* Get the last IN seen by the reader.
*/
public IN getIN() throws DatabaseException {
return ((INContainingEntry)targetLogEntry).getIN(env);
}
/**
* Get the last databaseId seen by the reader.
*/
public DatabaseId getDatabaseId(){
if (lastEntryWasDelete) {
return ((INDeleteInfo)targetLogEntry.getMainItem()).getDatabaseId();
}
else if (lastEntryWasDupDelete) {
return ((INDupDeleteInfo)targetLogEntry.getMainItem()).getDatabaseId();
}
else {
return ((INContainingEntry)targetLogEntry).getDbId();
}
}
/**
* Get the maximum node id seen by the reader.
*/
public long getMaxNodeId(){
return maxNodeId;
}
/**
* Get the maximum db id seen by the reader.
*/
public int getMaxDbId(){
return maxDbId;
}
/**
* Get the maximum txn id seen by the reader.
*/
public long getMaxTxnId(){
return maxTxnId;
}
/**
* @return true if the last entry was a delete info entry.
*/
public boolean isDeleteInfo(){
return lastEntryWasDelete;
}
/**
* @return true if the last entry was a dup delete info entry.
*/
public boolean isDupDeleteInfo(){
return lastEntryWasDupDelete;
}
/**
* Get the deleted node id stored in the last delete info log entry.
*/
public long getDeletedNodeId(){
return ((INDeleteInfo)targetLogEntry.getMainItem()).getDeletedNodeId();
}
/**
* Get the deleted id key stored in the last delete info log entry.
*/
public byte[] getDeletedIdKey(){
return ((INDeleteInfo)targetLogEntry.getMainItem()).getDeletedIdKey();
}
/**
* Get the deleted node id stored in the last delete info log entry.
*/
public long getDupDeletedNodeId(){
return ((INDupDeleteInfo)targetLogEntry.getMainItem()).getDeletedNodeId();
}
/**
* Get the deleted main key stored in the last delete info log entry.
*/
public byte[] getDupDeletedMainKey(){
return ((INDupDeleteInfo)targetLogEntry.getMainItem()).getDeletedMainKey();
}
/**
* Get the deleted main key stored in the last delete info log entry.
*/
public byte[] getDupDeletedDupKey(){
return ((INDupDeleteInfo)targetLogEntry.getMainItem()).getDeletedDupKey();
}
/**
* Get the LSN that should represent this IN. For most INs, it's the LSN
* that was just read. For BINDelta entries, it's the LSN of the last
* full version.
*/
public long getLsnOfIN(){
return ((INContainingEntry)targetLogEntry).getLsnOfIN(getLastLsn());
}
/**
* Get the current log entry type.
*/
public LogEntryType getLogEntryType(){
return LogEntryType.findType(currentEntryTypeNum,currentEntryTypeVersion);
}
}