package com.sleepycat.je.tree; import java.util.Comparator; import java.util.Iterator; import java.util.Set; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.cleaner.Cleaner; import com.sleepycat.je.dbi.CursorImpl; import com.sleepycat.je.dbi.DatabaseImpl; import com.sleepycat.je.dbi.DbConfigManager; import com.sleepycat.je.dbi.MemoryBudget; import com.sleepycat.je.log.LogEntryType; import com.sleepycat.je.log.LogManager; import com.sleepycat.je.log.LoggableObject; import com.sleepycat.je.txn.BasicLocker; import com.sleepycat.je.txn.LockGrantType; import com.sleepycat.je.txn.LockResult; import com.sleepycat.je.txn.LockType; import com.sleepycat.je.utilint.DbLsn; import com.sleepycat.je.utilint.TinyHashSet; import de.ovgu.cide.jakutil.*; /** * A BIN represents a Bottom Internal Node in the JE tree. */ public class BIN extends IN implements LoggableObject { private static final String BEGIN_TAG="<bin>"; private static final String END_TAG="</bin>"; private TinyHashSet cursorSet; private long lastDeltaVersion=DbLsn.NULL_LSN; private int numDeltasSinceLastFull; private boolean prohibitNextDelta; public BIN(){ cursorSet=new TinyHashSet(); numDeltasSinceLastFull=0; prohibitNextDelta=false; } public BIN( DatabaseImpl db, byte[] identifierKey, int maxEntriesPerNode, int level){ super(db,identifierKey,maxEntriesPerNode,level); cursorSet=new TinyHashSet(); numDeltasSinceLastFull=0; prohibitNextDelta=false; } /** * Create a holder object that encapsulates information about this BIN for * the INCompressor. */ public BINReference createReference(){ return new BINReference(getNodeId(),getDatabase().getId(),getIdentifierKey()); } /** * Create a new BIN. Need this because we can't call newInstance() without * getting a 0 for nodeid. */ protected IN createNewInstance( byte[] identifierKey, int maxEntries, int level){ return new BIN(getDatabase(),identifierKey,maxEntries,level); } /** * Get the key (dupe or identifier) in child that is used to locate it in * 'this' node. For BIN's, the child node has to be a DIN so we use the Dup * Key to cross the main-tree/dupe-tree boundary. */ public byte[] getChildKey( IN child) throws DatabaseException { return child.getDupKey(); } /** * @return the log entry type to use for bin delta log entries. */ LogEntryType getBINDeltaType(){ return LogEntryType.LOG_BIN_DELTA; } /** * @return location of last logged delta version. If never set, return null. */ public long getLastDeltaVersion(){ return lastDeltaVersion; } /** * If cleaned or compressed, must log full version. * @Override */ public void setProhibitNextDelta(){ prohibitNextDelta=true; } protected void descendOnParentSearch( SearchResult result, boolean targetContainsDuplicates, boolean targetIsRoot, long targetNodeId, Node child, boolean requireExactMatch) throws DatabaseException { if (child.canBeAncestor(targetContainsDuplicates)) { if (targetContainsDuplicates && targetIsRoot) { long childNid=child.getNodeId(); this.hook603(child); result.keepSearching=false; if (childNid == targetNodeId) { result.exactParentFound=true; } else { result.exactParentFound=false; } if (requireExactMatch && !result.exactParentFound) { result.parent=null; this.hook604(); } else { result.parent=this; } } else { this.hook605(); result.parent=(IN)child; } } else { result.exactParentFound=false; result.keepSearching=false; if (!requireExactMatch && targetContainsDuplicates) { result.parent=this; } else { this.hook606(); result.parent=null; } } } protected boolean canBeAncestor( boolean targetContainsDuplicates){ return targetContainsDuplicates; } /** * @Override */ boolean isEvictionProhibited(){ return (nCursors() > 0); } /** * @Override */ boolean hasNonLNChildren(){ for (int i=0; i < getNEntries(); i++) { Node node=getTarget(i); if (node != null) { if (!(node instanceof LN)) { return true; } } } return false; } /** * Indicates whether entry 0's key is "special" in that it always compares * less than any other key. BIN's don't have the special key, but IN's do. */ boolean entryZeroKeyComparesLow(){ return false; } /** * Mark this entry as deleted, using the delete flag. Only BINS may do this. * @param indexindicates target entry */ public void setKnownDeleted( int index){ super.setKnownDeleted(index); this.hook610(index); setMigrate(index,false); super.setTarget(index,null); setDirty(true); } /** * Mark this entry as deleted, using the delete flag. Only BINS may do this. * Don't null the target field. * This is used so that an LN can still be locked by the compressor even if * the entry is knownDeleted. See BIN.compress. * @param indexindicates target entry */ public void setKnownDeletedLeaveTarget( int index){ setMigrate(index,false); super.setKnownDeleted(index); setDirty(true); } /** * Clear the known deleted flag. Only BINS may do this. * @param indexindicates target entry */ public void clearKnownDeleted( int index){ super.clearKnownDeleted(index); setDirty(true); } public Set getCursorSet(){ return cursorSet.copy(); } /** * Register a cursor with this bin. Caller has this bin already latched. * @param cursorCursor to register. */ public void addCursor( CursorImpl cursor){ cursorSet.add(cursor); } /** * Unregister a cursor with this bin. Caller has this bin already latched. * @param cursorCursor to unregister. */ public void removeCursor( CursorImpl cursor){ cursorSet.remove(cursor); } /** * @return the number of cursors currently referring to this BIN. */ public int nCursors(){ return cursorSet.size(); } /** * The following four methods access the correct fields in a cursor * depending on whether "this" is a BIN or DBIN. For BIN's, the * CursorImpl.index and CursorImpl.bin fields should be used. For DBIN's, * the CursorImpl.dupIndex and CursorImpl.dupBin fields should be used. */ BIN getCursorBIN( CursorImpl cursor){ return cursor.getBIN(); } BIN getCursorBINToBeRemoved( CursorImpl cursor){ return cursor.getBINToBeRemoved(); } int getCursorIndex( CursorImpl cursor){ return cursor.getIndex(); } void setCursorBIN( CursorImpl cursor, BIN bin){ cursor.setBIN(bin); } void setCursorIndex( CursorImpl cursor, int index){ cursor.setIndex(index); } /** * Called when we know we are about to split on behalf of a key that is the * minimum (leftSide) or maximum (!leftSide) of this node. This is achieved * by just forcing the split to occur either one element in from the left or * the right (i.e. splitIndex is 1 or nEntries - 1). */ void splitSpecial( IN parent, int parentIndex, int maxEntriesPerNode, byte[] key, boolean leftSide) throws DatabaseException { int index=findEntry(key,true,false); int nEntries=getNEntries(); boolean exact=(index & IN.EXACT_MATCH) != 0; index&=~IN.EXACT_MATCH; if (leftSide && index < 0) { splitInternal(parent,parentIndex,maxEntriesPerNode,1); } else if (!leftSide && !exact && index == (nEntries - 1)) { splitInternal(parent,parentIndex,maxEntriesPerNode,nEntries - 1); } else { split(parent,parentIndex,maxEntriesPerNode); } } /** * Adjust any cursors that are referring to this BIN. This method is called * during a split operation. "this" is the BIN being split. newSibling is * the new BIN into which the entries from "this" between newSiblingLow and * newSiblingHigh have been copied. * @param newSibling - * the newSibling into which "this" has been split. * @param newSiblingLow, * newSiblingHigh - the low and high entry of "this" that were * moved into newSibling. */ void adjustCursors( IN newSibling, int newSiblingLow, int newSiblingHigh){ int adjustmentDelta=(newSiblingHigh - newSiblingLow); Iterator iter=cursorSet.iterator(); while (iter.hasNext()) { CursorImpl cursor=(CursorImpl)iter.next(); if (getCursorBINToBeRemoved(cursor) == this) { continue; } int cIdx=getCursorIndex(cursor); BIN cBin=getCursorBIN(cursor); assert cBin == this : "nodeId=" + getNodeId() + " cursor="+ cursor.dumpToString(true); assert newSibling instanceof BIN; BIN ns=(BIN)newSibling; if (newSiblingLow == 0) { if (cIdx < newSiblingHigh) { setCursorBIN(cursor,ns); iter.remove(); ns.addCursor(cursor); } else { setCursorIndex(cursor,cIdx - adjustmentDelta); } } else { if (cIdx >= newSiblingLow) { setCursorIndex(cursor,cIdx - newSiblingLow); setCursorBIN(cursor,ns); iter.remove(); ns.addCursor(cursor); } } } } /** * Adjust cursors referring to this BIN following an insert. * @param insertIndex - * The index of the new entry. */ void adjustCursorsForInsert( int insertIndex){ if (cursorSet != null) { Iterator iter=cursorSet.iterator(); while (iter.hasNext()) { CursorImpl cursor=(CursorImpl)iter.next(); if (getCursorBINToBeRemoved(cursor) != this) { int cIdx=getCursorIndex(cursor); if (insertIndex <= cIdx) { setCursorIndex(cursor,cIdx + 1); } } } } } /** * Adjust cursors referring to the given binIndex in this BIN following a * mutation of the entry from an LN to a DIN. The entry was moved from a BIN * to a newly created DBIN so each cursor must be added to the new DBIN. * @param binIndex - * The index of the DIN (previously LN) entry in the BIN. * @param dupBin - * The DBIN into which the LN entry was moved. * @param dupBinIndex - * The index of the moved LN entry in the DBIN. * @param excludeCursor - * The cursor being used for insertion and that should not be * updated. */ void adjustCursorsForMutation( int binIndex, DBIN dupBin, int dupBinIndex, CursorImpl excludeCursor){ if (cursorSet != null) { Iterator iter=cursorSet.iterator(); while (iter.hasNext()) { CursorImpl cursor=(CursorImpl)iter.next(); if (getCursorBINToBeRemoved(cursor) != this && cursor != excludeCursor && cursor.getIndex() == binIndex) { assert cursor.getDupBIN() == null; cursor.addCursor(dupBin); cursor.updateDBin(dupBin,dupBinIndex); } } } } /** * Compress this BIN by removing any entries that are deleted. Deleted * entries are those that have LN's marked deleted or if the knownDeleted * flag is set. Caller is responsible for latching and unlatching this node. * @param binRefis used to determine the set of keys to be checked for * deletedness, or is null to check all keys. * @param canFetchif false, don't fetch any non-resident children. We don't want * some callers of compress, such as the evictor, to fault in * other nodes. * @return true if we had to requeue the entry because we were unable to get * locks, false if all entries were processed and therefore any * remaining deleted keys in the BINReference must now be in some * other BIN because of a split. */ public boolean compress( BINReference binRef, boolean canFetch) throws DatabaseException { boolean ret=false; boolean setNewIdKey=false; boolean anyLocksDenied=false; DatabaseImpl db=getDatabase(); BasicLocker lockingTxn=new BasicLocker(db.getDbEnvironment()); try { for (int i=0; i < getNEntries(); i++) { boolean deleteEntry=false; if (binRef == null || isEntryPendingDeleted(i) || isEntryKnownDeleted(i) || binRef.hasDeletedKey(new Key(getKey(i)))) { Node n=null; if (canFetch) { n=fetchTarget(i); } else { n=getTarget(i); if (n == null) { continue; } } if (n == null) { deleteEntry=true; } else if (isEntryKnownDeleted(i)) { LockResult lockRet=lockingTxn.nonBlockingLock(n.getNodeId(),LockType.READ,db); if (lockRet.getLockGrant() == LockGrantType.DENIED) { anyLocksDenied=true; continue; } deleteEntry=true; } else { if (!n.containsDuplicates()) { LN ln=(LN)n; LockResult lockRet=lockingTxn.nonBlockingLock(ln.getNodeId(),LockType.READ,db); if (lockRet.getLockGrant() == LockGrantType.DENIED) { anyLocksDenied=true; continue; } if (ln.isDeleted()) { deleteEntry=true; } } } if (binRef != null) { binRef.removeDeletedKey(new Key(getKey(i))); } } if (deleteEntry) { boolean entryIsIdentifierKey=Key.compareKeys(getKey(i),getIdentifierKey(),getKeyComparator()) == 0; if (entryIsIdentifierKey) { setNewIdKey=true; } boolean deleteSuccess=deleteEntry(i,true); assert deleteSuccess; i--; } } } finally { if (lockingTxn != null) { lockingTxn.operationEnd(); } } if (anyLocksDenied && binRef != null) { this.hook609(binRef,db); ret=true; } if (getNEntries() != 0 && setNewIdKey) { setIdentifierKey(getKey(0)); } if (getNEntries() == 0) { setGeneration(0); } return ret; } public boolean isCompressible(){ return true; } boolean validateSubtreeBeforeDelete( int index) throws DatabaseException { return true; } /** * Check if this node fits the qualifications for being part of a deletable * subtree. It can only have one IN child and no LN children. */ boolean isValidForDelete() throws DatabaseException { try { int validIndex=0; int numValidEntries=0; boolean needToLatch=false; this.hook607(validIndex,numValidEntries,needToLatch); throw ReturnHack.returnBoolean; } catch ( ReturnBoolean r) { return r.value; } } void accumulateStats( TreeWalkerStatsAccumulator acc){ acc.processBIN(this,new Long(getNodeId()),getLevel()); } /** * Return the relevant user defined comparison function for this type of * node. For IN's and BIN's, this is the BTree Comparison function. * Overriden by DBIN. */ public Comparator getKeyComparator(){ return getDatabase().getBtreeComparator(); } public String beginTag(){ return BEGIN_TAG; } public String endTag(){ return END_TAG; } /** * @see LoggableObject#getLogType */ public LogEntryType getLogType(){ return LogEntryType.LOG_BIN; } public String shortClassName(){ return "BIN"; } /** * Decide how to log this node. BINs may be logged provisionally. If logging * a delta, return an null for the LSN. */ protected long logInternal( LogManager logManager, boolean allowDeltas, boolean isProvisional, boolean proactiveMigration, IN parent) throws DatabaseException { boolean doDeltaLog=false; long lastFullVersion=getLastFullVersion(); Cleaner cleaner=getDatabase().getDbEnvironment().getCleaner(); cleaner.lazyMigrateLNs(this,proactiveMigration); BINDelta deltaInfo=null; if ((allowDeltas) && (lastFullVersion != DbLsn.NULL_LSN) && !prohibitNextDelta) { deltaInfo=new BINDelta(this); doDeltaLog=doDeltaLog(deltaInfo); } long returnLsn=DbLsn.NULL_LSN; if (doDeltaLog) { lastDeltaVersion=logManager.log(deltaInfo); returnLsn=DbLsn.NULL_LSN; numDeltasSinceLastFull++; } else { returnLsn=super.logInternal(logManager,allowDeltas,isProvisional,proactiveMigration,parent); lastDeltaVersion=DbLsn.NULL_LSN; numDeltasSinceLastFull=0; } prohibitNextDelta=false; return returnLsn; } /** * Decide whether to log a full or partial BIN, depending on the ratio of * the delta size to full BIN size, and the number of deltas that have been * logged since the last full. * @return true if we should log the deltas of this BIN */ private boolean doDeltaLog( BINDelta deltaInfo) throws DatabaseException { int maxDiffs=(getNEntries() * getDatabase().getBinDeltaPercent()) / 100; if ((deltaInfo.getNumDeltas() <= maxDiffs) && (numDeltasSinceLastFull < getDatabase().getBinMaxDeltas())) { return true; } else { return false; } } protected void hook603( Node child) throws DatabaseException { } protected void hook604() throws DatabaseException { } protected void hook605() throws DatabaseException { } protected void hook606() throws DatabaseException { } protected void hook607( int validIndex, int numValidEntries, boolean needToLatch) throws DatabaseException { this.hook608(needToLatch); for (int i=0; i < getNEntries(); i++) { if (!isEntryKnownDeleted(i)) { numValidEntries++; validIndex=i; } } if (numValidEntries > 1) { throw new ReturnBoolean(false); } else { if (nCursors() > 0) { throw new ReturnBoolean(false); } if (numValidEntries == 1) { Node child=fetchTarget(validIndex); throw new ReturnBoolean(child != null && child.isValidForDelete()); } else { throw new ReturnBoolean(true); } } } protected void hook608( boolean needToLatch) throws DatabaseException { } protected void hook609( BINReference binRef, DatabaseImpl db) throws DatabaseException { } protected void hook610( int index){ } }