package com.sleepycat.je.dbi;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.PreloadConfig;
import com.sleepycat.je.PreloadStats;
import com.sleepycat.je.PreloadStatus;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.SortedLSNTreeWalker.TreeNodeProcessor;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogException;
import com.sleepycat.je.log.LogReadable;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LogWritable;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.TreeUtils;
import com.sleepycat.je.tree.TreeWalkerStatsAccumulator;
import com.sleepycat.je.tree.WithRootLatched;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.ThreadLocker;
import com.sleepycat.je.utilint.CmdUtil;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.TestHook;
import de.ovgu.cide.jakutil.*;
/**
* The underlying object for a given database.
*/
public class DatabaseImpl implements LogWritable, LogReadable, Cloneable {
private DatabaseId id;
Tree tree;
private EnvironmentImpl envImpl;
private boolean duplicatesAllowed;
private boolean transactional;
private Set referringHandles;
private long eofNodeId;
private Comparator btreeComparator=null;
private Comparator duplicateComparator=null;
private String btreeComparatorName="";
private String duplicateComparatorName="";
private int binDeltaPercent;
private int binMaxDeltas;
private int maxMainTreeEntriesPerNode;
private int maxDupTreeEntriesPerNode;
private String debugDatabaseName;
private TestHook pendingDeletedHook;
/**
* Create a database object for a new database.
*/
public DatabaseImpl( String dbName, DatabaseId id, EnvironmentImpl envImpl, DatabaseConfig dbConfig) throws DatabaseException {
this.id=id;
this.envImpl=envImpl;
this.btreeComparator=dbConfig.getBtreeComparator();
this.duplicateComparator=dbConfig.getDuplicateComparator();
duplicatesAllowed=dbConfig.getSortedDuplicates();
transactional=dbConfig.getTransactional();
maxMainTreeEntriesPerNode=dbConfig.getNodeMaxEntries();
maxDupTreeEntriesPerNode=dbConfig.getNodeMaxDupTreeEntries();
initDefaultSettings();
this.hook288();
tree=new Tree(this);
referringHandles=Collections.synchronizedSet(new HashSet());
eofNodeId=Node.getNextNodeId();
debugDatabaseName=dbName;
}
/**
* Create an empty database object for initialization from the log. Note
* that the rest of the initialization comes from readFromLog(), except for
* the debugDatabaseName, which is set by the caller.
*/
public DatabaseImpl() throws DatabaseException {
id=new DatabaseId();
envImpl=null;
this.hook289();
tree=new Tree();
referringHandles=Collections.synchronizedSet(new HashSet());
eofNodeId=Node.getNextNodeId();
}
public void setDebugDatabaseName( String debugName){
debugDatabaseName=debugName;
}
public String getDebugName(){
return debugDatabaseName;
}
public void setPendingDeletedHook( TestHook hook){
pendingDeletedHook=hook;
}
/**
* Initialize configuration settings when creating a new instance or after
* reading an instance from the log. The envImpl field must be set before
* calling this method.
*/
private void initDefaultSettings() throws DatabaseException {
DbConfigManager configMgr=envImpl.getConfigManager();
binDeltaPercent=configMgr.getInt(EnvironmentParams.BIN_DELTA_PERCENT);
binMaxDeltas=configMgr.getInt(EnvironmentParams.BIN_MAX_DELTAS);
if (maxMainTreeEntriesPerNode == 0) {
maxMainTreeEntriesPerNode=configMgr.getInt(EnvironmentParams.NODE_MAX);
}
if (maxDupTreeEntriesPerNode == 0) {
maxDupTreeEntriesPerNode=configMgr.getInt(EnvironmentParams.NODE_MAX_DUPTREE);
}
}
/**
* Clone. For now just pass off to the super class for a field-by-field
* copy.
*/
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* @return the database tree.
*/
public Tree getTree(){
return tree;
}
void setTree( Tree tree){
this.tree=tree;
}
/**
* @return the database id.
*/
public DatabaseId getId(){
return id;
}
void setId( DatabaseId id){
this.id=id;
}
public long getEofNodeId(){
return eofNodeId;
}
/**
* @return true if this database is transactional.
*/
public boolean isTransactional(){
return transactional;
}
/**
* Sets the transactional property for the first opened handle.
*/
public void setTransactional( boolean transactional){
this.transactional=transactional;
}
/**
* @return true if duplicates are allowed in this database.
*/
public boolean getSortedDuplicates(){
return duplicatesAllowed;
}
public int getNodeMaxEntries(){
return maxMainTreeEntriesPerNode;
}
public int getNodeMaxDupTreeEntries(){
return maxDupTreeEntriesPerNode;
}
/**
* Set the duplicate comparison function for this database.
* @param duplicateComparator -
* The Duplicate Comparison function.
*/
public void setDuplicateComparator( Comparator duplicateComparator){
this.duplicateComparator=duplicateComparator;
}
/**
* Set the btree comparison function for this database.
* @param btreeComparator -
* The btree Comparison function.
*/
public void setBtreeComparator( Comparator btreeComparator){
this.btreeComparator=btreeComparator;
}
/**
* @return the btree Comparator object.
*/
public Comparator getBtreeComparator(){
return btreeComparator;
}
/**
* @return the duplicate Comparator object.
*/
public Comparator getDuplicateComparator(){
return duplicateComparator;
}
/**
* Set the db environment during recovery, after instantiating the database
* from the log
*/
public void setEnvironmentImpl( EnvironmentImpl envImpl) throws DatabaseException {
this.envImpl=envImpl;
initDefaultSettings();
tree.setDatabase(this);
}
/**
* @return the database environment.
*/
public EnvironmentImpl getDbEnvironment(){
return envImpl;
}
/**
* Returns whether one or more handles are open.
*/
public boolean hasOpenHandles(){
return referringHandles.size() > 0;
}
/**
* Add a referring handle
*/
public void addReferringHandle( Database db){
referringHandles.add(db);
}
/**
* Decrement the reference count.
*/
public void removeReferringHandle( Database db){
referringHandles.remove(db);
}
/**
* @return the referring handle count.
*/
synchronized int getReferringHandleCount(){
return referringHandles.size();
}
/**
* For this secondary database return the primary that it is associated
* with, or null if not associated with any primary. Note that not all
* handles need be associated with a primary.
*/
public Database findPrimaryDatabase() throws DatabaseException {
for (Iterator i=referringHandles.iterator(); i.hasNext(); ) {
Object obj=i.next();
if (obj instanceof SecondaryDatabase) {
return ((SecondaryDatabase)obj).getPrimaryDatabase();
}
}
return null;
}
public String getName() throws DatabaseException {
return envImpl.getDbMapTree().getDbName(id);
}
private static class ObsoleteProcessor implements TreeNodeProcessor {
private UtilizationTracker tracker;
ObsoleteProcessor( UtilizationTracker tracker){
this.tracker=tracker;
}
public void processLSN( long childLsn, LogEntryType childType){
assert childLsn != DbLsn.NULL_LSN;
tracker.countObsoleteNodeInexact(childLsn,childType);
}
}
/**
* Return the count of nodes in the database. Used for truncate, perhaps
* should be made available through other means? Database should be
* quiescent.
*/
long countRecords() throws DatabaseException {
LNCounter lnCounter=new LNCounter();
SortedLSNTreeWalker walker=new SortedLSNTreeWalker(this,false,false,tree.getRootLsn(),lnCounter);
walker.walk();
return lnCounter.getCount();
}
private static class LNCounter implements TreeNodeProcessor {
private long counter;
public void processLSN( long childLsn, LogEntryType childType){
assert childLsn != DbLsn.NULL_LSN;
if (childType.equals(LogEntryType.LOG_LN_TRANSACTIONAL) || childType.equals(LogEntryType.LOG_LN)) {
counter++;
}
}
long getCount(){
return counter;
}
}
private boolean walkDatabaseTree( TreeWalkerStatsAccumulator statsAcc, PrintStream out, boolean verbose) throws DatabaseException {
boolean ok=true;
Locker locker=new ThreadLocker(envImpl);
Cursor cursor=null;
CursorImpl impl=null;
try {
EnvironmentImpl.incThreadLocalReferenceCount();
cursor=DbInternal.newCursor(this,locker,null);
impl=DbInternal.getCursorImpl(cursor);
tree.setTreeStatsAccumulator(statsAcc);
impl.setTreeStatsAccumulator(statsAcc);
DatabaseEntry foundData=new DatabaseEntry();
DatabaseEntry key=new DatabaseEntry();
OperationStatus status=DbInternal.position(cursor,key,foundData,LockMode.READ_UNCOMMITTED,true);
while (status == OperationStatus.SUCCESS) {
try {
status=DbInternal.retrieveNext(cursor,key,foundData,LockMode.READ_UNCOMMITTED,GetMode.NEXT);
}
catch ( DatabaseException DBE) {
ok=false;
if (DbInternal.advanceCursor(cursor,key,foundData)) {
if (verbose) {
out.println("Error encountered (continuing):");
out.println(DBE);
printErrorRecord(out,key,foundData);
}
}
else {
throw DBE;
}
}
}
}
finally {
if (impl != null) {
impl.setTreeStatsAccumulator(null);
}
tree.setTreeStatsAccumulator(null);
EnvironmentImpl.decThreadLocalReferenceCount();
if (cursor != null) {
cursor.close();
}
}
return ok;
}
/**
* Prints the key and data, if available, for a BIN entry that could not be
* read/verified. Uses the same format as DbDump and prints both the hex and
* printable versions of the entries.
*/
private void printErrorRecord( PrintStream out, DatabaseEntry key, DatabaseEntry data){
byte[] bytes=key.getData();
StringBuffer sb=new StringBuffer("Error Key ");
if (bytes == null) {
sb.append("UNKNOWN");
}
else {
CmdUtil.formatEntry(sb,bytes,false);
sb.append(' ');
CmdUtil.formatEntry(sb,bytes,true);
}
out.println(sb);
bytes=data.getData();
sb=new StringBuffer("Error Data ");
if (bytes == null) {
sb.append("UNKNOWN");
}
else {
CmdUtil.formatEntry(sb,bytes,false);
sb.append(' ');
CmdUtil.formatEntry(sb,bytes,true);
}
out.println(sb);
}
/**
* Undeclared exception used to throw through SortedLSNTreeWalker code when
* preload has either filled the user's max byte or time request.
*/
private static class HaltPreloadException extends RuntimeException {
private PreloadStatus status;
HaltPreloadException( PreloadStatus status){
super(status.toString());
this.status=status;
}
PreloadStatus getStatus(){
return status;
}
}
static final HaltPreloadException timeExceededPreloadException=new HaltPreloadException(PreloadStatus.EXCEEDED_TIME);
static final HaltPreloadException memoryExceededPreloadException=new HaltPreloadException(PreloadStatus.FILLED_CACHE);
/**
* Preload the cache, using up to maxBytes bytes or maxMillsecs msec.
*/
public PreloadStats preload( PreloadConfig config) throws DatabaseException {
return new DatabaseImpl_preload(this,config).execute();
}
public String dumpString( int nSpaces){
StringBuffer sb=new StringBuffer();
sb.append(TreeUtils.indent(nSpaces));
sb.append("<database id=\"");
sb.append(id.toString());
sb.append("\"");
if (btreeComparator != null) {
sb.append(" btc=\"");
sb.append(serializeComparator(btreeComparator));
sb.append("\"");
}
if (duplicateComparator != null) {
sb.append(" dupc=\"");
sb.append(serializeComparator(duplicateComparator));
sb.append("\"");
}
sb.append("/>");
return sb.toString();
}
/**
* @see LogWritable#getLogSize
*/
public int getLogSize(){
return id.getLogSize() + tree.getLogSize() + LogUtils.getBooleanLogSize()+ LogUtils.getStringLogSize(serializeComparator(btreeComparator))+ LogUtils.getStringLogSize(serializeComparator(duplicateComparator))+ (LogUtils.getIntLogSize() * 2);
}
/**
* @see LogWritable#writeToLog
*/
public void writeToLog( ByteBuffer logBuffer){
id.writeToLog(logBuffer);
tree.writeToLog(logBuffer);
LogUtils.writeBoolean(logBuffer,duplicatesAllowed);
LogUtils.writeString(logBuffer,serializeComparator(btreeComparator));
LogUtils.writeString(logBuffer,serializeComparator(duplicateComparator));
LogUtils.writeInt(logBuffer,maxMainTreeEntriesPerNode);
LogUtils.writeInt(logBuffer,maxDupTreeEntriesPerNode);
}
/**
* @see LogReadable#readFromLog
*/
public void readFromLog( ByteBuffer itemBuffer, byte entryTypeVersion) throws LogException {
id.readFromLog(itemBuffer,entryTypeVersion);
tree.readFromLog(itemBuffer,entryTypeVersion);
duplicatesAllowed=LogUtils.readBoolean(itemBuffer);
btreeComparatorName=LogUtils.readString(itemBuffer);
duplicateComparatorName=LogUtils.readString(itemBuffer);
try {
if (!EnvironmentImpl.getNoComparators()) {
if (btreeComparatorName.length() != 0) {
Class btreeComparatorClass=Class.forName(btreeComparatorName);
btreeComparator=instantiateComparator(btreeComparatorClass,"Btree");
}
if (duplicateComparatorName.length() != 0) {
Class duplicateComparatorClass=Class.forName(duplicateComparatorName);
duplicateComparator=instantiateComparator(duplicateComparatorClass,"Duplicate");
}
}
}
catch ( ClassNotFoundException CNFE) {
throw new LogException("couldn't instantiate class comparator",CNFE);
}
if (entryTypeVersion >= 1) {
maxMainTreeEntriesPerNode=LogUtils.readInt(itemBuffer);
maxDupTreeEntriesPerNode=LogUtils.readInt(itemBuffer);
}
}
/**
* @see LogReadable#dumpLog
*/
public void dumpLog( StringBuffer sb, boolean verbose){
sb.append("<database>");
id.dumpLog(sb,verbose);
tree.dumpLog(sb,verbose);
sb.append("<dupsort v=\"").append(duplicatesAllowed);
sb.append("\"/>");
sb.append("<btcf name=\"");
sb.append(btreeComparatorName);
sb.append("\"/>");
sb.append("<dupcf name=\"");
sb.append(duplicateComparatorName);
sb.append("\"/>");
sb.append("</database>");
}
/**
* @see LogReadable#logEntryIsTransactional
*/
public boolean logEntryIsTransactional(){
return false;
}
/**
* @see LogReadable#getTransactionId
*/
public long getTransactionId(){
return 0;
}
/**
* Used both to write to the log and to validate a comparator when set in
* DatabaseConfig.
*/
public static String serializeComparator( Comparator comparator){
if (comparator != null) {
return comparator.getClass().getName();
}
else {
return "";
}
}
/**
* Used both to read from the log and to validate a comparator when set in
* DatabaseConfig.
*/
public static Comparator instantiateComparator( Class comparator, String comparatorType) throws LogException {
if (comparator == null) {
return null;
}
try {
return (Comparator)comparator.newInstance();
}
catch ( InstantiationException IE) {
throw new LogException("Exception while trying to load " + comparatorType + " Comparator class: "+ IE);
}
catch ( IllegalAccessException IAE) {
throw new LogException("Exception while trying to load " + comparatorType + " Comparator class: "+ IAE);
}
}
public int getBinDeltaPercent(){
return binDeltaPercent;
}
public int getBinMaxDeltas(){
return binMaxDeltas;
}
@MethodObject static class DatabaseImpl_preload {
DatabaseImpl_preload( DatabaseImpl _this, PreloadConfig config){
this._this=_this;
this.config=config;
}
PreloadStats execute() throws DatabaseException {
maxBytes=config.getMaxBytes();
maxMillisecs=config.getMaxMillisecs();
targetTime=Long.MAX_VALUE;
if (maxMillisecs > 0) {
targetTime=System.currentTimeMillis() + maxMillisecs;
}
this.hook290();
ret=new PreloadStats();
callback=new PreloadProcessor(_this.envImpl,maxBytes,targetTime,ret);
walker=new PreloadLSNTreeWalker(_this,callback,config);
this.hook287();
return ret;
}
protected DatabaseImpl _this;
protected PreloadConfig config;
protected long maxBytes;
protected long maxMillisecs;
protected long targetTime;
protected long cacheBudget;
protected PreloadStats ret;
protected PreloadProcessor callback;
protected SortedLSNTreeWalker walker;
protected void hook287() throws DatabaseException {
walker.walk();
}
protected void hook290() throws DatabaseException {
}
}
protected void hook288() throws DatabaseException {
}
protected void hook289() throws DatabaseException {
}
}