package com.sleepycat.je.dbi;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.RunRecoveryException;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.BINBoundary;
import com.sleepycat.je.tree.DBIN;
import com.sleepycat.je.tree.DIN;
import com.sleepycat.je.tree.DupCountLN;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.TreeWalkerStatsAccumulator;
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.txn.Locker;
import com.sleepycat.je.txn.ThreadLocker;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;
import de.ovgu.cide.jakutil.*;
/**
* A CursorImpl is the internal implementation of the cursor.
*/
public class CursorImpl implements Cloneable {
private static final byte CURSOR_NOT_INITIALIZED=1;
private static final byte CURSOR_INITIALIZED=2;
private static final byte CURSOR_CLOSED=3;
private static final String TRACE_DELETE="Delete";
private static final String TRACE_MOD="Mod:";
volatile private BIN bin;
volatile private int index;
volatile private DBIN dupBin;
volatile private int dupIndex;
volatile private BIN binToBeRemoved;
volatile private DBIN dupBinToBeRemoved;
private BIN targetBin;
private int targetIndex;
private byte[] dupKey;
private DatabaseImpl database;
private Locker locker;
private CursorImpl lockerPrev;
private CursorImpl lockerNext;
private boolean retainNonTxnLocks;
private byte status;
private TestHook testHook;
private boolean nonCloning=false;
private int thisId;
private static long lastAllocatedId=0;
private ThreadLocal treeStatsAccumulatorTL=new ThreadLocal();
private static long getNextCursorId(){
return ++lastAllocatedId;
}
public int hashCode(){
return thisId;
}
private TreeWalkerStatsAccumulator getTreeStatsAccumulator(){
if (EnvironmentImpl.getThreadLocalReferenceCount() > 0) {
return (TreeWalkerStatsAccumulator)treeStatsAccumulatorTL.get();
}
else {
return null;
}
}
public void incrementLNCount(){
TreeWalkerStatsAccumulator treeStatsAccumulator=getTreeStatsAccumulator();
if (treeStatsAccumulator != null) {
treeStatsAccumulator.incrementLNCount();
}
}
public void setNonCloning( boolean nonCloning){
this.nonCloning=nonCloning;
}
/**
* public for Cursor et al
*/
public static class SearchMode {
public static final SearchMode SET=new SearchMode(true,false,"SET");
public static final SearchMode BOTH=new SearchMode(true,true,"BOTH");
public static final SearchMode SET_RANGE=new SearchMode(false,false,"SET_RANGE");
public static final SearchMode BOTH_RANGE=new SearchMode(false,true,"BOTH_RANGE");
private boolean exactSearch;
private boolean dataSearch;
private String name;
private SearchMode( boolean exactSearch, boolean dataSearch, String name){
this.exactSearch=exactSearch;
this.dataSearch=dataSearch;
this.name="SearchMode." + name;
}
/**
* Returns true when the key or key/data search is exact, i.e., for SET
* and BOTH.
*/
public final boolean isExactSearch(){
return exactSearch;
}
/**
* Returns true when the data value is included in the search, i.e., for
* BOTH and BOTH_RANGE.
*/
public final boolean isDataSearch(){
return dataSearch;
}
public String toString(){
return name;
}
}
/**
* Holder for an OperationStatus and a keyChange flag. Is used for search
* and getNextWithKeyChangeStatus operations.
*/
public static class KeyChangeStatus {
/**
* Operation status;
*/
public OperationStatus status;
/**
* Whether the operation moved to a new key.
*/
public boolean keyChange;
public KeyChangeStatus( OperationStatus status, boolean keyChange){
this.status=status;
this.keyChange=keyChange;
}
}
/**
* Creates a cursor with retainNonTxnLocks=true.
*/
public CursorImpl( DatabaseImpl database, Locker locker) throws DatabaseException {
this(database,locker,true);
}
/**
* Creates a cursor.
* @param retainNonTxnLocksis true if non-transactional locks should be retained (not
* released automatically) when the cursor is closed.
*/
public CursorImpl( DatabaseImpl database, Locker locker, boolean retainNonTxnLocks) throws DatabaseException {
thisId=(int)getNextCursorId();
bin=null;
index=-1;
dupBin=null;
dupIndex=-1;
assert !(retainNonTxnLocks && (locker instanceof ThreadLocker));
assert !(!retainNonTxnLocks && locker.getClass() == BasicLocker.class);
this.retainNonTxnLocks=retainNonTxnLocks;
this.database=database;
this.locker=locker;
this.locker.registerCursor(this);
status=CURSOR_NOT_INITIALIZED;
}
/**
* Shallow copy. addCursor() is optionally called.
*/
public CursorImpl cloneCursor( boolean addCursor) throws DatabaseException {
return cloneCursor(addCursor,null);
}
/**
* Shallow copy. addCursor() is optionally called. Allows inheriting the BIN
* position from some other cursor.
*/
public CursorImpl cloneCursor( boolean addCursor, CursorImpl usePosition) throws DatabaseException {
CursorImpl ret=null;
if (nonCloning) {
ret=this;
}
else {
try {
this.hook206();
ret=(CursorImpl)super.clone();
if (!retainNonTxnLocks) {
ret.locker=locker.newNonTxnLocker();
}
ret.locker.registerCursor(ret);
if (usePosition != null && usePosition.status == CURSOR_INITIALIZED) {
ret.bin=usePosition.bin;
ret.index=usePosition.index;
ret.dupBin=usePosition.dupBin;
ret.dupIndex=usePosition.dupIndex;
}
if (addCursor) {
ret.addCursor();
}
}
catch ( CloneNotSupportedException cannotOccur) {
return null;
}
finally {
this.hook207();
}
}
return ret;
}
public int getIndex(){
return index;
}
public void setIndex( int idx){
index=idx;
}
public BIN getBIN(){
return bin;
}
public void setBIN( BIN newBin){
bin=newBin;
}
public BIN getBINToBeRemoved(){
return binToBeRemoved;
}
public int getDupIndex(){
return dupIndex;
}
public void setDupIndex( int dupIdx){
dupIndex=dupIdx;
}
public DBIN getDupBIN(){
return dupBin;
}
public void setDupBIN( DBIN newDupBin){
dupBin=newDupBin;
}
public DBIN getDupBINToBeRemoved(){
return dupBinToBeRemoved;
}
public void setTreeStatsAccumulator( TreeWalkerStatsAccumulator tSA){
treeStatsAccumulatorTL.set(tSA);
}
/**
* Figure out which BIN/index set to use.
*/
private boolean setTargetBin(){
targetBin=null;
targetIndex=0;
boolean isDup=(dupBin != null);
dupKey=null;
if (isDup) {
targetBin=dupBin;
targetIndex=dupIndex;
dupKey=dupBin.getDupKey();
}
else {
targetBin=bin;
targetIndex=index;
}
return isDup;
}
/**
* Advance a cursor. Used so that verify can advance a cursor even in the
* face of an exception [12932].
* @param keyon return contains the key if available, or null.
* @param dataon return contains the data if available, or null.
*/
public boolean advanceCursor( DatabaseEntry key, DatabaseEntry data){
BIN oldBin=bin;
BIN oldDupBin=dupBin;
int oldIndex=index;
int oldDupIndex=dupIndex;
key.setData(null);
data.setData(null);
try {
getNext(key,data,LockType.NONE,true,false);
}
catch ( DatabaseException ignored) {
}
if (bin != oldBin || dupBin != oldDupBin || index != oldIndex || dupIndex != oldDupIndex) {
if (key.getData() == null && bin != null && index > 0) {
setDbt(key,bin.getKey(index));
}
if (data.getData() == null && dupBin != null && dupIndex > 0) {
setDbt(data,dupBin.getKey(dupIndex));
}
return true;
}
else {
return false;
}
}
public BIN latchBIN() throws DatabaseException {
return new CursorImpl_latchBIN(this).execute();
}
public DBIN latchDBIN() throws DatabaseException {
return new CursorImpl_latchDBIN(this).execute();
}
public Locker getLocker(){
return locker;
}
public void addCursor( BIN bin){
if (bin != null) {
this.hook208(bin);
bin.addCursor(this);
}
}
/**
* Add to the current cursor. (For dups)
*/
public void addCursor(){
if (dupBin != null) {
addCursor(dupBin);
}
if (bin != null) {
addCursor(bin);
}
}
public void updateBin( BIN bin, int index) throws DatabaseException {
removeCursorDBIN();
setDupIndex(-1);
setDupBIN(null);
setIndex(index);
setBIN(bin);
addCursor(bin);
}
public void updateDBin( DBIN dupBin, int dupIndex){
setDupIndex(dupIndex);
setDupBIN(dupBin);
addCursor(dupBin);
}
private void removeCursor() throws DatabaseException {
removeCursorBIN();
removeCursorDBIN();
}
private void removeCursorBIN() throws DatabaseException {
BIN abin=latchBIN();
if (abin != null) {
abin.removeCursor(this);
this.hook209(abin);
}
}
private void removeCursorDBIN() throws DatabaseException {
DBIN abin=latchDBIN();
if (abin != null) {
abin.removeCursor(this);
this.hook210(abin);
}
}
/**
* Clear the reference to the dup tree, if any.
*/
public void clearDupBIN( boolean alreadyLatched) throws DatabaseException {
if (dupBin != null) {
if (alreadyLatched) {
dupBin.removeCursor(this);
this.hook211();
}
else {
removeCursorDBIN();
}
dupBin=null;
dupIndex=-1;
}
}
public void dumpTree() throws DatabaseException {
database.getTree().dump();
}
/**
* @return true if this cursor is closed
*/
public boolean isClosed(){
return (status == CURSOR_CLOSED);
}
/**
* @return true if this cursor is not initialized
*/
public boolean isNotInitialized(){
return (status == CURSOR_NOT_INITIALIZED);
}
/**
* Reset a cursor to an uninitialized state, but unlike close(), allow it to
* be used further.
*/
public void reset() throws DatabaseException {
removeCursor();
if (!retainNonTxnLocks) {
locker.releaseNonTxnLocks();
}
bin=null;
index=-1;
dupBin=null;
dupIndex=-1;
status=CURSOR_NOT_INITIALIZED;
}
/**
* Close a cursor.
* @throws DatabaseExceptionif the cursor was previously closed.
*/
public void close() throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
removeCursor();
locker.unRegisterCursor(this);
if (!retainNonTxnLocks) {
locker.releaseNonTxnLocks();
}
status=CURSOR_CLOSED;
}
public int count( LockType lockType) throws DatabaseException {
try {
assert assertCursorState(true) : dumpToString(true);
if (!database.getSortedDuplicates()) {
return 1;
}
if (bin == null) {
return 0;
}
this.hook212(lockType);
throw ReturnHack.returnInt;
}
catch ( ReturnInt r) {
return r.value;
}
}
/**
* Delete the item pointed to by the cursor. If cursor is not initialized or
* item is already deleted, return appropriate codes. Returns with nothing
* latched. bin and dupBin are latched as appropriate.
* @return 0 on success, appropriate error code otherwise.
*/
public OperationStatus delete() throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
boolean isDup=setTargetBin();
if (targetBin == null) {
return OperationStatus.KEYEMPTY;
}
if (targetBin.isEntryKnownDeleted(targetIndex)) {
this.hook214();
return OperationStatus.KEYEMPTY;
}
LN ln=(LN)targetBin.fetchTarget(targetIndex);
if (ln == null) {
this.hook215();
return OperationStatus.KEYEMPTY;
}
LockResult lockResult=lockLN(ln,LockType.WRITE);
ln=lockResult.getLN();
if (ln == null) {
this.hook216();
return OperationStatus.KEYEMPTY;
}
LockResult dclLockResult=null;
DIN dupRoot=null;
this.hook213(isDup,ln,lockResult,dclLockResult,dupRoot);
return OperationStatus.SUCCESS;
}
/**
* Return a new copy of the cursor. If position is true, position the
* returned cursor at the same position.
*/
public CursorImpl dup( boolean samePosition) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
CursorImpl ret=cloneCursor(samePosition);
if (!samePosition) {
ret.bin=null;
ret.index=-1;
ret.dupBin=null;
ret.dupIndex=-1;
ret.status=CURSOR_NOT_INITIALIZED;
}
return ret;
}
/**
* Search for the next key (or duplicate) following the given key (and
* datum), and acquire a range insert lock on it. If there are no more
* records following the given key and datum, lock the special EOF node for
* the database.
*/
public void lockNextKeyForInsert( DatabaseEntry key, DatabaseEntry data) throws DatabaseException {
new CursorImpl_lockNextKeyForInsert(this,key,data).execute();
}
/**
* Insert the given LN in the tree or return KEYEXIST if the key is already
* present.
* <p>
* This method is called directly internally for putting tree map LNs and
* file summary LNs. It should not be used otherwise, and in the future we
* should find a way to remove this special case.
* </p>
*/
public OperationStatus putLN( byte[] key, LN ln, boolean allowDuplicates) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
this.hook217();
LockResult lockResult=locker.lock(ln.getNodeId(),LockType.WRITE,false,database);
if (database.getTree().insert(ln,key,allowDuplicates,this,lockResult)) {
status=CURSOR_INITIALIZED;
return OperationStatus.SUCCESS;
}
else {
locker.releaseLock(ln.getNodeId());
return OperationStatus.KEYEXIST;
}
}
/**
* Insert or overwrite the key/data pair.
* @param key
* @param data
* @return 0 if successful, failure status value otherwise
*/
public OperationStatus put( DatabaseEntry key, DatabaseEntry data, DatabaseEntry foundData) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
OperationStatus result=putLN(Key.makeKey(key),new LN(data),database.getSortedDuplicates());
if (result == OperationStatus.KEYEXIST) {
status=CURSOR_INITIALIZED;
result=putCurrent(data,null,foundData);
}
return result;
}
/**
* Insert the key/data pair in No Overwrite mode.
* @param key
* @param data
* @return 0 if successful, failure status value otherwise
*/
public OperationStatus putNoOverwrite( DatabaseEntry key, DatabaseEntry data) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
return putLN(Key.makeKey(key),new LN(data),false);
}
/**
* Insert the key/data pair as long as no entry for key/data exists yet.
*/
public OperationStatus putNoDupData( DatabaseEntry key, DatabaseEntry data) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
if (!database.getSortedDuplicates()) {
throw new DatabaseException("putNoDupData() called, but database is not configured " + "for duplicate data.");
}
return putLN(Key.makeKey(key),new LN(data),true);
}
/**
* Modify the current record with this data.
* @param data
*/
public OperationStatus putCurrent( DatabaseEntry data, DatabaseEntry foundKey, DatabaseEntry foundData) throws DatabaseException {
try {
assert assertCursorState(true) : dumpToString(true);
if (foundKey != null) {
foundKey.setData(null);
}
if (foundData != null) {
foundData.setData(null);
}
if (bin == null) {
return OperationStatus.KEYEMPTY;
}
this.hook219();
boolean isDup=setTargetBin();
this.hook218(data,foundKey,foundData,isDup);
throw ReturnHack.returnObject;
}
catch ( ReturnObject r) {
return (OperationStatus)r.value;
}
}
/**
* Retrieve the current record.
*/
public OperationStatus getCurrent( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
if (bin == null) {
return OperationStatus.KEYEMPTY;
}
this.hook220();
return getCurrentAlreadyLatched(foundKey,foundData,lockType,true);
}
/**
* Retrieve the current record. Assume the bin is already latched. Return
* with the target bin unlatched.
*/
public OperationStatus getCurrentAlreadyLatched( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean first) throws DatabaseException {
try {
assert assertCursorState(true) : dumpToString(true);
this.hook221(foundKey,foundData,lockType,first);
throw ReturnHack.returnObject;
}
catch ( ReturnObject r) {
return (OperationStatus)r.value;
}
}
/**
* Retrieve the current LN, return with the target bin unlatched.
*/
public LN getCurrentLN( LockType lockType) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
if (bin == null) {
return null;
}
else {
this.hook222();
return getCurrentLNAlreadyLatched(lockType);
}
}
/**
* Retrieve the current LN, assuming the BIN is already latched. Return with
* the target BIN unlatched.
*/
public LN getCurrentLNAlreadyLatched( LockType lockType) throws DatabaseException {
try {
this.hook223(lockType);
throw ReturnHack.returnObject;
}
catch ( ReturnObject r) {
return (LN)r.value;
}
}
public OperationStatus getNext( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean forward, boolean alreadyLatched) throws DatabaseException {
return getNextWithKeyChangeStatus(foundKey,foundData,lockType,forward,alreadyLatched).status;
}
/**
* Move the cursor forward and return the next record. This will cross BIN
* boundaries and dip into duplicate sets.
* @param foundKeyDatabaseEntry to use for returning key
* @param foundDataDatabaseEntry to use for returning data
* @param forwardif true, move forward, else move backwards
* @param alreadyLatchedif true, the bin that we're on is already latched.
* @return the status and an indication of whether we advanced to a new key
* during the operation.
*/
public KeyChangeStatus getNextWithKeyChangeStatus( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean forward, boolean alreadyLatched) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
this.hook224(alreadyLatched);
KeyChangeStatus result=new KeyChangeStatus(OperationStatus.NOTFOUND,true);
try {
while (bin != null) {
if (dupBin != null) {
this.hook277();
if (getNextDuplicate(foundKey,foundData,lockType,forward,alreadyLatched) == OperationStatus.SUCCESS) {
result.status=OperationStatus.SUCCESS;
result.keyChange=false;
break;
}
else {
removeCursorDBIN();
alreadyLatched=this.hook226(alreadyLatched);
dupBin=null;
dupIndex=-1;
continue;
}
}
alreadyLatched=this.hook225(alreadyLatched);
this.hook276();
if ((forward && ++index < bin.getNEntries()) || (!forward && --index > -1)) {
OperationStatus ret=getCurrentAlreadyLatched(foundKey,foundData,lockType,forward);
if (ret == OperationStatus.SUCCESS) {
incrementLNCount();
result.status=OperationStatus.SUCCESS;
break;
}
else {
this.hook227();
if (binToBeRemoved != null) {
flushBINToBeRemoved();
}
continue;
}
}
else {
if (binToBeRemoved != null) {
this.hook229();
flushBINToBeRemoved();
this.hook228();
}
binToBeRemoved=bin;
bin=null;
BIN newBin;
assert TestHookExecute.doHookIfSet(testHook);
if (forward) {
newBin=database.getTree().getNextBin(binToBeRemoved,false);
}
else {
newBin=database.getTree().getPrevBin(binToBeRemoved,false);
}
if (newBin == null) {
result.status=OperationStatus.NOTFOUND;
break;
}
else {
if (forward) {
index=-1;
}
else {
index=newBin.getNEntries();
}
addCursor(newBin);
bin=newBin;
this.hook230(alreadyLatched);
}
}
}
}
finally {
this.hook231();
if (binToBeRemoved != null) {
flushBINToBeRemoved();
}
}
return result;
}
private void flushBINToBeRemoved() throws DatabaseException {
binToBeRemoved.removeCursor(this);
this.hook232();
binToBeRemoved=null;
}
public OperationStatus getNextNoDup( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean forward, boolean alreadyLatched) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
if (dupBin != null) {
clearDupBIN(alreadyLatched);
alreadyLatched=false;
}
return getNext(foundKey,foundData,lockType,forward,alreadyLatched);
}
/**
* Retrieve the first duplicate at the current cursor position.
*/
public OperationStatus getFirstDuplicate( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
if (dupBin != null) {
removeCursorDBIN();
dupBin=null;
dupIndex=-1;
}
return getCurrent(foundKey,foundData,lockType);
}
/**
* Enter with dupBin unlatched. Pass foundKey == null to just advance cursor
* to next duplicate without fetching data.
*/
public OperationStatus getNextDuplicate( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean forward, boolean alreadyLatched) throws DatabaseException {
return new CursorImpl_getNextDuplicate(this,foundKey,foundData,lockType,forward,alreadyLatched).execute();
}
private void flushDBINToBeRemoved() throws DatabaseException {
dupBinToBeRemoved.removeCursor(this);
this.hook233();
dupBinToBeRemoved=null;
}
/**
* Position the cursor at the first or last record of the database. It's
* okay if this record is deleted. Returns with the target BIN latched.
* @return true if a first or last position is found, false if the tree
* being searched is empty.
*/
public boolean positionFirstOrLast( boolean first, DIN duplicateRoot) throws DatabaseException {
try {
assert assertCursorState(false) : dumpToString(true);
IN in=null;
boolean found=false;
this.hook234(first,duplicateRoot,in,found);
throw ReturnHack.returnBoolean;
}
catch ( ReturnBoolean r) {
return r.value;
}
}
public static final int FOUND=0x1;
public static final int EXACT_KEY=0x2;
public static final int EXACT_DATA=0x4;
public static final int FOUND_LAST=0x8;
/**
* Position the cursor at the key. This returns a three part value that's
* bitwise or'ed into the int. We find out if there was any kind of match
* and if the match was exact. Note that this match focuses on whether the
* searching criteria (key, or key and data, depending on the search type)
* is met.
* <p>
* Note this returns with the BIN latched!
* </p>
* <p>
* If this method returns without the FOUND bit set, the caller can assume
* that no match is possible. Otherwise, if the FOUND bit is set, the caller
* should check the EXACT_KEY and EXACT_DATA bits. If EXACT_KEY is not set
* (or for BOTH and BOTH_RANGE, if EXACT_DATA is not set), an approximate
* match was found. In an approximate match, the cursor is always positioned
* before the target key/data. This allows the caller to perform a 'next'
* operation to advance to the value that is equal or higher than the target
* key/data.
* </p>
* <p>
* Even if the search returns an exact result, the record may be deleted.
* The caller must therefore check for both an approximate match and for
* whether the cursor is positioned on a deleted record.
* </p>
* <p>
* If SET or BOTH is specified, the FOUND bit will only be returned if an
* exact match is found. However, the record found may be deleted.
* </p>
* <p>
* There is one special case where this method may be called without
* checking the EXACT_KEY (and EXACT_DATA) bits and without checking for a
* deleted record: If SearchMode.SET is specified then only the FOUND bit
* need be checked. When SET is specified and FOUND is returned, it is
* guaranteed to be an exact match on a non-deleted record. It is for this
* case only that this method is public.
* </p>
* <p>
* If FOUND is set, FOUND_LAST may also be set if the cursor is positioned
* on the last record in the database. Note that this state can only be
* counted on as long as the BIN is latched, so it is not set if this method
* must release the latch to lock the record. Therefore, it should only be
* used for optimizations. If FOUND_LAST is set, the cursor is positioned on
* the last record and the BIN is latched. If FOUND_LAST is not set, the
* cursor may or may not be positioned on the last record. Note that exact
* searches always perform an unlatch and a lock, so FOUND_LAST will only be
* set for inexact (range) searches.
* </p>
* <p>
* Be aware that when an approximate match is returned, the index or
* dupIndex may be set to -1. This is done intentionally so that a 'next'
* operation will increment it.
* </p>
*/
public int searchAndPosition( DatabaseEntry matchKey, DatabaseEntry matchData, SearchMode searchMode, LockType lockType) throws DatabaseException {
try {
assert assertCursorState(false) : dumpToString(true);
removeCursor();
bin=null;
boolean foundSomething=false;
boolean foundExactKey=false;
boolean foundExactData=false;
boolean foundLast=false;
boolean exactSearch=searchMode.isExactSearch();
BINBoundary binBoundary=new BINBoundary();
this.hook235(matchKey,matchData,searchMode,lockType,foundSomething,foundExactKey,foundExactData,foundLast,exactSearch,binBoundary);
throw ReturnHack.returnInt;
}
catch ( ReturnInt r) {
return r.value;
}
}
/**
* For this type of search, we need to match both key and data. This method
* is called after the key is matched to perform the data portion of the
* match. We may be matching just against an LN, or doing further searching
* into the dup tree. See searchAndPosition for more details.
*/
private int searchAndPositionBoth( boolean containsDuplicates, Node n, DatabaseEntry matchData, boolean exactSearch, LockType lockType, long oldLsn) throws DatabaseException {
assert assertCursorState(false) : dumpToString(true);
boolean found=false;
boolean exact=false;
assert (matchData != null);
byte[] data=Key.makeKey(matchData);
if (containsDuplicates) {
DIN duplicateRoot=(DIN)n;
this.hook236(duplicateRoot);
dupBin=(DBIN)database.getTree().searchSubTree(duplicateRoot,data,Tree.SearchType.NORMAL,-1,null,true);
if (dupBin != null) {
addCursor(dupBin);
dupIndex=dupBin.findEntry(data,true,exactSearch);
if (dupIndex >= 0) {
if ((dupIndex & IN.EXACT_MATCH) != 0) {
exact=true;
}
dupIndex&=~IN.EXACT_MATCH;
found=true;
}
else {
dupIndex=-1;
found=!exactSearch;
}
}
}
else {
LN ln=(LN)n;
LockResult lockResult=lockLN(ln,lockType);
ln=lockResult.getLN();
if (ln == null) {
found=!exactSearch;
}
else {
dupBin=null;
dupIndex=-1;
int cmp=Key.compareKeys(ln.getData(),data,database.getDuplicateComparator());
if (cmp == 0 || (cmp <= 0 && !exactSearch)) {
if (cmp == 0) {
exact=true;
}
found=true;
}
else {
index--;
found=!exactSearch;
}
}
}
return (found ? FOUND : 0) | (exact ? EXACT_DATA : 0);
}
private OperationStatus fetchCurrent( DatabaseEntry foundKey, DatabaseEntry foundData, LockType lockType, boolean first) throws DatabaseException {
return new CursorImpl_fetchCurrent(this,foundKey,foundData,lockType,first).execute();
}
/**
* Locks the given LN's node ID; a deleted LN will not be locked or
* returned. Attempts to use a non-blocking lock to avoid
* unlatching/relatching. Retries if necessary, to handle the case where the
* LN is changed while the BIN is unlatched.
* Preconditions: The target BIN must be latched. When positioned in a dup
* tree, the BIN may be latched on entry also and if so it will be latched
* on exit.
* Postconditions: The target BIN is latched. When positioned in a dup tree,
* the BIN will be latched if it was latched on entry or a blocking lock was
* needed. Therefore, when positioned in a dup tree, releaseDBIN should be
* called.
* @param lnthe LN to be locked.
* @param lockTypethe type of lock requested.
* @return the LockResult containing the LN that was locked, or containing a
* null LN if the LN was deleted or cleaned. If the LN is deleted, a
* lock will not be held.
*/
private LockResult lockLN( LN ln, LockType lockType) throws DatabaseException {
LockResult lockResult=lockLNDeletedAllowed(ln,lockType);
ln=lockResult.getLN();
if (ln != null) {
setTargetBin();
if (targetBin.isEntryKnownDeleted(targetIndex) || ln.isDeleted()) {
revertLock(ln.getNodeId(),lockResult.getLockGrant());
lockResult.setLN(null);
}
}
return lockResult;
}
/**
* Locks the given LN's node ID; a deleted LN will be locked and returned.
* Attempts to use a non-blocking lock to avoid unlatching/relatching.
* Retries if necessary, to handle the case where the LN is changed while
* the BIN is unlatched.
* Preconditions: The target BIN must be latched. When positioned in a dup
* tree, the BIN may be latched on entry also and if so it will be latched
* on exit.
* Postconditions: The target BIN is latched. When positioned in a dup tree,
* the BIN will be latched if it was latched on entry or a blocking lock was
* needed. Therefore, when positioned in a dup tree, releaseDBIN should be
* called.
* @param lnthe LN to be locked.
* @param lockTypethe type of lock requested.
* @return the LockResult containing the LN that was locked, or containing a
* null LN if the LN was cleaned.
*/
public LockResult lockLNDeletedAllowed( LN ln, LockType lockType) throws DatabaseException {
LockResult lockResult;
if (lockType == LockType.NONE) {
lockResult=new LockResult(LockGrantType.NONE_NEEDED,null);
lockResult.setLN(ln);
return lockResult;
}
if (locker.getDefaultNoWait()) {
lockResult=locker.lock(ln.getNodeId(),lockType,true,database);
}
else {
lockResult=locker.nonBlockingLock(ln.getNodeId(),lockType,database);
}
if (lockResult.getLockGrant() != LockGrantType.DENIED) {
lockResult.setLN(ln);
return lockResult;
}
while (true) {
long nodeId=ln.getNodeId();
this.hook238();
lockResult=locker.lock(nodeId,lockType,false,database);
this.hook237();
setTargetBin();
ln=(LN)targetBin.fetchTarget(targetIndex);
if (ln != null && nodeId != ln.getNodeId()) {
revertLock(nodeId,lockResult.getLockGrant());
continue;
}
else {
lockResult.setLN(ln);
return lockResult;
}
}
}
/**
* Locks the DupCountLN for the given duplicate root. Attempts to use a
* non-blocking lock to avoid unlatching/relatching.
* Preconditions: The dupRoot, BIN and DBIN are latched. Postconditions: The
* dupRoot, BIN and DBIN are latched.
* Note that the dupRoot may change during locking and should be refetched
* if needed.
* @param dupRootthe duplicate root containing the DupCountLN to be locked.
* @param lockTypethe type of lock requested.
* @return the LockResult containing the LN that was locked.
*/
public LockResult lockDupCountLN( DIN dupRoot, LockType lockType) throws DatabaseException {
DupCountLN ln=dupRoot.getDupCountLN();
LockResult lockResult;
if (locker.getDefaultNoWait()) {
lockResult=locker.lock(ln.getNodeId(),lockType,true,database);
}
else {
lockResult=locker.nonBlockingLock(ln.getNodeId(),lockType,database);
}
if (lockResult.getLockGrant() == LockGrantType.DENIED) {
this.hook241(dupRoot);
lockResult=locker.lock(ln.getNodeId(),lockType,false,database);
this.hook240();
dupRoot=(DIN)bin.fetchTarget(index);
this.hook239(dupRoot);
ln=dupRoot.getDupCountLN();
}
lockResult.setLN(ln);
return lockResult;
}
/**
* Fetch, latch and return the DIN root of the duplicate tree at the cursor
* position.
* Preconditions: The BIN must be latched and the current BIN entry must
* contain a DIN.
* Postconditions: The BIN and DIN will be latched. The DBIN will remain
* latched if isDBINLatched is true.
* @param isDBINLatchedis true if the DBIN is currently latched.
*/
public DIN getLatchedDupRoot( boolean isDBINLatched) throws DatabaseException {
assert bin != null;
this.hook243();
assert index >= 0;
DIN dupRoot=(DIN)bin.fetchTarget(index);
this.hook242(isDBINLatched,dupRoot);
return dupRoot;
}
/**
* Helper to return a Data DBT from a BIN.
*/
private void setDbt( DatabaseEntry data, byte[] bytes){
if (bytes != null) {
boolean partial=data.getPartial();
int off=partial ? data.getPartialOffset() : 0;
int len=partial ? data.getPartialLength() : bytes.length;
if (off + len > bytes.length) {
len=(off > bytes.length) ? 0 : bytes.length - off;
}
byte[] newdata=null;
if (len == 0) {
newdata=LogUtils.ZERO_LENGTH_BYTE_ARRAY;
}
else {
newdata=new byte[len];
System.arraycopy(bytes,off,newdata,0,len);
}
data.setData(newdata);
data.setOffset(0);
data.setSize(len);
}
else {
data.setData(null);
data.setOffset(0);
data.setSize(0);
}
}
/**
* Calls checkCursorState and returns false is an exception is thrown.
*/
private boolean assertCursorState( boolean mustBeInitialized){
try {
checkCursorState(mustBeInitialized);
return true;
}
catch ( DatabaseException e) {
return false;
}
}
/**
* Check that the cursor is open and optionally if it is initialized.
*/
public void checkCursorState( boolean mustBeInitialized) throws DatabaseException {
if (status == CURSOR_INITIALIZED) {
this.hook278();
return;
}
else if (status == CURSOR_NOT_INITIALIZED) {
if (mustBeInitialized) {
throw new DatabaseException("Cursor Not Initialized.");
}
}
else if (status == CURSOR_CLOSED) {
throw new DatabaseException("Cursor has been closed.");
}
else {
throw new DatabaseException("Unknown cursor status: " + status);
}
}
/**
* Return this lock to its prior status. If the lock was just obtained,
* release it. If it was promoted, demote it.
*/
private void revertLock( LN ln, LockResult lockResult) throws DatabaseException {
revertLock(ln.getNodeId(),lockResult.getLockGrant());
}
/**
* Return this lock to its prior status. If the lock was just obtained,
* release it. If it was promoted, demote it.
*/
private void revertLock( long nodeId, LockGrantType lockStatus) throws DatabaseException {
if ((lockStatus == LockGrantType.NEW) || (lockStatus == LockGrantType.WAIT_NEW)) {
locker.releaseLock(nodeId);
}
else if ((lockStatus == LockGrantType.PROMOTION) || (lockStatus == LockGrantType.WAIT_PROMOTION)) {
locker.demoteLock(nodeId);
}
}
/**
* Locks the logical EOF node for the database.
*/
public void lockEofNode( LockType lockType) throws DatabaseException {
locker.lock(database.getEofNodeId(),lockType,false,database);
}
/**
* @throws RunRecoveryExceptionif the underlying environment is invalid.
*/
public void checkEnv() throws RunRecoveryException {
database.getDbEnvironment().checkIfInvalid();
}
public CursorImpl getLockerPrev(){
return lockerPrev;
}
public CursorImpl getLockerNext(){
return lockerNext;
}
public void setLockerPrev( CursorImpl p){
lockerPrev=p;
}
public void setLockerNext( CursorImpl n){
lockerNext=n;
}
/**
* Dump the cursor for debugging purposes. Dump the bin and dbin that the
* cursor refers to if verbose is true.
*/
public void dump( boolean verbose){
System.out.println(dumpToString(verbose));
}
/**
* dump the cursor for debugging purposes.
*/
public void dump(){
System.out.println(dumpToString(true));
}
private String statusToString( byte status){
switch (status) {
case CURSOR_NOT_INITIALIZED:
return "CURSOR_NOT_INITIALIZED";
case CURSOR_INITIALIZED:
return "CURSOR_INITIALIZED";
case CURSOR_CLOSED:
return "CURSOR_CLOSED";
default :
return "UNKNOWN (" + Byte.toString(status) + ")";
}
}
public String dumpToString(boolean verbose){
StringBuffer sb=new StringBuffer();
sb.append("<Cursor idx=\"").append(index).append("\"");
if (dupBin != null) {
sb.append(" dupIdx=\"").append(dupIndex).append("\"");
}
sb.append(" status=\"").append(statusToString(status)).append("\"");
sb.append(">\n");
if (verbose) {
sb.append((bin == null) ? "" : bin.dumpString(2,true));
sb.append((dupBin == null) ? "" : dupBin.dumpString(2,true));
}
sb.append("\n</Cursor>");
return sb.toString();
}
public void setTestHook(TestHook hook){
testHook=hook;
}
@MethodObject static class CursorImpl_latchBIN {
CursorImpl_latchBIN(CursorImpl _this){
this._this=_this;
}
BIN execute() throws DatabaseException {
try {
this.hook244();
throw ReturnHack.returnObject;
}
catch (ReturnObject r) {
return (BIN)r.value;
}
}
protected CursorImpl _this;
protected BIN waitingOn;
protected void hook244() throws DatabaseException {
this.hook245();
}
protected void hook245() throws DatabaseException {
throw new ReturnObject(_this.bin);
}
}
@MethodObject static class CursorImpl_latchDBIN {
CursorImpl_latchDBIN(CursorImpl _this){
this._this=_this;
}
DBIN execute() throws DatabaseException {
try {
this.hook246();
throw ReturnHack.returnObject;
}
catch (ReturnObject r) {
return (DBIN)r.value;
}
}
protected CursorImpl _this;
protected BIN waitingOn;
protected void hook246() throws DatabaseException {
this.hook247();
}
protected void hook247() throws DatabaseException {
throw new ReturnObject(_this.dupBin);
}
}
@MethodObject static class CursorImpl_lockNextKeyForInsert {
CursorImpl_lockNextKeyForInsert(CursorImpl _this,DatabaseEntry key,DatabaseEntry data){
this._this=_this;
this.key=key;
this.data=data;
}
void execute() throws DatabaseException {
tempKey=new DatabaseEntry(key.getData(),key.getOffset(),key.getSize());
tempData=new DatabaseEntry(data.getData(),data.getOffset(),data.getSize());
tempKey.setPartial(0,0,true);
tempData.setPartial(0,0,true);
lockedNextKey=false;
searchMode=_this.database.getSortedDuplicates() ? SearchMode.BOTH_RANGE : SearchMode.SET_RANGE;
this.hook248();
if (!lockedNextKey) {
_this.lockEofNode(LockType.RANGE_INSERT);
}
}
protected CursorImpl _this;
protected DatabaseEntry key;
protected DatabaseEntry data;
protected DatabaseEntry tempKey;
protected DatabaseEntry tempData;
protected boolean lockedNextKey;
protected SearchMode searchMode;
protected boolean latched;
protected int searchResult;
protected OperationStatus status;
protected void hook248() throws DatabaseException {
searchResult=_this.searchAndPosition(tempKey,tempData,searchMode,LockType.RANGE_INSERT);
if ((searchResult & _this.FOUND) != 0 && (searchResult & _this.FOUND_LAST) == 0) {
{
}
if ((searchResult & _this.EXACT_KEY) != 0) {
status=_this.getNext(tempKey,tempData,LockType.RANGE_INSERT,true,true);
}
else {
status=_this.getNextNoDup(tempKey,tempData,LockType.RANGE_INSERT,true,true);
}
if (status == OperationStatus.SUCCESS) {
lockedNextKey=true;
}
this.hook249();
}
}
protected void hook249() throws DatabaseException {
}
}
@MethodObject static class CursorImpl_getNextDuplicate {
CursorImpl_getNextDuplicate(CursorImpl _this,DatabaseEntry foundKey,DatabaseEntry foundData,LockType lockType,boolean forward,boolean alreadyLatched){
this._this=_this;
this.foundKey=foundKey;
this.foundData=foundData;
this.lockType=lockType;
this.forward=forward;
this.alreadyLatched=alreadyLatched;
}
OperationStatus execute() throws DatabaseException {
try {
assert _this.assertCursorState(true) : _this.dumpToString(true);
this.hook250();
try {
while (_this.dupBin != null) {
this.hook251();
this.hook279();
if ((forward && ++_this.dupIndex < _this.dupBin.getNEntries()) || (!forward && --_this.dupIndex > -1)) {
ret=OperationStatus.SUCCESS;
if (foundKey != null) {
ret=_this.getCurrentAlreadyLatched(foundKey,foundData,lockType,forward);
}
else {
this.hook252();
}
if (ret == OperationStatus.SUCCESS) {
_this.incrementLNCount();
return ret;
}
else {
this.hook253();
if (_this.dupBinToBeRemoved != null) {
_this.flushDBINToBeRemoved();
}
continue;
}
}
else {
if (_this.dupBinToBeRemoved != null) {
_this.flushDBINToBeRemoved();
}
_this.dupBinToBeRemoved=_this.dupBin;
_this.dupBin=null;
this.hook255();
this.hook275();
this.hook254();
{
}
if (forward) {
newDupBin=(DBIN)_this.database.getTree().getNextBin(_this.dupBinToBeRemoved,true);
}
else {
newDupBin=(DBIN)_this.database.getTree().getPrevBin(_this.dupBinToBeRemoved,true);
}
if (newDupBin == null) {
return OperationStatus.NOTFOUND;
}
else {
if (forward) {
_this.dupIndex=-1;
}
else {
_this.dupIndex=newDupBin.getNEntries();
}
_this.addCursor(newDupBin);
_this.dupBin=newDupBin;
this.hook256();
}
}
}
}
finally {
this.hook257();
if (_this.dupBinToBeRemoved != null) {
_this.flushDBINToBeRemoved();
}
}
return OperationStatus.NOTFOUND;
}
catch (ReturnObject r) {
return (OperationStatus)r.value;
}
}
protected CursorImpl _this;
protected DatabaseEntry foundKey;
protected DatabaseEntry foundData;
protected LockType lockType;
protected boolean forward;
protected boolean alreadyLatched;
protected OperationStatus ret;
protected TreeWalkerStatsAccumulator treeStatsAccumulator;
protected DIN duplicateRoot;
protected DupCountLN dcl;
protected DBIN newDupBin;
protected void hook250() throws DatabaseException {
}
protected void hook251() throws DatabaseException {
}
protected void hook252() throws DatabaseException {
}
protected void hook253() throws DatabaseException {
}
protected void hook254() throws DatabaseException {
}
protected void hook255() throws DatabaseException {
}
protected void hook256() throws DatabaseException {
}
protected void hook257() throws DatabaseException {
}
protected void hook275() throws DatabaseException {
}
protected void hook279() throws DatabaseException {
}
}
@MethodObject static class CursorImpl_fetchCurrent {
CursorImpl_fetchCurrent(CursorImpl _this,DatabaseEntry foundKey,DatabaseEntry foundData,LockType lockType,boolean first){
this._this=_this;
this.foundKey=foundKey;
this.foundData=foundData;
this.lockType=lockType;
this.first=first;
}
OperationStatus execute() throws DatabaseException {
try {
treeStatsAccumulator=_this.getTreeStatsAccumulator();
duplicateFetch=_this.setTargetBin();
if (_this.targetBin == null) {
return OperationStatus.NOTFOUND;
}
this.hook259();
n=null;
if (_this.targetIndex < 0 || _this.targetIndex >= _this.targetBin.getNEntries() || _this.targetBin.isEntryKnownDeleted(_this.targetIndex)) {
}
else {
if (_this.targetBin.isEntryPendingDeleted(_this.targetIndex)) {
this.hook280();
}
this.hook260();
}
if (n == null) {
if (treeStatsAccumulator != null) {
treeStatsAccumulator.incrementDeletedLNCount();
}
this.hook261();
return OperationStatus.KEYEMPTY;
}
_this.addCursor(_this.targetBin);
if (n.containsDuplicates()) {
assert !duplicateFetch;
duplicateRoot=(DIN)n;
this.hook262();
if (_this.positionFirstOrLast(first,duplicateRoot)) {
this.hook263();
}
else {
return OperationStatus.NOTFOUND;
}
}
ln=(LN)n;
assert TestHookExecute.doHookIfSet(_this.testHook);
lockResult=_this.lockLN(ln,lockType);
this.hook258();
throw ReturnHack.returnObject;
}
catch (ReturnObject r) {
return (OperationStatus)r.value;
}
}
protected CursorImpl _this;
protected DatabaseEntry foundKey;
protected DatabaseEntry foundData;
protected LockType lockType;
protected boolean first;
protected TreeWalkerStatsAccumulator treeStatsAccumulator;
protected boolean duplicateFetch;
protected Node n;
protected EnvironmentImpl envImpl;
protected DIN duplicateRoot;
protected LN ln;
protected LockResult lockResult;
protected byte[] lnData;
protected void hook258() throws DatabaseException {
ln=lockResult.getLN();
lnData=(ln != null) ? ln.getData() : null;
if (ln == null || lnData == null) {
if (treeStatsAccumulator != null) {
treeStatsAccumulator.incrementDeletedLNCount();
}
throw new ReturnObject(OperationStatus.KEYEMPTY);
}
duplicateFetch=_this.setTargetBin();
if (duplicateFetch) {
if (foundData != null) {
_this.setDbt(foundData,_this.targetBin.getKey(_this.targetIndex));
}
if (foundKey != null) {
_this.setDbt(foundKey,_this.targetBin.getDupKey());
}
}
else {
if (foundData != null) {
_this.setDbt(foundData,lnData);
}
if (foundKey != null) {
_this.setDbt(foundKey,_this.targetBin.getKey(_this.targetIndex));
}
}
throw new ReturnObject(OperationStatus.SUCCESS);
}
protected void hook259() throws DatabaseException {
}
protected void hook260() throws DatabaseException {
n=_this.targetBin.fetchTarget(_this.targetIndex);
}
protected void hook261() throws DatabaseException {
}
protected void hook262() throws DatabaseException {
}
protected void hook263() throws DatabaseException {
throw new ReturnObject(_this.fetchCurrent(foundKey,foundData,lockType,first));
}
protected void hook280() throws DatabaseException {
}
}
protected void hook204(LN ln,long oldLsn,long newLsn) throws DatabaseException {
}
protected void hook205(LN ln,long oldLsn,long newLsn) throws DatabaseException {
}
protected void hook206() throws DatabaseException, CloneNotSupportedException {
}
protected void hook207() throws DatabaseException {
}
protected void hook208(BIN bin){
}
protected void hook209(BIN abin) throws DatabaseException {
}
protected void hook210(DBIN abin) throws DatabaseException {
}
protected void hook211() throws DatabaseException {
}
protected void hook212(LockType lockType) throws DatabaseException {
if (bin.getNEntries() <= index) {
throw new ReturnInt(0);
}
Node n=bin.fetchTarget(index);
if (n != null && n.containsDuplicates()) {
DIN dupRoot=(DIN)n;
this.hook265(dupRoot);
DupCountLN dupCountLN=(DupCountLN)dupRoot.getDupCountLNRef().fetchTarget(database,dupRoot);
this.hook264(dupRoot);
if (lockType != LockType.NONE) {
locker.lock(dupCountLN.getNodeId(),lockType,false,database);
}
throw new ReturnInt(dupCountLN.getDupCount());
}
else {
throw new ReturnInt(1);
}
}
protected void hook213(boolean isDup,LN ln,LockResult lockResult,LockResult dclLockResult,DIN dupRoot) throws DatabaseException {
isDup=(dupBin != null);
if (isDup) {
dupRoot=getLatchedDupRoot(true);
dclLockResult=lockDupCountLN(dupRoot,LockType.WRITE);
dupRoot=(DIN)bin.getTarget(index);
this.hook267();
}
setTargetBin();
long oldLsn=targetBin.getLsn(targetIndex);
byte[] lnKey=targetBin.getKey(targetIndex);
lockResult.setAbortLsn(oldLsn,targetBin.isEntryKnownDeleted(targetIndex));
long oldLNSize=0;
oldLNSize=this.hook284(ln,oldLNSize);
long newLsn=ln.delete(database,lnKey,dupKey,oldLsn,locker);
long newLNSize=0;
newLNSize=this.hook283(ln,newLNSize);
targetBin.updateEntry(targetIndex,newLsn,oldLNSize,newLNSize);
targetBin.setPendingDeleted(targetIndex);
this.hook266();
if (isDup) {
dupRoot.incrementDuplicateCount(dclLockResult,dupKey,locker,false);
this.hook268(dupRoot);
dupRoot=null;
this.hook281(lnKey);
}
else {
this.hook282(lnKey);
}
this.hook204(ln,oldLsn,newLsn);
}
protected void hook214() throws DatabaseException {
}
protected void hook215() throws DatabaseException {
}
protected void hook216() throws DatabaseException {
}
protected void hook217() throws DatabaseException {
}
protected void hook218(DatabaseEntry data,DatabaseEntry foundKey,DatabaseEntry foundData,boolean isDup) throws DatabaseException {
LN ln=(LN)targetBin.fetchTarget(targetIndex);
byte[] lnKey=targetBin.getKey(targetIndex);
Comparator userComparisonFcn=targetBin.getKeyComparator();
if (targetBin.isEntryKnownDeleted(targetIndex) || ln == null) {
this.hook270();
throw new ReturnObject(OperationStatus.NOTFOUND);
}
LockResult lockResult=lockLN(ln,LockType.WRITE);
ln=lockResult.getLN();
if (ln == null) {
this.hook271();
throw new ReturnObject(OperationStatus.NOTFOUND);
}
byte[] foundDataBytes;
byte[] foundKeyBytes;
isDup=setTargetBin();
if (isDup) {
foundDataBytes=lnKey;
foundKeyBytes=targetBin.getDupKey();
}
else {
foundDataBytes=ln.getData();
foundKeyBytes=lnKey;
}
byte[] newData;
if (data.getPartial()) {
int dlen=data.getPartialLength();
int doff=data.getPartialOffset();
int origlen=(foundDataBytes != null) ? foundDataBytes.length : 0;
int oldlen=(doff + dlen > origlen) ? doff + dlen : origlen;
int len=oldlen - dlen + data.getSize();
if (len == 0) {
newData=LogUtils.ZERO_LENGTH_BYTE_ARRAY;
}
else {
newData=new byte[len];
}
int pos=0;
int slicelen=(doff < origlen) ? doff : origlen;
if (slicelen > 0) System.arraycopy(foundDataBytes,0,newData,pos,slicelen);
pos+=doff;
slicelen=data.getSize();
System.arraycopy(data.getData(),data.getOffset(),newData,pos,slicelen);
pos+=slicelen;
slicelen=origlen - (doff + dlen);
if (slicelen > 0) System.arraycopy(foundDataBytes,doff + dlen,newData,pos,slicelen);
}
else {
int len=data.getSize();
if (len == 0) {
newData=LogUtils.ZERO_LENGTH_BYTE_ARRAY;
}
else {
newData=new byte[len];
}
System.arraycopy(data.getData(),data.getOffset(),newData,0,len);
}
if (database.getSortedDuplicates()) {
boolean keysEqual=false;
if (foundDataBytes != null) {
keysEqual=Key.compareKeys(foundDataBytes,newData,userComparisonFcn) == 0;
}
if (!keysEqual) {
revertLock(ln,lockResult);
throw new DatabaseException("Can't replace a duplicate with different data.");
}
}
if (foundData != null) {
setDbt(foundData,foundDataBytes);
}
if (foundKey != null) {
setDbt(foundKey,foundKeyBytes);
}
long oldLsn=targetBin.getLsn(targetIndex);
lockResult.setAbortLsn(oldLsn,targetBin.isEntryKnownDeleted(targetIndex));
long oldLNSize=0;
oldLNSize=this.hook286(ln,oldLNSize);
byte[] newKey=(isDup ? targetBin.getDupKey() : lnKey);
long newLsn=ln.modify(newData,database,newKey,oldLsn,locker);
long newLNSize=0;
newLNSize=this.hook285(ln,newLNSize);
targetBin.updateEntry(targetIndex,newLsn,oldLNSize,newLNSize);
this.hook269();
this.hook205(ln,oldLsn,newLsn);
status=CURSOR_INITIALIZED;
throw new ReturnObject(OperationStatus.SUCCESS);
}
protected void hook219() throws DatabaseException {
}
protected void hook220() throws DatabaseException {
}
protected void hook221(DatabaseEntry foundKey,DatabaseEntry foundData,LockType lockType,boolean first) throws DatabaseException {
throw new ReturnObject(fetchCurrent(foundKey,foundData,lockType,first));
}
protected void hook222() throws DatabaseException {
}
protected void hook223(LockType lockType) throws DatabaseException {
assert assertCursorState(true) : dumpToString(true);
this.hook272();
if (bin == null) {
throw new ReturnObject(null);
}
LN ln=null;
if (!bin.isEntryKnownDeleted(index)) {
ln=(LN)bin.fetchTarget(index);
}
if (ln == null) {
this.hook273();
throw new ReturnObject(null);
}
addCursor(bin);
LockResult lockResult=lockLN(ln,lockType);
ln=lockResult.getLN();
throw new ReturnObject(ln);
}
protected void hook224(boolean alreadyLatched) throws DatabaseException {
}
protected boolean hook225(boolean alreadyLatched) throws DatabaseException {
return alreadyLatched;
}
protected boolean hook226(boolean alreadyLatched) throws DatabaseException {
return alreadyLatched;
}
protected void hook227() throws DatabaseException {
}
protected void hook228() throws DatabaseException {
}
protected void hook229() throws DatabaseException {
}
protected void hook230(boolean alreadyLatched) throws DatabaseException {
}
protected void hook231() throws DatabaseException {
}
protected void hook232() throws DatabaseException {
}
protected void hook233() throws DatabaseException {
}
protected void hook234(boolean first,DIN duplicateRoot,IN in,boolean found) throws DatabaseException {
if (duplicateRoot == null) {
removeCursorBIN();
if (first) {
in=database.getTree().getFirstNode();
}
else {
in=database.getTree().getLastNode();
}
if (in != null) {
assert (in instanceof BIN);
dupBin=null;
dupIndex=-1;
bin=(BIN)in;
index=(first ? 0 : (bin.getNEntries() - 1));
addCursor(bin);
TreeWalkerStatsAccumulator treeStatsAccumulator=getTreeStatsAccumulator();
if (bin.getNEntries() == 0) {
found=true;
}
else {
Node n=null;
if (!in.isEntryKnownDeleted(index)) {
n=in.fetchTarget(index);
}
if (n != null && n.containsDuplicates()) {
DIN dupRoot=(DIN)n;
this.hook274(in,dupRoot);
in=null;
found=positionFirstOrLast(first,dupRoot);
}
else {
if (treeStatsAccumulator != null) {
if (n == null || ((LN)n).isDeleted()) {
treeStatsAccumulator.incrementDeletedLNCount();
}
else {
treeStatsAccumulator.incrementLNCount();
}
}
found=true;
}
}
}
}
else {
removeCursorDBIN();
if (first) {
in=database.getTree().getFirstNode(duplicateRoot);
}
else {
in=database.getTree().getLastNode(duplicateRoot);
}
if (in != null) {
assert (in instanceof DBIN);
dupBin=(DBIN)in;
dupIndex=(first ? 0 : (dupBin.getNEntries() - 1));
addCursor(dupBin);
found=true;
}
}
status=CURSOR_INITIALIZED;
throw new ReturnBoolean(found);
}
protected void hook235(DatabaseEntry matchKey,DatabaseEntry matchData,SearchMode searchMode,LockType lockType,boolean foundSomething,boolean foundExactKey,boolean foundExactData,boolean foundLast,boolean exactSearch,BINBoundary binBoundary) throws DatabaseException {
byte[] key=Key.makeKey(matchKey);
bin=(BIN)database.getTree().search(key,Tree.SearchType.NORMAL,-1,binBoundary,true);
if (bin != null) {
addCursor(bin);
index=bin.findEntry(key,true,exactSearch);
foundSomething=!exactSearch;
dupBin=null;
dupIndex=-1;
boolean containsDuplicates=false;
if (index >= 0) {
if ((index & IN.EXACT_MATCH) != 0) {
foundExactKey=true;
index&=~IN.EXACT_MATCH;
}
Node n=null;
if (!bin.isEntryKnownDeleted(index)) {
n=bin.fetchTarget(index);
}
if (n != null) {
containsDuplicates=n.containsDuplicates();
if (searchMode.isDataSearch()) {
if (foundExactKey) {
int searchResult=searchAndPositionBoth(containsDuplicates,n,matchData,exactSearch,lockType,bin.getLsn(index));
foundSomething=(searchResult & FOUND) != 0;
foundExactData=(searchResult & EXACT_DATA) != 0;
}
}
else {
foundSomething=true;
if (!containsDuplicates && exactSearch) {
LN ln=(LN)n;
LockResult lockResult=lockLN(ln,lockType);
ln=lockResult.getLN();
if (ln == null) {
foundSomething=false;
}
}
}
}
foundLast=(searchMode == SearchMode.SET_RANGE && foundSomething && !containsDuplicates && binBoundary.isLastBin && index == bin.getNEntries() - 1);
}
}
status=CURSOR_INITIALIZED;
throw new ReturnInt((foundSomething ? FOUND : 0) | (foundExactKey ? EXACT_KEY : 0) | (foundExactData ? EXACT_DATA : 0)| (foundLast ? FOUND_LAST : 0));
}
protected void hook236(DIN duplicateRoot) throws DatabaseException {
}
protected void hook237() throws DatabaseException {
}
protected void hook238() throws DatabaseException {
}
protected void hook239(DIN dupRoot) throws DatabaseException {
}
protected void hook240() throws DatabaseException {
}
protected void hook241(DIN dupRoot) throws DatabaseException {
}
protected void hook242(boolean isDBINLatched,DIN dupRoot) throws DatabaseException {
}
protected void hook243() throws DatabaseException {
}
protected void hook264(DIN dupRoot) throws DatabaseException {
}
protected void hook265(DIN dupRoot) throws DatabaseException {
}
protected void hook266() throws DatabaseException {
}
protected void hook267() throws DatabaseException {
}
protected void hook268(DIN dupRoot) throws DatabaseException {
}
protected void hook269() throws DatabaseException {
}
protected void hook270() throws DatabaseException {
}
protected void hook271() throws DatabaseException {
}
protected void hook272() throws DatabaseException {
}
protected void hook273() throws DatabaseException {
}
protected void hook274(IN in,DIN dupRoot) throws DatabaseException {
}
protected void hook276() throws DatabaseException {
}
protected void hook277() throws DatabaseException {
}
protected void hook278() throws DatabaseException {
}
protected void hook281(byte[] lnKey) throws DatabaseException {
}
protected void hook282(byte[] lnKey) throws DatabaseException {
}
protected long hook283(LN ln,long newLNSize) throws DatabaseException {
return newLNSize;
}
protected long hook284(LN ln,long oldLNSize) throws DatabaseException {
return oldLNSize;
}
protected long hook285(LN ln,long newLNSize) throws DatabaseException {
return newLNSize;
}
protected long hook286(LN ln,long oldLNSize) throws DatabaseException {
return oldLNSize;
}
}