package com.sleepycat.je; import java.util.logging.Level; import java.util.logging.Logger; import com.sleepycat.je.dbi.CursorImpl; import com.sleepycat.je.dbi.DatabaseImpl; import com.sleepycat.je.dbi.GetMode; import com.sleepycat.je.dbi.PutMode; import com.sleepycat.je.dbi.RangeRestartException; import com.sleepycat.je.dbi.CursorImpl.KeyChangeStatus; import com.sleepycat.je.dbi.CursorImpl.SearchMode; import com.sleepycat.je.tree.BIN; import com.sleepycat.je.tree.DBIN; import com.sleepycat.je.tree.Key; import com.sleepycat.je.tree.LN; import com.sleepycat.je.tree.Node; import com.sleepycat.je.txn.BuddyLocker; import com.sleepycat.je.txn.LockType; import com.sleepycat.je.txn.Locker; import com.sleepycat.je.txn.LockerFactory; import com.sleepycat.je.utilint.InternalException; import de.ovgu.cide.jakutil.*; /** * Javadoc for this public class is generated * via the doc templates in the doc_src directory. */ public class Cursor { /** * The underlying cursor. */ CursorImpl cursorImpl; /** * The CursorConfig used to configure this cursor. */ CursorConfig config; /** * True if update operations are prohibited through this cursor. Update * operations are prohibited if the database is read-only or: * (1) The database is transactional, * and * (2) The user did not supply a txn to the cursor ctor (meaning, the * locker is non-transactional). */ private boolean updateOperationsProhibited; /** * Handle under which this cursor was created; may be null. */ private Database dbHandle; /** * Database implementation. */ private DatabaseImpl dbImpl; private boolean readUncommittedDefault; private boolean serializableIsolationDefault; /** * Creates a cursor for a given user transaction. * <p>If txn is null, a non-transactional cursor will be created that * releases locks for the prior operation when the next operation * suceeds.</p> */ Cursor( Database dbHandle, Transaction txn, CursorConfig cursorConfig) throws DatabaseException { if (cursorConfig == null) { cursorConfig=CursorConfig.DEFAULT; } Locker locker=LockerFactory.getReadableLocker(dbHandle.getEnvironment(),txn,dbHandle.isTransactional(),false,cursorConfig.getReadCommitted()); init(dbHandle,dbHandle.getDatabaseImpl(),locker,dbHandle.isWritable(),cursorConfig); } /** * Creates a cursor for a given locker. * <p>If locker is null or is non-transactional, a non-transactional cursor * will be created that releases locks for the prior operation when the * next operation suceeds.</p> */ Cursor( Database dbHandle, Locker locker, CursorConfig cursorConfig) throws DatabaseException { if (cursorConfig == null) { cursorConfig=CursorConfig.DEFAULT; } locker=LockerFactory.getReadableLocker(dbHandle.getEnvironment(),dbHandle,locker,false,cursorConfig.getReadCommitted()); init(dbHandle,dbHandle.getDatabaseImpl(),locker,dbHandle.isWritable(),cursorConfig); } /** * Creates a cursor for a given locker and no db handle. * <p>The locker parameter must be non-null. With this constructor, we use * the given locker without applying any special rules for different * isolation levels -- the caller must supply the correct locker.</p> */ Cursor( DatabaseImpl dbImpl, Locker locker, CursorConfig cursorConfig) throws DatabaseException { if (cursorConfig == null) { cursorConfig=CursorConfig.DEFAULT; } init(null,dbImpl,locker,true,cursorConfig); } private void init( Database dbHandle, DatabaseImpl dbImpl, Locker locker, boolean isWritable, CursorConfig cursorConfig) throws DatabaseException { assert locker != null; assert dbImpl != null; cursorImpl=new CursorImpl(dbImpl,locker,false); readUncommittedDefault=cursorConfig.getReadUncommitted() || locker.isReadUncommittedDefault(); serializableIsolationDefault=cursorImpl.getLocker().isSerializableIsolation(); updateOperationsProhibited=(dbImpl.isTransactional() && !locker.isTransactional()) || !isWritable; this.dbImpl=dbImpl; this.dbHandle=dbHandle; if (dbHandle != null) { dbHandle.addCursor(this); } this.config=cursorConfig; this.hook36(dbImpl); } /** * Copy constructor. */ Cursor( Cursor cursor, boolean samePosition) throws DatabaseException { readUncommittedDefault=cursor.readUncommittedDefault; serializableIsolationDefault=cursor.serializableIsolationDefault; updateOperationsProhibited=cursor.updateOperationsProhibited; cursorImpl=cursor.cursorImpl.dup(samePosition); dbImpl=cursor.dbImpl; dbHandle=cursor.dbHandle; if (dbHandle != null) { dbHandle.addCursor(this); } config=cursor.config; } /** * Internal entrypoint. */ CursorImpl getCursorImpl(){ return cursorImpl; } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public Database getDatabase(){ return dbHandle; } /** * Always returns non-null, while getDatabase() returns null if no handle * is associated with this cursor. */ DatabaseImpl getDatabaseImpl(){ return dbImpl; } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public CursorConfig getConfig(){ return config.cloneConfig(); } void setNonCloning( boolean nonCloning){ cursorImpl.setNonCloning(nonCloning); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public synchronized void close() throws DatabaseException { checkState(false); cursorImpl.close(); if (dbHandle != null) { dbHandle.removeCursor(this); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public int count() throws DatabaseException { checkState(true); this.hook0(); return countInternal(null); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public Cursor dup( boolean samePosition) throws DatabaseException { checkState(false); return new Cursor(this,samePosition); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus delete() throws DatabaseException { checkState(true); checkUpdatesAllowed("delete"); this.hook1(); return deleteInternal(); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus put( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",true); DatabaseUtil.checkForPartialKey(key); checkUpdatesAllowed("put"); this.hook2(key,data); return putInternal(key,data,PutMode.OVERWRITE); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putNoOverwrite( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",true); DatabaseUtil.checkForPartialKey(key); checkUpdatesAllowed("putNoOverwrite"); this.hook3(key,data); return putInternal(key,data,PutMode.NOOVERWRITE); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putNoDupData( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",true); DatabaseUtil.checkForPartialKey(key); checkUpdatesAllowed("putNoDupData"); this.hook4(key,data); return putInternal(key,data,PutMode.NODUP); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus putCurrent( DatabaseEntry data) throws DatabaseException { checkState(true); DatabaseUtil.checkForNullDbt(data,"data",true); checkUpdatesAllowed("putCurrent"); this.hook5(data); return putInternal(null,data,PutMode.CURRENT); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getCurrent( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key,data); this.hook6(lockMode); return getCurrentInternal(key,data,lockMode); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getFirst( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook7(lockMode); return position(key,data,lockMode,true); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getLast( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook8(lockMode); return position(key,data,lockMode,false); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNext( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook9(lockMode); if (cursorImpl.isNotInitialized()) { return position(key,data,lockMode,true); } else { return retrieveNext(key,data,lockMode,GetMode.NEXT); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextDup( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key,data); this.hook10(lockMode); return retrieveNext(key,data,lockMode,GetMode.NEXT_DUP); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getNextNoDup( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook11(lockMode); if (cursorImpl.isNotInitialized()) { return position(key,data,lockMode,true); } else { return retrieveNext(key,data,lockMode,GetMode.NEXT_NODUP); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrev( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook12(lockMode); if (cursorImpl.isNotInitialized()) { return position(key,data,lockMode,false); } else { return retrieveNext(key,data,lockMode,GetMode.PREV); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevDup( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(true); checkArgsNoValRequired(key,data); this.hook13(lockMode); return retrieveNext(key,data,lockMode,GetMode.PREV_DUP); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getPrevNoDup( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsNoValRequired(key,data); this.hook14(lockMode); if (cursorImpl.isNotInitialized()) { return position(key,data,lockMode,false); } else { return retrieveNext(key,data,lockMode,GetMode.PREV_NODUP); } } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKey( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",false); this.hook15(key,lockMode); return search(key,data,lockMode,SearchMode.SET); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchKeyRange( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",false); this.hook16(key,lockMode); return search(key,data,lockMode,SearchMode.SET_RANGE); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBoth( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsValRequired(key,data); this.hook17(key,data,lockMode); return search(key,data,lockMode,SearchMode.BOTH); } /** * Javadoc for this public method is generated via * the doc templates in the doc_src directory. */ public OperationStatus getSearchBothRange( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { checkState(false); checkArgsValRequired(key,data); this.hook18(key,data,lockMode); return search(key,data,lockMode,SearchMode.BOTH_RANGE); } /** * Counts duplicates without parameter checking. */ int countInternal( LockMode lockMode) throws DatabaseException { CursorImpl origCursor=null; CursorImpl dup=null; try { origCursor=cursorImpl; dup=origCursor.cloneCursor(true); return dup.count(getLockType(lockMode,false)); } finally { if (dup != origCursor) { dup.close(); } } } /** * Internal version of delete() that does no parameter checking. Calls * deleteNoNotify() and notifies triggers (performs secondary updates). */ OperationStatus deleteInternal() throws DatabaseException { DatabaseEntry oldKey=null; DatabaseEntry oldData=null; boolean doNotifyTriggers=dbHandle != null && dbHandle.hasTriggers(); if (doNotifyTriggers) { oldKey=new DatabaseEntry(); oldData=new DatabaseEntry(); OperationStatus status=getCurrentInternal(oldKey,oldData,LockMode.RMW); if (status != OperationStatus.SUCCESS) { return OperationStatus.KEYEMPTY; } } if (doNotifyTriggers) { dbHandle.notifyTriggers(cursorImpl.getLocker(),oldKey,oldData,null); } OperationStatus status=deleteNoNotify(); return status; } /** * Clone the cursor, delete at current position, and if successful, swap * cursors. Does not notify triggers (does not perform secondary updates). */ OperationStatus deleteNoNotify() throws DatabaseException { CursorImpl origCursor=null; CursorImpl dup=null; OperationStatus status=OperationStatus.KEYEMPTY; try { origCursor=cursorImpl; dup=origCursor.cloneCursor(true); this.hook19(dup); status=dup.delete(); return status; } finally { this.hook20(origCursor,dup); boolean success=(status == OperationStatus.SUCCESS); if (cursorImpl == dup) { if (!success) { cursorImpl.reset(); } } else { if (success) { origCursor.close(); cursorImpl=dup; } else { dup.close(); } } } } /** * Internal version of put() that does no parameter checking. Calls * putNoNotify() and notifies triggers (performs secondary updates). * Prevents phantoms. */ OperationStatus putInternal( DatabaseEntry key, DatabaseEntry data, PutMode putMode) throws DatabaseException { DatabaseEntry oldData=null; boolean doNotifyTriggers=dbHandle != null && dbHandle.hasTriggers(); if (doNotifyTriggers && (putMode == PutMode.CURRENT || putMode == PutMode.OVERWRITE)) { oldData=new DatabaseEntry(); if (key == null && putMode == PutMode.CURRENT) { key=new DatabaseEntry(); } } OperationStatus commitStatus=putNoNotify(key,data,putMode,oldData); if (doNotifyTriggers && commitStatus == OperationStatus.SUCCESS) { if (oldData != null && oldData.getData() == null) { oldData=null; } dbHandle.notifyTriggers(cursorImpl.getLocker(),key,oldData,data); } return commitStatus; } /** * Performs the put operation but does not notify triggers (does not * perform secondary updates). Prevents phantoms. */ OperationStatus putNoNotify( DatabaseEntry key, DatabaseEntry data, PutMode putMode, DatabaseEntry returnOldData) throws DatabaseException { Locker nextKeyLocker=null; CursorImpl nextKeyCursor=null; try { Locker cursorLocker=cursorImpl.getLocker(); if (putMode != PutMode.CURRENT && dbImpl.getDbEnvironment().getTxnManager().areOtherSerializableTransactionsActive(cursorLocker)) { nextKeyLocker=new BuddyLocker(dbImpl.getDbEnvironment(),cursorLocker); nextKeyCursor=new CursorImpl(dbImpl,nextKeyLocker); nextKeyCursor.lockNextKeyForInsert(key,data); } return putAllowPhantoms(key,data,putMode,returnOldData,nextKeyCursor); } finally { if (nextKeyCursor != null) { nextKeyCursor.close(); } if (nextKeyLocker != null) { nextKeyLocker.operationEnd(); } } } /** * Clone the cursor, put key/data according to PutMode, and if successful, * swap cursors. Does not notify triggers (does not perform secondary * updates). Does not prevent phantoms. * @param nextKeyCursor is the cursor used to lock the next key during * phantom prevention. If this cursor is non-null and initialized, it's * BIN will be used to initialize the dup cursor used to perform insertion. * This enables an optimization that skips the search for the BIN. */ private OperationStatus putAllowPhantoms( DatabaseEntry key, DatabaseEntry data, PutMode putMode, DatabaseEntry returnOldData, CursorImpl nextKeyCursor) throws DatabaseException { if (data == null) { throw new NullPointerException("put passed a null DatabaseEntry arg"); } if (putMode != PutMode.CURRENT && key == null) { throw new IllegalArgumentException("put passed a null DatabaseEntry arg"); } CursorImpl origCursor=null; OperationStatus status=OperationStatus.NOTFOUND; CursorImpl dup=null; try { origCursor=cursorImpl; if (putMode == PutMode.CURRENT) { dup=origCursor.cloneCursor(true); } else { dup=origCursor.cloneCursor(false,nextKeyCursor); } if (putMode == PutMode.CURRENT) { status=dup.putCurrent(data,key,returnOldData); } else if (putMode == PutMode.OVERWRITE) { status=dup.put(key,data,returnOldData); } else if (putMode == PutMode.NOOVERWRITE) { status=dup.putNoOverwrite(key,data); } else if (putMode == PutMode.NODUP) { status=dup.putNoDupData(key,data); } else { throw new InternalException("unknown PutMode"); } return status; } finally { this.hook21(origCursor); boolean success=(status == OperationStatus.SUCCESS); if (cursorImpl == dup) { if (!success) { cursorImpl.reset(); } } else { if (success) { origCursor.close(); cursorImpl=dup; } else { if (dup != null) { dup.close(); } } } } } /** * Position the cursor at the first or last record of the database. * Prevents phantoms. */ OperationStatus position( DatabaseEntry key, DatabaseEntry data, LockMode lockMode, boolean first) throws DatabaseException { if (!isSerializableIsolation(lockMode)) { return positionAllowPhantoms(key,data,getLockType(lockMode,false),first); } while (true) { try { if (!first) { cursorImpl.lockEofNode(LockType.RANGE_READ); } LockType lockType=getLockType(lockMode,first); OperationStatus status=positionAllowPhantoms(key,data,lockType,first); if (first && status != OperationStatus.SUCCESS) { cursorImpl.lockEofNode(LockType.RANGE_READ); } return status; } catch ( RangeRestartException e) { continue; } } } /** * Position without preventing phantoms. */ private OperationStatus positionAllowPhantoms( DatabaseEntry key, DatabaseEntry data, LockType lockType, boolean first) throws DatabaseException { assert (key != null && data != null); OperationStatus status=OperationStatus.NOTFOUND; CursorImpl dup=null; try { dup=beginRead(false); if (!dup.positionFirstOrLast(first,null)) { status=OperationStatus.NOTFOUND; this.hook22(); } else { this.hook23(); status=dup.getCurrentAlreadyLatched(key,data,lockType,first); if (status == OperationStatus.SUCCESS) { if (dup.getDupBIN() != null) { dup.incrementLNCount(); } } else { status=dup.getNext(key,data,lockType,first,false); } } } finally { this.hook24(); endRead(dup,status == OperationStatus.SUCCESS); } return status; } /** * Perform search by key, data, or both. Prevents phantoms. */ OperationStatus search( DatabaseEntry key, DatabaseEntry data, LockMode lockMode, SearchMode searchMode) throws DatabaseException { if (!isSerializableIsolation(lockMode)) { LockType lockType=getLockType(lockMode,false); KeyChangeStatus result=searchAllowPhantoms(key,data,lockType,lockType,searchMode); return result.status; } while (true) { try { LockType searchLockType=getLockType(lockMode,false); LockType advanceLockType=getLockType(lockMode,true); DatabaseEntry tryKey=new DatabaseEntry(key.getData(),key.getOffset(),key.getSize()); DatabaseEntry tryData=new DatabaseEntry(data.getData(),data.getOffset(),data.getSize()); KeyChangeStatus result; if (searchMode.isExactSearch()) { result=searchExactAndRangeLock(tryKey,tryData,searchLockType,advanceLockType,searchMode); } else { result=searchAllowPhantoms(tryKey,tryData,searchLockType,advanceLockType,searchMode); if (result.status != OperationStatus.SUCCESS) { cursorImpl.lockEofNode(LockType.RANGE_READ); } } if (result.status == OperationStatus.SUCCESS) { key.setData(tryKey.getData(),0,tryKey.getSize()); data.setData(tryData.getData(),0,tryData.getSize()); } return result.status; } catch ( RangeRestartException e) { continue; } } } /** * For an exact search, perform a range search and return NOTFOUND if the * key changes (or if the data changes for BOTH) during the search. * If no exact match is found the range search will range lock the * following key for phantom prevention. Importantly, the cursor position * is not changed if an exact match is not found, even though we advance to * the following key in order to range lock it. */ private KeyChangeStatus searchExactAndRangeLock( DatabaseEntry key, DatabaseEntry data, LockType searchLockType, LockType advanceLockType, SearchMode searchMode) throws DatabaseException { searchMode=(searchMode == SearchMode.SET) ? SearchMode.SET_RANGE : SearchMode.BOTH_RANGE; KeyChangeStatus result=null; boolean noNextKeyFound; CursorImpl dup=beginRead(false); try { result=searchInternal(dup,key,data,searchLockType,advanceLockType,searchMode,true); noNextKeyFound=!result.keyChange; if (result.keyChange && result.status == OperationStatus.SUCCESS) { result.status=OperationStatus.NOTFOUND; } } finally { endRead(dup,result != null && result.status == OperationStatus.SUCCESS); } if (noNextKeyFound) { cursorImpl.lockEofNode(LockType.RANGE_READ); } return result; } /** * Perform search without preventing phantoms. */ private KeyChangeStatus searchAllowPhantoms( DatabaseEntry key, DatabaseEntry data, LockType searchLockType, LockType advanceLockType, SearchMode searchMode) throws DatabaseException { OperationStatus status=OperationStatus.NOTFOUND; CursorImpl dup=beginRead(false); try { KeyChangeStatus result=searchInternal(dup,key,data,searchLockType,advanceLockType,searchMode,false); status=result.status; return result; } finally { endRead(dup,status == OperationStatus.SUCCESS); } } /** * Perform search for a given CursorImpl. */ private KeyChangeStatus searchInternal( CursorImpl dup, DatabaseEntry key, DatabaseEntry data, LockType searchLockType, LockType advanceLockType, SearchMode searchMode, boolean advanceAfterRangeSearch) throws DatabaseException { assert key != null && data != null; OperationStatus status=OperationStatus.NOTFOUND; boolean keyChange=false; this.hook25(dup,key,data,searchLockType,advanceLockType,searchMode,advanceAfterRangeSearch,status,keyChange); return new KeyChangeStatus(status,keyChange); } /** * Retrieve the next or previous record. Prevents phantoms. */ OperationStatus retrieveNext( DatabaseEntry key, DatabaseEntry data, LockMode lockMode, GetMode getMode) throws DatabaseException { if (!isSerializableIsolation(lockMode)) { return retrieveNextAllowPhantoms(key,data,getLockType(lockMode,false),getMode); } while (true) { try { OperationStatus status; if (getMode == GetMode.NEXT_DUP) { status=getNextDupAndRangeLock(key,data,lockMode); } else { if (!getMode.isForward()) { rangeLockCurrentPosition(getMode); } LockType lockType=getLockType(lockMode,getMode.isForward()); status=retrieveNextAllowPhantoms(key,data,lockType,getMode); if (getMode.isForward() && status != OperationStatus.SUCCESS) { cursorImpl.lockEofNode(LockType.RANGE_READ); } } return status; } catch ( RangeRestartException e) { continue; } } } /** * Retrieve the next dup; if no next dup is found then range lock the * following key for phantom prevention. Importantly, the cursor position * is not changed if there are no more dups, even though we advance to the * following key in order to range lock it. */ private OperationStatus getNextDupAndRangeLock( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { DatabaseEntry tryKey=new DatabaseEntry(); DatabaseEntry tryData=new DatabaseEntry(); LockType lockType=getLockType(lockMode,true); OperationStatus status; boolean noNextKeyFound; while (true) { this.hook26(); CursorImpl dup=beginRead(true); try { KeyChangeStatus result=dup.getNextWithKeyChangeStatus(tryKey,tryData,lockType,true,false); status=result.status; noNextKeyFound=(status != OperationStatus.SUCCESS); if (result.keyChange && status == OperationStatus.SUCCESS) { status=OperationStatus.NOTFOUND; } } catch ( DatabaseException DBE) { endRead(dup,false); throw DBE; } if (checkForInsertion(GetMode.NEXT,cursorImpl,dup)) { endRead(dup,false); continue; } else { endRead(dup,status == OperationStatus.SUCCESS); this.hook27(); break; } } if (noNextKeyFound) { cursorImpl.lockEofNode(LockType.RANGE_READ); } if (status == OperationStatus.SUCCESS) { key.setData(tryKey.getData(),0,tryKey.getSize()); data.setData(tryData.getData(),0,tryData.getSize()); } return status; } /** * For 'prev' operations, upgrade to a range lock at the current position. * For PREV_NODUP, range lock the first duplicate instead. If there are no * records at the current position, get a range lock on the next record or, * if not found, on the logical EOF node. Do not modify the current * cursor position, use a separate cursor. */ private void rangeLockCurrentPosition( GetMode getMode) throws DatabaseException { DatabaseEntry tempKey=new DatabaseEntry(); DatabaseEntry tempData=new DatabaseEntry(); tempKey.setPartial(0,0,true); tempData.setPartial(0,0,true); OperationStatus status; CursorImpl dup=cursorImpl.cloneCursor(true); try { if (getMode == GetMode.PREV_NODUP) { status=dup.getFirstDuplicate(tempKey,tempData,LockType.RANGE_READ); } else { status=dup.getCurrent(tempKey,tempData,LockType.RANGE_READ); } if (status != OperationStatus.SUCCESS) { while (true) { this.hook28(); status=dup.getNext(tempKey,tempData,LockType.RANGE_READ,true,false); if (checkForInsertion(GetMode.NEXT,cursorImpl,dup)) { dup.close(); dup=cursorImpl.cloneCursor(true); continue; } else { this.hook29(); break; } } } } finally { if (cursorImpl == dup) { dup.reset(); } else { dup.close(); } } if (status != OperationStatus.SUCCESS) { cursorImpl.lockEofNode(LockType.RANGE_READ); } } /** * Retrieve without preventing phantoms. */ private OperationStatus retrieveNextAllowPhantoms( DatabaseEntry key, DatabaseEntry data, LockType lockType, GetMode getMode) throws DatabaseException { assert (key != null && data != null); OperationStatus status; while (true) { this.hook30(); CursorImpl dup=beginRead(true); try { if (getMode == GetMode.NEXT) { status=dup.getNext(key,data,lockType,true,false); } else if (getMode == GetMode.PREV) { status=dup.getNext(key,data,lockType,false,false); } else if (getMode == GetMode.NEXT_DUP) { status=dup.getNextDuplicate(key,data,lockType,true,false); } else if (getMode == GetMode.PREV_DUP) { status=dup.getNextDuplicate(key,data,lockType,false,false); } else if (getMode == GetMode.NEXT_NODUP) { status=dup.getNextNoDup(key,data,lockType,true,false); } else if (getMode == GetMode.PREV_NODUP) { status=dup.getNextNoDup(key,data,lockType,false,false); } else { throw new InternalException("unknown GetMode"); } } catch ( DatabaseException DBE) { endRead(dup,false); throw DBE; } if (checkForInsertion(getMode,cursorImpl,dup)) { endRead(dup,false); continue; } else { endRead(dup,status == OperationStatus.SUCCESS); this.hook31(); break; } } return status; } /** * Returns the current key and data. There is no need to prevent phantoms. */ OperationStatus getCurrentInternal( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { LockType lockType=getLockType(lockMode,false); return cursorImpl.getCurrent(key,data,lockType); } private boolean checkForInsertion( GetMode getMode, CursorImpl origCursor, CursorImpl dupCursor) throws DatabaseException { BIN origBIN=origCursor.getBIN(); BIN dupBIN=dupCursor.getBIN(); DBIN origDBIN=origCursor.getDupBIN(); boolean forward=true; if (getMode == GetMode.PREV || getMode == GetMode.PREV_DUP || getMode == GetMode.PREV_NODUP) { forward=false; } boolean ret=false; if (origBIN != dupBIN) { this.hook33(origCursor); if (origDBIN == null) { if (forward) { if (origBIN.getNEntries() - 1 > origCursor.getIndex()) { for (int i=origCursor.getIndex() + 1; i < origBIN.getNEntries(); i++) { if (!origBIN.isEntryKnownDeleted(i)) { Node n=origBIN.fetchTarget(i); if (n != null && !n.containsDuplicates()) { LN ln=(LN)n; if (!ln.isDeleted()) { ret=true; break; } } } else { } } } } else { if (origCursor.getIndex() > 0) { for (int i=0; i < origCursor.getIndex(); i++) { if (!origBIN.isEntryKnownDeleted(i)) { Node n=origBIN.fetchTarget(i); if (n != null && !n.containsDuplicates()) { LN ln=(LN)n; if (!ln.isDeleted()) { ret=true; break; } } else { } } } } } } this.hook32(origCursor); return ret; } if (origDBIN != dupCursor.getDupBIN() && origCursor.getIndex() == dupCursor.getIndex() && getMode != GetMode.NEXT_NODUP && getMode != GetMode.PREV_NODUP) { this.hook35(origCursor); if (forward) { if (origDBIN.getNEntries() - 1 > origCursor.getDupIndex()) { for (int i=origCursor.getDupIndex() + 1; i < origDBIN.getNEntries(); i++) { if (!origDBIN.isEntryKnownDeleted(i)) { Node n=origDBIN.fetchTarget(i); LN ln=(LN)n; if (n != null && !ln.isDeleted()) { ret=true; break; } } } } } else { if (origCursor.getDupIndex() > 0) { for (int i=0; i < origCursor.getDupIndex(); i++) { if (!origDBIN.isEntryKnownDeleted(i)) { Node n=origDBIN.fetchTarget(i); LN ln=(LN)n; if (n != null && !ln.isDeleted()) { ret=true; break; } } } } } this.hook34(origCursor); return ret; } return false; } /** * If the cursor is initialized, dup it and return the dup; otherwise, * return the original. This avoids the overhead of duping when the * original is uninitialized. The cursor returned must be passed to * endRead() to close the correct cursor. */ private CursorImpl beginRead( boolean addCursor) throws DatabaseException { CursorImpl dup; if (cursorImpl.isNotInitialized()) { dup=cursorImpl; } else { dup=cursorImpl.cloneCursor(addCursor); } return dup; } /** * If the operation is successful, swaps cursors and closes the original * cursor; otherwise, closes the duped cursor. In the case where the * original cursor was not duped by beginRead because it was uninitialized, * just resets the original cursor if the operation did not succeed. */ private void endRead( CursorImpl dup, boolean success) throws DatabaseException { if (dup == cursorImpl) { if (!success) { cursorImpl.reset(); } } else { if (success) { cursorImpl.close(); cursorImpl=dup; } else { dup.close(); } } } boolean advanceCursor( DatabaseEntry key, DatabaseEntry data){ return cursorImpl.advanceCursor(key,data); } private LockType getLockType( LockMode lockMode, boolean rangeLock){ if (isReadUncommittedMode(lockMode)) { return LockType.NONE; } else if (lockMode == null || lockMode == LockMode.DEFAULT) { return rangeLock ? LockType.RANGE_READ : LockType.READ; } else if (lockMode == LockMode.RMW) { return rangeLock ? LockType.RANGE_WRITE : LockType.WRITE; } else if (lockMode == LockMode.READ_COMMITTED) { throw new IllegalArgumentException(lockMode.toString() + " not allowed with Cursor methods"); } else { assert false : lockMode; return LockType.NONE; } } /** * Returns whether the given lock mode will cause a read-uncommitted when * used with this cursor, taking into account the default cursor * configuration. */ boolean isReadUncommittedMode( LockMode lockMode){ return (lockMode == LockMode.READ_UNCOMMITTED || (readUncommittedDefault && (lockMode == null || lockMode == LockMode.DEFAULT))); } private boolean isSerializableIsolation( LockMode lockMode){ return serializableIsolationDefault && !isReadUncommittedMode(lockMode); } protected void checkUpdatesAllowed( String operation) throws DatabaseException { if (updateOperationsProhibited) { throw new DatabaseException("A transaction was not supplied when opening this cursor: " + operation); } } /** * Note that this flavor of checkArgs doesn't require that the dbt data is * set. */ private void checkArgsNoValRequired( DatabaseEntry key, DatabaseEntry data){ DatabaseUtil.checkForNullDbt(key,"key",false); DatabaseUtil.checkForNullDbt(data,"data",false); } /** * Note that this flavor of checkArgs requires that the dbt data is set. */ private void checkArgsValRequired( DatabaseEntry key, DatabaseEntry data){ DatabaseUtil.checkForNullDbt(key,"key",true); DatabaseUtil.checkForNullDbt(data,"data",true); } /** * Check the environment and cursor state. */ void checkState( boolean mustBeInitialized) throws DatabaseException { checkEnv(); cursorImpl.checkCursorState(mustBeInitialized); } /** * @throws RunRecoveryException if the underlying environment is invalid. */ void checkEnv() throws RunRecoveryException { cursorImpl.checkEnv(); } private void traceCursorImpl( StringBuffer sb){ sb.append(" locker=").append(cursorImpl.getLocker().getId()); if (cursorImpl.getBIN() != null) { sb.append(" bin=").append(cursorImpl.getBIN().getNodeId()); } sb.append(" idx=").append(cursorImpl.getIndex()); if (cursorImpl.getDupBIN() != null) { sb.append(" Dbin=").append(cursorImpl.getDupBIN().getNodeId()); } sb.append(" dupIdx=").append(cursorImpl.getDupIndex()); } protected void hook0() throws DatabaseException { } protected void hook1() throws DatabaseException { } protected void hook2( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook3( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook4( DatabaseEntry key, DatabaseEntry data) throws DatabaseException { } protected void hook5( DatabaseEntry data) throws DatabaseException { } protected void hook6( LockMode lockMode) throws DatabaseException { } protected void hook7( LockMode lockMode) throws DatabaseException { } protected void hook8( LockMode lockMode) throws DatabaseException { } protected void hook9( LockMode lockMode) throws DatabaseException { } protected void hook10( LockMode lockMode) throws DatabaseException { } protected void hook11( LockMode lockMode) throws DatabaseException { } protected void hook12( LockMode lockMode) throws DatabaseException { } protected void hook13( LockMode lockMode) throws DatabaseException { } protected void hook14( LockMode lockMode) throws DatabaseException { } protected void hook15( DatabaseEntry key, LockMode lockMode) throws DatabaseException { } protected void hook16( DatabaseEntry key, LockMode lockMode) throws DatabaseException { } protected void hook17( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { } protected void hook18( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { } protected void hook19( CursorImpl dup) throws DatabaseException { } protected void hook20( CursorImpl origCursor, CursorImpl dup) throws DatabaseException { } protected void hook21( CursorImpl origCursor) throws DatabaseException { } protected void hook22() throws DatabaseException { } protected void hook23() throws DatabaseException { } protected void hook24() throws DatabaseException { } protected void hook25( CursorImpl dup, DatabaseEntry key, DatabaseEntry data, LockType searchLockType, LockType advanceLockType, SearchMode searchMode, boolean advanceAfterRangeSearch, OperationStatus status, boolean keyChange) throws DatabaseException { int searchResult=dup.searchAndPosition(key,data,searchMode,searchLockType); if ((searchResult & CursorImpl.FOUND) != 0) { boolean exactKeyMatch=((searchResult & CursorImpl.EXACT_KEY) != 0); boolean exactDataMatch=((searchResult & CursorImpl.EXACT_DATA) != 0); boolean foundLast=((searchResult & CursorImpl.FOUND_LAST) != 0); boolean rangeMatch=false; if (searchMode == SearchMode.SET_RANGE && !exactKeyMatch) { rangeMatch=true; } if (searchMode == SearchMode.BOTH_RANGE && (!exactKeyMatch || !exactDataMatch)) { rangeMatch=true; } DatabaseEntry useKey=(searchMode == SearchMode.SET) ? null : key; if (rangeMatch || (status=dup.getCurrentAlreadyLatched(useKey,data,searchLockType,true)) == OperationStatus.KEYEMPTY) { if (foundLast) { status=OperationStatus.NOTFOUND; } else if (searchMode == SearchMode.SET) { status=dup.getNextDuplicate(key,data,advanceLockType,true,rangeMatch); } else if (searchMode == SearchMode.BOTH) { if (status == OperationStatus.KEYEMPTY) { status=OperationStatus.NOTFOUND; } } else { assert !searchMode.isExactSearch(); byte[] searchKey=null; if (searchMode.isDataSearch()) { searchKey=Key.makeKey(key); } if (exactKeyMatch) { KeyChangeStatus result=dup.getNextWithKeyChangeStatus(key,data,advanceLockType,true,rangeMatch); status=result.status; keyChange=searchMode.isDataSearch() ? (status == OperationStatus.SUCCESS) : result.keyChange; } else if (searchMode.isDataSearch() && !advanceAfterRangeSearch) { status=OperationStatus.NOTFOUND; } else { status=dup.getNextNoDup(key,data,advanceLockType,true,rangeMatch); keyChange=(status == OperationStatus.SUCCESS); } if (status == OperationStatus.SUCCESS && searchMode.isDataSearch()) { if (Key.compareKeys(key.getData(),searchKey,dbImpl.getDuplicateComparator()) != 0) { status=OperationStatus.NOTFOUND; } } } } } } protected void hook26() throws DatabaseException { } protected void hook27() throws DatabaseException { } protected void hook28() throws DatabaseException { } protected void hook29() throws DatabaseException { } protected void hook30() throws DatabaseException { } protected void hook31() throws DatabaseException { } protected void hook32( CursorImpl origCursor) throws DatabaseException { } protected void hook33( CursorImpl origCursor) throws DatabaseException { } protected void hook34( CursorImpl origCursor) throws DatabaseException { } protected void hook35( CursorImpl origCursor) throws DatabaseException { } protected void hook36( DatabaseImpl dbImpl) throws DatabaseException { } }