Package com.sleepycat.je.dbi

Source Code of com.sleepycat.je.dbi.CursorImpl$KeyChangeStatus

/*
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2010 Oracle.  All rights reserved.
*
*/

package com.sleepycat.je.dbi;

import java.util.logging.Level;

import com.sleepycat.je.CacheMode;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DuplicateDataException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.LockConflictException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.evictor.Evictor.EvictionSource;
import com.sleepycat.je.latch.LatchSupport;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.ReplicationContext;
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.LockGrantType;
import com.sleepycat.je.txn.LockResult;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.LockerFactory;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;

/**
* A CursorImpl is the internal implementation of the cursor.
*/
public class CursorImpl implements Cloneable {

    private static final boolean DEBUG = false;

    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:";

    /*
     * Cursor location in the databaseImpl, represented by a BIN and an index
     * in the BIN.  bin/index must have a non-null/non-negative value if dupBin
     * is set to non-null.
     */
    volatile private BIN bin;
    volatile private int index;

    /*
     * Cursor location in a given duplicate set.  If the cursor is not
     * referencing a duplicate set then these are null.
     */
    volatile private DBIN dupBin;
    volatile private int dupIndex;

    /*
     * BIN and DBIN that are no longer referenced by this cursor but have not
     * yet been removed.  If non-null, the BIN/DBIN will be removed soon.
     * BIN.adjustCursors should ignore cursors that are to be removed.
     */
    volatile private BIN binToBeRemoved;
    volatile private DBIN dupBinToBeRemoved;

    /*
     * The cursor location used for a given operation.
     */
    private BIN targetBin;
    private int targetIndex;
    private byte[] dupKey;

    /* The databaseImpl behind the handle. */
    private final DatabaseImpl databaseImpl;

    /* Owning transaction. */
    private Locker locker;
    private final boolean retainNonTxnLocks;

    /* State of the cursor. See CURSOR_XXX above. */
    private byte status;

    private CacheMode cacheMode;
    private boolean allowEviction;
    private TestHook testHook;

    /*
     * Unique id that we can return as a hashCode to prevent calls to
     * Object.hashCode(). [#13896]
     */
    private final int thisId;

    /*
     * Allocate hashCode ids from this. [#13896]
     */
    private static long lastAllocatedId = 0;

    private ThreadLocal<TreeWalkerStatsAccumulator> treeStatsAccumulatorTL;

    /**
     * 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 final boolean exactSearch;
        private final boolean dataSearch;
        private final 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;
        }

        @Override
        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) {
        this(database, locker, true);
    }

    /**
     * Creates a cursor.
     *
     * A cursor always retains transactional locks when it is reset or closed.
     * Non-transaction locks may be retained or not, depending on the
     * retainNonTxnLocks parameter value.
     *
     * Normally a user-created non-transactional Cursor releases locks on reset
     * and close, and a ThreadLocker is normally used.  However, by passing
     * true for retainNonTxnLocks a ThreadLocker can be made to retain locks;
     * this capability is used by SecondaryCursor.readPrimaryAfterGet.
     *
     * For internal (non-user) cursors, a BasicLocker is often used and locks
     * are retained.  BasicLocker does not currently support releasing locks
     * per cursor operation, so true must be passed for retainNonTxnLocks. In
     * addition, in these internal use cases the caller explicitly calls
     * BasicLocker.operationEnd, and retainNonTxnLocks is set to true to
     * prevent operationEnd from being called when the cursor is closed.
     *
     * BasicLocker is also used for NameLN operations while opening a Database
     * handle.  Database handle locks must be retained, even if the Database is
     * opened non-transactionally.
     *
     * @param retainNonTxnLocks is true if non-transactional locks should be
     * retained (not released automatically) when the cursor is reset or
     * closed.
     */
    public CursorImpl(DatabaseImpl databaseImpl,
                      Locker locker,
                      boolean retainNonTxnLocks) {

        thisId = (int) getNextCursorId();
        bin = null;
        index = -1;
        dupBin = null;
        dupIndex = -1;

        this.retainNonTxnLocks = retainNonTxnLocks;

        /* Associate this cursor with the databaseImpl. */
        this.databaseImpl = databaseImpl;
        this.locker = locker;
        this.locker.registerCursor(this);

        /*
         * This default value is used only when the CursorImpl is used directly
         * (mainly for internal databases).  When the CursorImpl is created by
         * a Cursor, CursorImpl.setCacheMode will be called.
         */
        this.cacheMode = CacheMode.DEFAULT;

        status = CURSOR_NOT_INITIALIZED;

        /*
         * Do not perform eviction here because we may be synchronized on the
         * Database instance. For example, this happens when we call
         * Database.openCursor().  Also eviction may be disabled after the
         * cursor is constructed.
         */
    }

    /*
     * Allocate a new hashCode id.  Doesn't need to be synchronized since it's
     * ok for two objects to have the same hashcode.
     */
    private static long getNextCursorId() {
        return ++lastAllocatedId;
    }

    @Override
    public int hashCode() {
        return thisId;
    }

    private void maybeInitTreeStatsAccumulator() {
        if (treeStatsAccumulatorTL == null) {
            treeStatsAccumulatorTL =
                new ThreadLocal<TreeWalkerStatsAccumulator>();
        }
    }

    private TreeWalkerStatsAccumulator getTreeStatsAccumulator() {
        if (EnvironmentImpl.getThreadLocalReferenceCount() > 0) {
            maybeInitTreeStatsAccumulator();
            return treeStatsAccumulatorTL.get();
        } else {
            return null;
        }
    }

    public void incrementLNCount() {
        TreeWalkerStatsAccumulator treeStatsAccumulator =
            getTreeStatsAccumulator();
        if (treeStatsAccumulator != null) {
            treeStatsAccumulator.incrementLNCount();
        }
    }

    /**
     * Disables or enables eviction during cursor operations.  For example, a
     * cursor used to implement eviction (e.g., in some UtilizationProfile and
     * most DbTree and VLSNIndex methods) should not itself perform eviction,
     * but eviction should be enabled for user cursors.  Eviction is disabled
     * by default.
     */
    public void setAllowEviction(boolean allowed) {
        allowEviction = allowed;
    }

    public void criticalEviction() {

        /*
         * In addition to disabling critical eviction for internal cursors (see
         * setAllowEviction above), we do not perform critical eviction when
         * EVICT_BIN or MAKE_COLD is used.  Operations using MAKE_COLD and
         * EVICT_BIN generally do not add any net memory to the cache, so they
         * shouldn't have to perform critical eviction or block while another
         * thread performs eviction.
         */
        if (allowEviction &&
            cacheMode != CacheMode.MAKE_COLD &&
            cacheMode != CacheMode.EVICT_BIN) {
            databaseImpl.getDbEnvironment().
                criticalEviction(false /*backgroundIO*/);
        }
    }

    /**
     * Shallow copy.  addCursor() is optionally called.
     */
    public CursorImpl cloneCursor(boolean addCursor, CacheMode cacheMode)
        throws DatabaseException {

        return cloneCursor(addCursor, cacheMode, null /*usePosition*/);
    }

    /**
     * Performs a shallow copy.
     *
     * @param addCursor If true, addCursor() is called to register the new
     * cursor with the BINs.  This is done after the usePosition parameter is
     * applied, if any.  There are two cases where you may not want addCursor()
     * to be called: 1) When creating a fresh uninitialized cursor as in when
     * Cursor.dup(false) is called, or 2) when the caller will call addCursor()
     * as part of a larger operation.
     *
     * @param usePosition Is null to duplicate the position of this cursor, or
     * non-null to duplicate the position of the given cursor.
     */
    public CursorImpl cloneCursor(final boolean addCursor,
                                  final CacheMode cacheMode,
                                  final CursorImpl usePosition)
        throws DatabaseException {

        CursorImpl ret = null;
        try {
            latchBINs();
            ret = (CursorImpl) super.clone();
            /* Must set cache mode before calling criticalEviction. */
            ret.setCacheMode(cacheMode);

            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 {
            releaseBINs();
        }

        /* Perform eviction before and after each cursor operation. */
        criticalEviction();

        return ret;
    }

    /**
     * Called when a cursor has been duplicated prior to being moved.  The new
     * locker is informed of the old locker, so that a preempted lock taken by
     * the old locker can be ignored. [#16513]
     *
     * @param closingCursor the old cursor that will be closed if the new
     * cursor is moved successfully.
     */
    public void setClosingLocker(CursorImpl closingCursor) {

        /*
         * If the two lockers are different, then the old locker will be closed
         * when the operation is complete.  This is currently the case only for
         * ReadCommitted cursors and non-transactional cursors that do not
         * retain locks.
         */
        if (!retainNonTxnLocks && locker != closingCursor.locker) {
            locker.setClosingLocker(closingCursor.locker);
        }
    }

    /**
     * Called when a cursor move operation is complete.  Clears the
     * closingLocker so that a reference to the old closed locker is not held.
     */
    public void clearClosingLocker() {
        locker.setClosingLocker(null);
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int idx) {
        index = idx;
    }

    public BIN getBIN() {
        return bin;
    }

    public void setBIN(BIN newBin) {
        /* Ensure that cursor was removed for the prior BIN. [#16280] */
        assert (bin == newBin) ||
               (bin == binToBeRemoved) ||
               (!bin.containsCursor(this));
        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) {
        /* Ensure that cursor was removed for the prior DBIN. [#16280] */
        assert (dupBin == newDupBin) ||
               (dupBin == dupBinToBeRemoved) ||
               (!dupBin.containsCursor(this));
        dupBin = newDupBin;
    }

    public DBIN getDupBINToBeRemoved() {
        return dupBinToBeRemoved;
    }

    public CacheMode getCacheMode() {
        return cacheMode;
    }

    /**
     * Sets the effective cache mode to use for the next operation.  The
     * cacheMode field will never be set to null or DYNAMIC, and can be passed
     * directly to latching methods.
     *
     * @see #performCacheEviction
     */
    public void setCacheMode(final CacheMode mode) {
        cacheMode = databaseImpl.getEffectiveCacheMode(mode);
    }

    public void setTreeStatsAccumulator(TreeWalkerStatsAccumulator tSA) {
        maybeInitTreeStatsAccumulator();
        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 key on return contains the key if available, or null.
     * @param data on 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 /* forward */,
                    false /* alreadyLatched */);
        } catch (DatabaseException ignored) {
            /* Klockwork - ok */
        }

        /*
         * If the position changed, regardless of an exception, then we believe
         * that we have advanced the cursor.
         */
        if (bin != oldBin ||
            dupBin != oldDupBin ||
            index != oldIndex ||
            dupIndex != oldDupIndex) {

            /*
             * Return the key and data from the BIN entries, if we were not
             * able to read it above.
             */
            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 {

        while (bin != null) {
            BIN waitingOn = bin;
            waitingOn.latch(cacheMode);
            if (bin == waitingOn) {
                return bin;
            }
            waitingOn.releaseLatch();
        }

        return null;
    }

    public void releaseBIN() {
        if (bin != null) {
            bin.releaseLatchIfOwner();
        }
    }

    public void latchBINs()
        throws DatabaseException {

        latchBIN();
        latchDBIN();
    }

    public void releaseBINs() {
        releaseBIN();
        releaseDBIN();
    }

    public DBIN latchDBIN()
        throws DatabaseException {

        while (dupBin != null) {
            BIN waitingOn = dupBin;
            waitingOn.latch(cacheMode);
            if (dupBin == waitingOn) {
                return dupBin;
            }
            waitingOn.releaseLatch();
        }
        return null;
    }

    public void releaseDBIN() {
        if (dupBin != null) {
            dupBin.releaseLatchIfOwner();
        }
    }

    public Locker getLocker() {
        return locker;
    }

    public void addCursor(BIN bin) {
        if (bin != null) {
            assert bin.isLatchOwnerForWrite();
            bin.addCursor(this);
        }
    }

    /**
     * Add to the current cursor. (For dups)
     */
    public void addCursor() {
        if (dupBin != null) {
            addCursor(dupBin);
        }
        if (bin != null) {
            addCursor(bin);
        }
    }

    /*
     * Update a cursor to refer to a new BIN or DBin following an insert.
     * Don't bother removing this cursor from the previous bin.  Cursor will do
     * that with a cursor swap thereby preventing latch deadlocks down here.
     */
    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);
            abin.releaseLatch();
        }
    }

    private void removeCursorDBIN()
        throws DatabaseException {

        DBIN abin = latchDBIN();
        if (abin != null) {
            abin.removeCursor(this);
            abin.releaseLatch();
        }
    }

    /**
     * Clear the reference to the dup tree, if any.
     */
    public void clearDupBIN(boolean alreadyLatched)
        throws DatabaseException {

        if (dupBin != null) {
            if (alreadyLatched) {
                dupBin.removeCursor(this);
                dupBin.releaseLatch();
            } else {
                removeCursorDBIN();
            }
            dupBin = null;
            dupIndex = -1;
        }
    }

    public void dumpTree() {
        databaseImpl.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);
    }

    public boolean isInternalDbCursor() {
        return databaseImpl.isInternalDb();
    }

    /**
     * Reset a cursor to an uninitialized state, but unlike close(), allow it
     * to be used further.
     */
    public void reset()
        throws DatabaseException {

        /* Must remove cursor before evicting BIN. */
        removeCursor();
        performCacheEviction(null /*newCursor*/);

        if (!retainNonTxnLocks) {
            locker.releaseNonTxnLocks();
        }

        bin = null;
        index = -1;
        dupBin = null;
        dupIndex = -1;

        status = CURSOR_NOT_INITIALIZED;

        /* Perform eviction before and after each cursor operation. */
        criticalEviction();
    }

    public void close()
        throws DatabaseException {

        close(null);
    }

    /**
     * Close a cursor.
     *
     * @param newCursor is another cursor that is kept open by the parent
     * Cursor object, or null if no other cursor is kept open.
     *
     * @throws DatabaseException if the cursor was previously closed.
     */
    public void close(final CursorImpl newCursor)
        throws DatabaseException {

        assert assertCursorState(false) : dumpToString(true);

        /* Must remove cursor before evicting BIN. */
        removeCursor();
        performCacheEviction(newCursor);

        locker.unRegisterCursor(this);

        if (!retainNonTxnLocks) {
            locker.nonTxnOperationEnd();
        }

        status = CURSOR_CLOSED;

        /* Perform eviction before and after each cursor operation. */
        criticalEviction();
    }

    /**
     * When multiple operations are performed, CacheMode-based eviction is
     * performed for a given operation at the end of the next operation, which
     * calls close() or reset() on the CursorImpl of the previous operation.
     * Eviction for the last operation (including when only one operation is
     * performed) also occurs during Cursor.close(), which calls
     * CursorImpl.close().
     *
     * By default, the CacheMode returned by DatabaseImpl.getCacheMode is used,
     * and the defaults specified by the user for the Database or Environment
     * are applied.  However, the default mode can be overridden by the user by
     * calling Cursor.setCacheMode, and the mode may be changed prior to each
     * operation, if desired.
     *
     * To implement a per-operation CacheMode, two CacheMode fields are
     * maintained.  Cursor.cacheMode is the mode to use for the next operation.
     * CursorImpl.cacheMode is the mode that was used for the previous
     * operation, and that is used for eviction when that CursorImpl is closed
     * or reset.
     */
    private void performCacheEviction(final CursorImpl newCursor) {
        EnvironmentImpl envImpl = databaseImpl.getDbEnvironment();
        if (cacheMode != CacheMode.EVICT_LN &&
            cacheMode != CacheMode.EVICT_BIN &&
            (cacheMode != CacheMode.MAKE_COLD ||
             !(envImpl.isCacheFull() || envImpl.wasCacheEverFull()))) {
            /* Eviction not configured, or for MAKE_COLD, cache never full. */
            return;
        }
        setTargetBin();
        if (targetBin == null) {
            /* Nothing to evict. */
            return;
        }
        final BIN nextBin;
        final int nextIndex;
        if (newCursor != null) {
            newCursor.setTargetBin();
            nextBin = newCursor.targetBin;
            nextIndex = newCursor.targetIndex;
        } else {
            nextBin = null;
            nextIndex = -1;
        }
        switch (cacheMode) {
        case EVICT_LN:
            /* Evict the LN if we've moved to a new record. */
            if (targetBin != nextBin || targetIndex != nextIndex) {
                evict();
            }
            break;
        case EVICT_BIN:
            if (targetBin == nextBin) {
                /* BIN has not changed, do not evict it.  May evict the LN. */
                if (targetIndex != nextIndex) {
                    evict();
                }
            } else {
                /* BIN has changed, evict it. */
                envImpl.getEvictor().evictIN
                    (targetBin, false /*backgroundIO*/,
                     EvictionSource.CACHEMODE);
            }
            break;
        case MAKE_COLD:
            if (targetBin == nextBin || !envImpl.isCacheFull()) {

                /*
                 * If BIN has not changed, do not evict it, but may still evict
                 * the LN. If cache not full, use LN eviction instead.
                 */
                if (targetIndex != nextIndex) {
                    evict();
                }
            } else {
                /* BIN has changed, evict it. */
                envImpl.getEvictor().evictIN
                    (targetBin, false /*backgroundIO*/,
                     EvictionSource.CACHEMODE);
            }
            break;
        default:
            assert false;
        }
    }

    public int count(LockType lockType)
        throws DatabaseException {

        assert assertCursorState(true) : dumpToString(true);

        if (!databaseImpl.getSortedDuplicates()) {
            return 1;
        }

        if (bin == null) {
            return 0;
        }

        latchBIN();
        try {
            if (bin.getNEntries() <= index) {
                return 0;
            }

            /* If fetchTarget returns null, a deleted LN was cleaned. */
            Node n = bin.fetchTarget(index);
            if (n != null && n.containsDuplicates()) {
                DIN dupRoot = (DIN) n;

                /* Latch couple down the tree. */
                dupRoot.latch(cacheMode);
                releaseBIN();
                DupCountLN dupCountLN = (DupCountLN)
                    dupRoot.getDupCountLNRef().fetchTarget(databaseImpl,
                                                           dupRoot);

                /* We can't hold latches when we acquire locks. */
                dupRoot.releaseLatch();

                /*
                 * Call lock directly.  There is no need to call lockLN because
                 * the node ID cannot change (a slot cannot be reused) for a
                 * DupCountLN.
                 */
                locker.lock
                    (dupCountLN.getNodeId(), lockType, false /*noWait*/,
                     databaseImpl);
                return dupCountLN.getDupCount();
            } else {
                /* If an LN is in the slot, the count is one. */
                return 1;
            }
        } finally {
            releaseBIN();
        }
    }

    /**
     * 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(ReplicationContext repContext)
        throws DatabaseException {

        assert assertCursorState(true) : dumpToString(true);
        boolean isDup = setTargetBin();

        /* If nothing at current position, return. */
        if (targetBin == null) {
            return OperationStatus.KEYEMPTY;
        }

        /*
         * Check if this is already deleted. We may know that the record is
         * deleted w/out seeing the LN.
         */
        if (targetBin.isEntryKnownDeleted(targetIndex)) {
            releaseBINs();
            return OperationStatus.KEYEMPTY;
        }

        /* If fetchTarget returns null, a deleted LN was cleaned. */
        LN ln = (LN) targetBin.fetchTarget(targetIndex);
        if (ln == null) {
            releaseBINs();
            return OperationStatus.KEYEMPTY;
        }

        /* Get a write lock. */
        LockResult lockResult = lockLN(ln, LockType.WRITE);
        ln = lockResult.getLN();

        /* Check LN deleted status under the protection of a write lock. */
        if (ln == null) {
            releaseBINs();
            return OperationStatus.KEYEMPTY;
        }

        /* Lock the DupCountLN before logging any LNs. */
        LockResult dclLockResult = null;
        DIN dupRoot = null;
        boolean dupRootIsLatched = false;
        try {
            isDup = (dupBin != null);
            if (isDup) {
                dupRoot = getLatchedDupRoot(true /*isDBINLatched*/);
                dclLockResult = lockDupCountLN(dupRoot, LockType.WRITE);
                /* Don't mark latched until after locked. */
                dupRootIsLatched = true;

                /*
                 * Refresh the dupRoot variable because it may have changed
                 * during locking, but is sure to be resident and latched by
                 * lockDupCountLN.
                 */
                dupRoot = (DIN) bin.getTarget(index);
                /* Release BIN to increase concurrency. */
                releaseBIN();
            }

            /*
             * Between the release of the BIN latch and acquiring the write
             * lock any number of operations may have executed which would
             * result in a new abort LSN for this record. Therefore, wait until
             * now to get the abort LSN.
             */
            setTargetBin();
            long oldLsn = targetBin.getLsn(targetIndex);
            byte[] lnKey = targetBin.getKey(targetIndex);
            lockResult.setAbortLsn
                (oldLsn, targetBin.isEntryKnownDeleted(targetIndex));

            /* Log the LN. */
            long oldLNSize = ln.getMemorySizeIncludedByParent();
            long newLsn = ln.delete(databaseImpl, lnKey,
                                    dupKey, oldLsn, locker,
                                    repContext);

            /*
             * Now update the parent of the LN (be it BIN or DBIN) to correctly
             * reference the LN and adjust the memory sizing.  Be sure to do
             * this update of the LSN before updating the dup count LN. In case
             * we encounter problems there we need the LSN to match the latest
             * version to ensure that undo works.
             */
            targetBin.updateNode
                (targetIndex, ln, oldLNSize, newLsn, null /*lnSlotKey*/);
            targetBin.setPendingDeleted(targetIndex);
            releaseBINs();

            if (isDup) {
                dupRoot.incrementDuplicateCount
                    (dclLockResult, dupKey, locker, false /*increment*/);
                dupRoot.releaseLatch();
                dupRootIsLatched = false;
                dupRoot = null;

                locker.addDeleteInfo(dupBin, new Key(lnKey));
            } else {
                locker.addDeleteInfo(bin, new Key(lnKey));
            }

            trace(Level.FINER, TRACE_DELETE, targetBin,
                  ln, targetIndex, oldLsn, newLsn);
        } finally {
            if (dupRoot != null &&
                dupRootIsLatched) {
                dupRoot.releaseLatch();
            }
        }

        return OperationStatus.SUCCESS;
    }

    /**
     * Return a new copy of the cursor.
     *
     * @param samePosition If true, position the returned cursor at the same
     * position as this cursor; if false, return a new uninitialized cursor.
     */
    public CursorImpl dup(boolean samePosition)
        throws DatabaseException {

        assert assertCursorState(false) : dumpToString(true);

        CursorImpl ret = cloneCursor(samePosition /*addCursor*/, cacheMode);

        if (!samePosition) {
            ret.bin = null;
            ret.index = -1;
            ret.dupBin = null;
            ret.dupIndex = -1;

            ret.status = CURSOR_NOT_INITIALIZED;
        }

        return ret;
    }

    /**
     * Evict the LN node at the cursor position.
     */
    public void evict()
        throws DatabaseException {

        evict(false); // alreadyLatched
    }

    /**
     * Evict the LN node at the cursor position.
     */
    public void evict(boolean alreadyLatched)
        throws DatabaseException {

        try {
            if (!alreadyLatched) {
                latchBINs();
            }
            setTargetBin();
            if (targetIndex >= 0) {
                targetBin.evictLN(targetIndex);
            }
        } finally {
            if (!alreadyLatched) {
                releaseBINs();
            }
        }
    }

    /*
     * Puts
     */

    /**
     * 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 databaseImpl.
     */
    public void lockNextKeyForInsert(DatabaseEntry key, DatabaseEntry data)
        throws DatabaseException {

        DatabaseEntry tempKey = new DatabaseEntry
            (key.getData(), key.getOffset(), key.getSize());
        DatabaseEntry tempData = new DatabaseEntry
            (data.getData(), data.getOffset(), data.getSize());
        tempKey.setPartial(0, 0, true);
        tempData.setPartial(0, 0, true);
        boolean lockedNextKey = false;

        /* Don't search for data if duplicates are not configured. */
        SearchMode searchMode = databaseImpl.getSortedDuplicates() ?
            SearchMode.BOTH_RANGE : SearchMode.SET_RANGE;
        boolean latched = true;
        try {
            /* Search. */
            int searchResult = searchAndPosition
                (tempKey, tempData, searchMode, LockType.RANGE_INSERT);
            if ((searchResult & FOUND) != 0 &&
                (searchResult & FOUND_LAST) == 0) {

                /*
                 * If searchAndPosition found a record (other than the last
                 * one), in all cases we should advance to the next record:
                 *
                 * 1- found a deleted record,
                 * 2- found an exact match, or
                 * 3- found the record prior to the given key/data.
                 *
                 * If we didn't match the key, skip over duplicates to the next
                 * key with getNextNoDup.
                 */
                OperationStatus status;
                if ((searchResult & EXACT_KEY) != 0) {
                    status = getNext
                        (tempKey, tempData, LockType.RANGE_INSERT, true, true);
                } else {
                    status = getNextNoDup
                        (tempKey, tempData, LockType.RANGE_INSERT, true, true);
                }
                if (status == OperationStatus.SUCCESS) {
                    lockedNextKey = true;
                }
                latched = false;
            }
        } finally {
            if (latched) {
                releaseBINs();
            }
        }

        /* Lock the EOF node if no next key was found. */
        if (!lockedNextKey) {
            lockEofNode(LockType.RANGE_INSERT);
        }
    }

    /**
     * Performs all put operations except for CURRENT (use putCurrent instead).
     */
    public OperationStatus put(final DatabaseEntry key,
                               final DatabaseEntry data,
                               final LN ln,
                               final PutMode putMode,
                               final DatabaseEntry returnOldData,
                               final DatabaseEntry returnNewData,
                               final ReplicationContext repContext) {
        assert key != null;
        assert data != null;
        assert ln != null;
        assert putMode != null;
        assert assertCursorState(false) : dumpToString(true);
        assert LatchSupport.countLatchesHeld() == 0;

        final boolean allowDups;
        final boolean allowOverwrite;
        final boolean overwriteKnownNodeId;

        switch (putMode) {
        case NO_OVERWRITE:
            allowDups = false;
            allowOverwrite = false;
            overwriteKnownNodeId = false;
            break;
        case NO_DUP_DATA:
            assert databaseImpl.getSortedDuplicates();
            allowDups = true;
            allowOverwrite = false;
            overwriteKnownNodeId = false;
            break;
        case OVERWRITE:
            allowDups = databaseImpl.getSortedDuplicates();
            allowOverwrite = true;
            overwriteKnownNodeId = false;
            break;
        case OVERWRITE_KNOWN:
            allowDups = databaseImpl.getSortedDuplicates();
            allowOverwrite = true;
            overwriteKnownNodeId = true;
            break;
        default:
            throw EnvironmentFailureException.unexpectedState
                (putMode.toString());
        }

        LockResult lockResult = locker.lock
            (ln.getNodeId(), LockType.WRITE, false /*noWait*/, databaseImpl);

        /*
         * We'll set abortLsn down in Tree.insert when we know whether we're
         * re-using a BIN entry or not.
         */
        if (databaseImpl.getTree().insert(ln, Key.makeKey(key), allowDups,
                                          this, lockResult, repContext)) {
            status = CURSOR_INITIALIZED;
            /* Return a copy of resulting data, if requested. [#16932] */
            if (returnNewData != null) {
                returnNewData.setData(ln.copyData());
            }
            return OperationStatus.SUCCESS;
        } else {
            final long checkNodeId;
            if (overwriteKnownNodeId) {
                checkNodeId = ln.getNodeId();
            } else {
                checkNodeId = Node.NULL_NODE_ID;
                locker.releaseLock(ln.getNodeId());
            }
            if (allowOverwrite) {
                status = CURSOR_INITIALIZED;

                /*
                 * In addition to overwriting the existing value, putCurrent
                 * will get a write lock on the existing node.  We already have
                 * a write lock only when using OVERWRITE_KNOWN; in other cases
                 * we released the lock on the new LN.
                 */
                return putCurrent(data, null /*foundKey*/, returnOldData,
                                  returnNewData, checkNodeId, repContext);
            } else {
                return OperationStatus.KEYEXIST;
            }
        }
    }

    /**
     * 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>
     *
     * @param returnNewData if non-null, is used to return a complete copy of
     * the resulting data after any partial data has been resolved.
     */
    public OperationStatus putLN(byte[] key,
                                 LN ln,
                                 DatabaseEntry returnNewData,
                                 boolean allowDuplicates,
                                 ReplicationContext repContext)
        throws DatabaseException {

        assert assertCursorState(false) : dumpToString(true);

        assert LatchSupport.countLatchesHeld() == 0;
        LockResult lockResult = locker.lock
            (ln.getNodeId(), LockType.WRITE, false /*noWait*/, databaseImpl);

        /*
         * We'll set abortLsn down in Tree.insert when we know whether we're
         * re-using a BIN entry or not.
         */
        if (databaseImpl.getTree().insert
            (ln, key, allowDuplicates, this, lockResult, repContext)) {
            status = CURSOR_INITIALIZED;
            /* Return a copy of resulting data, if requested. [#16932] */
            if (returnNewData != null) {
                returnNewData.setData(ln.copyData());
            }
            return OperationStatus.SUCCESS;
        } else {
            locker.releaseLock(ln.getNodeId());
            return OperationStatus.KEYEXIST;
        }
    }

    /**
     * Modify the current record with this data.
     *
     * @param data to overwrite, may be partial.
     *
     * @param foundKey if non-null, is used to return a copy of the existing
     * key, may be partial.
     *
     * @param foundData if non-null, is used to return a copy of the existing
     * data, may be partial.
     *
     * @param returnNewData if non-null, is used to return a complete copy of
     * the resulting data after any partial data has been resolved.
     *
     * @param checkNodeId if not Node.NULL_NODE_ID, is the node ID to be
     * overwritten.  If the given node ID is not equal to the node ID at the
     * current position, an EnvironmentFailureExcepion UNEXPECTED_STATE is
     * thrown.
     */
    public OperationStatus putCurrent(DatabaseEntry data,
                                      DatabaseEntry foundKey,
                                      DatabaseEntry foundData,
                                      DatabaseEntry returnNewData,
                                      long checkNodeId,
                                      ReplicationContext repContext)
        throws DatabaseException {

        assert assertCursorState(true) : dumpToString(true);

        if (foundKey != null) {
            foundKey.setData(null);
        }
        if (foundData != null) {
            foundData.setData(null);
        }
        if (returnNewData != null) {
            returnNewData.setData(null);
        }

        if (bin == null) {
            return OperationStatus.KEYEMPTY;
        }

        latchBINs();
        boolean isDup = setTargetBin();

        /* Variables for tracing. */
        final long oldLsn;
        final long newLsn;
        LN ln;

        try {

            /*
             * Find the existing entry and get a reference to all BIN fields
             * while latched.
             */
            ln = (LN) targetBin.fetchTarget(targetIndex);
            byte[] lnKey = targetBin.getKey(targetIndex);

            /* If fetchTarget returned null, a deleted LN was cleaned. */
            if (targetBin.isEntryKnownDeleted(targetIndex) ||
                ln == null) {
                return OperationStatus.NOTFOUND;
            }

            /* Get a write lock. */
            LockResult lockResult = lockLN(ln, LockType.WRITE);
            ln = lockResult.getLN();

            /* Check LN deleted status under the protection of a write lock. */
            if (ln == null) {
                return OperationStatus.NOTFOUND;
            }

            if (checkNodeId != Node.NULL_NODE_ID &&
                checkNodeId != ln.getNodeId()) {
                EnvironmentFailureException.unexpectedState
                    ("Overwrite node ID expected = " + checkNodeId +
                     " but in Btree = " + ln.getNodeId());
            }

            /*
             * If cursor points at a dup, then we can only replace the entry
             * with a new entry that is "equal" to the old one.  Since a user
             * defined comparison function may actually compare equal for two
             * byte sequences that are actually different we still have to do
             * the replace.  Arguably we could skip the replacement if there is
             * no user defined comparison function and the new data is the
             * same.
             */
            byte[] foundDataBytes;
            byte[] foundKeyBytes;
            isDup = setTargetBin();
            if (isDup) {
                foundDataBytes = lnKey;
                foundKeyBytes = targetBin.getDupKey();
            } else {
                foundDataBytes = ln.getData();
                foundKeyBytes = lnKey;
            }
            byte[] newData;

            /* Resolve partial puts. */
            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;

                /*
                 * Keep 0..doff of the old data (truncating if doff > length).
                 */
                int slicelen = (doff < origlen) ? doff : origlen;
                if (slicelen > 0) {
                    System.arraycopy(foundDataBytes, 0, newData,
                                     pos, slicelen);
                }
                pos += doff;

                /* Copy in the new data. */
                slicelen = data.getSize();
                System.arraycopy(data.getData(), data.getOffset(),
                                 newData, pos, slicelen);
                pos += slicelen;

                /* Append the rest of the old data (if any). */
                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 (databaseImpl.getSortedDuplicates()) {
                /* Check that data compares equal before replacing it. */
                boolean keysEqual = false;
                if (foundDataBytes != null) {
                    keysEqual = Key.compareKeys
                        (foundDataBytes, newData,
                         databaseImpl.getDuplicateComparator()) == 0;

                }

                if (!keysEqual) {
                    revertLock(ln, lockResult);
                    throw new DuplicateDataException
                        ("Can't replace a duplicate with data that is " +
                         "unequal according to the duplicate comparator.");
                }
            }

            if (foundData != null) {
                setDbt(foundData, foundDataBytes);
            }
            if (foundKey != null) {
                setDbt(foundKey, foundKeyBytes);
            }

            /*
             * Between the release of the BIN latch and acquiring the write
             * lock any number of operations may have executed which would
             * result in a new abort LSN for this record. Therefore, wait until
             * now to get the abort LSN.
             */
            oldLsn = targetBin.getLsn(targetIndex);
            lockResult.setAbortLsn
                (oldLsn, targetBin.isEntryKnownDeleted(targetIndex));

            /*
             * The modify has to be inside the latch so that the BIN is updated
             * inside the latch.
             */
            long oldLNSize = ln.getMemorySizeIncludedByParent();
            byte[] newKey = (isDup ? targetBin.getDupKey() : lnKey);
            newLsn = ln.modify(newData, databaseImpl, newKey, oldLsn, locker,
                               repContext);

            /* Return a copy of resulting data, if requested. [#16932] */
            if (returnNewData != null) {
                returnNewData.setData(ln.copyData());
            }

            /*
             * Update the parent BIN.  Update the data-as-key, if changed, for
             * a DBIN. [#15704]
             */
            targetBin.updateNode
                (targetIndex, ln, oldLNSize, newLsn, isDup ? newData : null);
        } finally {
            releaseBINs();
        }

        /* Trace after releasing latches. */
        trace(Level.FINER, TRACE_MOD, targetBin, ln, targetIndex, oldLsn,
              newLsn);

        status = CURSOR_INITIALIZED;
        return OperationStatus.SUCCESS;
    }

    /*
     * Gets
     */

    /**
     * Retrieve the current record.
     */
    public OperationStatus getCurrent(DatabaseEntry foundKey,
                                      DatabaseEntry foundData,
                                      LockType lockType)
        throws DatabaseException {

        assert assertCursorState(true) : dumpToString(true);

        // If not pointing at valid entry, return failure
        if (bin == null) {
            return OperationStatus.KEYEMPTY;
        }

        if (dupBin == null) {
            latchBIN();
        } else {
            latchDBIN();
        }

        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 {

        assert assertCursorState(true) : dumpToString(true);
        assert checkAlreadyLatched(true) : dumpToString(true);

        try {
            return fetchCurrent(foundKey, foundData, lockType, first);
        } finally {
            releaseBINs();
        }
    }

    /**
     * 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 {
            latchBIN();
            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 {
            assert assertCursorState(true) : dumpToString(true);
            assert checkAlreadyLatched(true) : dumpToString(true);

            if (bin == null) {
                return null;
            }

            /*
             * Get a reference to the LN under the latch.  Check the deleted
             * flag in the BIN.  If fetchTarget returns null, a deleted LN was
             * cleaned.
             */
            LN ln = null;
            if (!bin.isEntryKnownDeleted(index)) {
                ln = (LN) bin.fetchTarget(index);
            }
            if (ln == null) {
                releaseBIN();
                return null;
            }

            addCursor(bin);

            /* Lock LN.  */
            LockResult lockResult = lockLN(ln, lockType);
            ln = lockResult.getLN();

            /* Don't set abort LSN for a read operation! */
            return ln;

        } finally {
            releaseBINs();
        }
    }

    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 foundKey DatabaseEntry to use for returning key
     *
     * @param foundData DatabaseEntry to use for returning data
     *
     * @param forward if true, move forward, else move backwards
     *
     * @param alreadyLatched if 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);
        assert checkAlreadyLatched(alreadyLatched) : dumpToString(true);

        KeyChangeStatus result =
            new KeyChangeStatus(OperationStatus.NOTFOUND, true);

        try {
            while (bin != null) {

                /* Are we positioned on a DBIN? */
                if (dupBin != null) {
                    if (DEBUG) {
                        verifyCursor(dupBin);
                    }
                    if (getNextDuplicate(foundKey, foundData, lockType,
                                         forward, alreadyLatched) ==
                        OperationStatus.SUCCESS) {
                        result.status = OperationStatus.SUCCESS;
                        /* We returned a duplicate. */
                        result.keyChange = false;
                        break;
                    } else {
                        removeCursorDBIN();
                        alreadyLatched = false;
                        dupBin = null;
                        dupIndex = -1;
                        continue;
                    }
                }

                assert checkAlreadyLatched(alreadyLatched) :
                    dumpToString(true);
                if (!alreadyLatched) {
                    latchBIN();
                } else {
                    alreadyLatched = false;
                }

                if (DEBUG) {
                    verifyCursor(bin);
                }

                /* Is there anything left on this BIN? */
                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 {
                        assert LatchSupport.countLatchesHeld() == 0;

                        if (binToBeRemoved != null) {
                            flushBINToBeRemoved();
                        }

                        continue;
                    }

                } else {

                    /*
                     * binToBeRemoved is used to release a BIN earlier in the
                     * traversal chain when we move onto the next BIN. When
                     * we traverse across BINs, there is a point when two BINs
                     * point to the same cursor.
                     *
                     * Example:  BINa(empty) BINb(empty) BINc(populated)
                     *           Cursor (C) is traversing
                     * loop, leaving BINa:
                     *   binToBeRemoved is null, C points to BINa, and
                     *     BINa points to C
                     *   set binToBeRemoved to BINa
                     *   find BINb, make BINb point to C
                     *   note that BINa and BINb point to C.
                     * loop, leaving BINb:
                     *   binToBeRemoved == BINa, remove C from BINa
                     *   set binToBeRemoved to BINb
                     *   find BINc, make BINc point to C
                     *   note that BINb and BINc point to C
                     * finally, when leaving this method, remove C from BINb.
                     */
                    if (binToBeRemoved != null) {
                        releaseBIN();
                        flushBINToBeRemoved();
                        latchBIN();
                    }
                    binToBeRemoved = bin;
                    bin = null;

                    BIN newBin;

                    /*
                     * SR #12736
                     * Prune away oldBin. Assert has intentional side effect
                     */
                    assert TestHookExecute.doHookIfSet(testHook);

                    if (forward) {
                        newBin = databaseImpl.getTree().getNextBin
                            (binToBeRemoved,
                             false /*traverseWithinDupTree*/,
                             cacheMode);
                    } else {
                        newBin = databaseImpl.getTree().getPrevBin
                            (binToBeRemoved,
                             false /*traverseWithinDupTree*/,
                             cacheMode);
                    }
                    if (newBin == null) {
                        result.status = OperationStatus.NOTFOUND;
                        break;
                    } else {
                        if (forward) {
                            index = -1;
                        } else {
                            index = newBin.getNEntries();
                        }
                        addCursor(newBin);
                        /* Ensure that setting bin is under newBin's latch */
                        bin = newBin;
                        alreadyLatched = true;
                    }
                }
            }
        } finally {
            assert LatchSupport.countLatchesHeld() == 0 :
                LatchSupport.latchesHeldToString();
            if (binToBeRemoved != null) {
                flushBINToBeRemoved();
            }
        }
        return result;
    }

    private void flushBINToBeRemoved()
        throws DatabaseException {

        binToBeRemoved.latch(cacheMode);
        binToBeRemoved.removeCursor(this);
        binToBeRemoved.releaseLatch();
        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);

        /*
         * By clearing the dupBin, the next call to fetchCurrent will move to
         * the first duplicate.
         */
        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 {

        assert assertCursorState(true) : dumpToString(true);
        assert checkAlreadyLatched(alreadyLatched) : dumpToString(true);
        try {
            while (dupBin != null) {
                if (!alreadyLatched) {
                    latchDBIN();
                } else {
                    alreadyLatched = false;
                }

                if (DEBUG) {
                    verifyCursor(dupBin);
                }

                /* Are we still on this DBIN? */
                if ((forward && ++dupIndex < dupBin.getNEntries()) ||
                    (!forward && --dupIndex > -1)) {

                    OperationStatus ret = OperationStatus.SUCCESS;
                    if (foundKey != null) {
                        ret = getCurrentAlreadyLatched(foundKey, foundData,
                                                       lockType, forward);
                    } else {
                        releaseDBIN();
                    }
                    if (ret == OperationStatus.SUCCESS) {
                        incrementLNCount();
                        return ret;
                    } else {
                        assert LatchSupport.countLatchesHeld() == 0;

                        if (dupBinToBeRemoved != null) {
                            flushDBINToBeRemoved();
                        }

                        continue;
                    }

                } else {

                    /*
                     * We need to go to the next DBIN.  Remove the cursor and
                     * be sure to change the dupBin field after removing the
                     * cursor.
                     */
                    if (dupBinToBeRemoved != null) {
                        flushDBINToBeRemoved();
                    }
                    dupBinToBeRemoved = dupBin;

                    dupBin = null;
                    dupBinToBeRemoved.releaseLatch();

                    TreeWalkerStatsAccumulator treeStatsAccumulator =
                        getTreeStatsAccumulator();
                    if (treeStatsAccumulator != null) {
                        latchBIN();
                        try {
                            if (index < 0) {
                                /* This duplicate tree has been deleted. */
                                return OperationStatus.NOTFOUND;
                            }

                            DIN duplicateRoot = (DIN) bin.fetchTarget(index);
                            duplicateRoot.latch(cacheMode);
                            try {
                                DupCountLN dcl = duplicateRoot.getDupCountLN();
                                if (dcl != null) {
                                    dcl.accumulateStats(treeStatsAccumulator);
                                }
                            } finally {
                                duplicateRoot.releaseLatch();
                            }
                        } finally {
                            releaseBIN();
                        }
                    }
                    assert (LatchSupport.countLatchesHeld() == 0);

                    dupBinToBeRemoved.latch(cacheMode);
                    DBIN newDupBin;

                    if (forward) {
                        newDupBin = (DBIN) databaseImpl.getTree().getNextBin
                            (dupBinToBeRemoved,
                             true /*traverseWithinDupTree*/,
                             cacheMode);
                    } else {
                        newDupBin = (DBIN) databaseImpl.getTree().getPrevBin
                            (dupBinToBeRemoved,
                             true /*traverseWithinDupTree*/,
                             cacheMode);
                    }

                    if (newDupBin == null) {
                        return OperationStatus.NOTFOUND;
                    } else {
                        if (forward) {
                            dupIndex = -1;
                        } else {
                            dupIndex = newDupBin.getNEntries();
                        }
                        addCursor(newDupBin);

                        /*
                         * Ensure that setting dupBin is under newDupBin's
                         * latch.
                         */
                        dupBin = newDupBin;
                        alreadyLatched = true;
                    }
                }
            }
        } finally {
            assert LatchSupport.countLatchesHeld() == 0;
            if (dupBinToBeRemoved != null) {
                flushDBINToBeRemoved();
            }
        }

        return OperationStatus.NOTFOUND;
    }

    private void flushDBINToBeRemoved()
        throws DatabaseException {

        dupBinToBeRemoved.latch(cacheMode);
        dupBinToBeRemoved.removeCursor(this);
        dupBinToBeRemoved.releaseLatch();
        dupBinToBeRemoved = null;
    }

    /**
     * Position the cursor at the first or last record of the databaseImpl.
     * 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 {

        assert assertCursorState(false) : dumpToString(true);

        IN in = null;
        boolean found = false;
        try {
            if (duplicateRoot == null) {
                removeCursorBIN();
                if (first) {
                    in = databaseImpl.getTree().getFirstNode(cacheMode);
                } else {
                    in = databaseImpl.getTree().getLastNode(cacheMode);
                }

                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) {

                        /*
                         * An IN was found. Even if it's empty, let Cursor
                         * handle moving to the first non-deleted entry.
                         */
                        found = true;
                    } else {

                        /*
                         * See if we need to descend further.  If fetchTarget
                         * returns null, a deleted LN was cleaned.
                         *
                         * We do not need to fetch a known deleted entry.  We
                         * do need to fetch to determine if this is a dup
                         * database into which we will descend further.
                         */
                        Node n = null;
                        if (!bin.isEntryKnownDeleted(index) &&
                            duplicateRoot == null &&
                            databaseImpl.getSortedDuplicates()) {
                            n = bin.fetchTarget(index);
                        }

                        if (n != null && n.containsDuplicates()) {
                            DIN dupRoot = (DIN) n;
                            dupRoot.latch(cacheMode);
                            in.releaseLatch();
                            in = null;
                            found = positionFirstOrLast(first, dupRoot);
                        } else {

                            /*
                             * Even if the entry is deleted, just leave our
                             * position here and return.
                             */
                            if (treeStatsAccumulator != null) {
                                if (bin.isEntryKnownDeleted(index) ||
                                    bin.isEntryPendingDeleted(index) ||
                                    (n != null && ((LN) n).isDeleted())) {
                                    treeStatsAccumulator.
                                        incrementDeletedLNCount();
                                } else {
                                    treeStatsAccumulator.
                                        incrementLNCount();
                                }
                            }
                            found = true;
                        }
                    }
                }
            } else {
                removeCursorDBIN();
                if (first) {
                    in = databaseImpl.getTree().
                        getFirstNode(duplicateRoot, cacheMode);
                } else {
                    in = databaseImpl.getTree().
                        getLastNode(duplicateRoot, cacheMode);
                }

                if (in != null) {

                    /*
                     * An IN was found. Even if it's empty, let Cursor handle
                     * moving to the first non-deleted entry.
                     */
                    /*
                     * assert (in instanceof DBIN);
                     * Will always be true since Tree.getFirst/LastNode always
                     * returns a DBIN.
                     */

                    dupBin = (DBIN) in;
                    dupIndex = (first ? 0 : (dupBin.getNEntries() - 1));
                    addCursor(dupBin);
                    found = true;
                }
            }
            status = CURSOR_INITIALIZED;
            return found;
        } catch (DatabaseException e) {
            /* Release latch on error. */
            if (in != null) {
                in.releaseLatch();
            }
            throw e;
        }
    }

    public static final int FOUND = 0x1;
    /* Exact match on the key portion. */
    public static final int EXACT_KEY = 0x2;
    /* Exact match on the DATA portion when searchAndPositionBoth used. */
    public static final int EXACT_DATA = 0x4;
    /* Record found is the last one in the databaseImpl. */
    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 databaseImpl.  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 {

        assert assertCursorState(false) : dumpToString(true);

        removeCursor();

        /* Reset the cursor. */
        bin = null;
        dupBin = null;
        dupIndex = -1;

        boolean foundSomething = false;
        boolean foundExactKey = false;
        boolean foundExactData = false;
        boolean foundLast = false;
        boolean exactSearch = searchMode.isExactSearch();
        BINBoundary binBoundary = new BINBoundary();

        try {
            byte[] key = Key.makeKey(matchKey);
            bin = (BIN) databaseImpl.getTree().search
                (key, Tree.SearchType.NORMAL, -1, binBoundary, cacheMode);

            if (bin != null) {
                addCursor(bin);

                /*
                 * If we're doing an exact search, tell bin.findEntry we
                 * require an exact match. If it's a range search, we don't
                 * need that exact match.
                 */
                index = bin.findEntry(key, true, exactSearch);

                /*
                 * If we're doing an exact search, as a starting point, we'll
                 * assume that we haven't found anything. If this is a range
                 * search, we'll assume the opposite, that we have found a
                 * record. That's because for a range search, the higher level
                 * will take care of sorting out whether anything is really
                 * there or not.
                 */
                foundSomething = !exactSearch;
                boolean containsDuplicates = false;

                if (index >= 0) {
                    if ((index & IN.EXACT_MATCH) != 0) {

                        /*
                         * The binary search told us we had an exact match.
                         * Note that this really only tells us that the key
                         * matched. The underlying LN may be deleted or the
                         * reference may be knownDeleted, or maybe there's a
                         * dup tree w/no entries, but the next layer up will
                         * find these cases.
                         */
                        foundExactKey = true;

                        /*
                         * Now turn off the exact match bit so the index will
                         * be a valid value, before we use it to retrieve the
                         * child reference from the bin.
                         */
                        index &= ~IN.EXACT_MATCH;
                    }

                    if (bin.isEntryKnownDeleted(index)) {
                        /* Do not fetch. */
                    } else if (!searchMode.isDataSearch() &&
                               lockType == LockType.NONE &&
                               !databaseImpl.getSortedDuplicates()) {

                        /*
                         * No need to fetch if we don't need the data for
                         * searching, don't need to lock, and will not descend
                         * into a dup tree.
                         */
                        if (!bin.isEntryKnownDeleted(index)) {
                            foundSomething = true;
                        }
                    } else {

                        /*
                         * If fetchTarget returns null, a deleted LN was
                         * cleaned.
                         */
                        Node n = bin.fetchTarget(index);
                        if (n != null) {
                            containsDuplicates = n.containsDuplicates();
                            if (searchMode.isDataSearch()) {
                                if (foundExactKey) {
                                    /* If the key matches, try the data. */
                                    int searchResult = searchAndPositionBoth
                                        (containsDuplicates, n, matchData,
                                         exactSearch, lockType);
                                    foundSomething =
                                        (searchResult & FOUND) != 0;
                                    foundExactData =
                                        (searchResult & EXACT_DATA) != 0;
                                }
                            } else {
                                foundSomething = true;
                                if (!containsDuplicates && exactSearch) {
                                    /* Lock LN, check if deleted. */
                                    LN ln = (LN) n;
                                    LockResult lockResult =
                                        lockLN(ln, lockType);
                                    ln = lockResult.getLN();

                                    if (ln == null) {
                                        foundSomething = false;
                                    }

                                    /*
                                     * Note that we must not set the abort LSN
                                     * for a read operation, lest false
                                     * obsoletes are set. [13158]
                                     */
                                }
                            }
                        }
                    }

                    /*
                     * Determine whether the last record was found.  This is
                     * only possible when we don't lock the record, and when
                     * there are no duplicates.
                     */
                    foundLast = (searchMode == SearchMode.SET_RANGE &&
                                 foundSomething &&
                                 !containsDuplicates &&
                                 binBoundary.isLastBin &&
                                 index == bin.getNEntries() - 1);
                }
            }
            status = CURSOR_INITIALIZED;

            /* Return a multi-part status value */
            return (foundSomething ? FOUND : 0) |
                (foundExactKey ? EXACT_KEY : 0) |
                (foundExactData ? EXACT_DATA : 0) |
                (foundLast ? FOUND_LAST : 0);
        } catch (DatabaseException e) {
            /* Release latch on error. */
            releaseBIN();
            throw e;
        }
    }

    /**
     * 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)
        throws DatabaseException {

        assert assertCursorState(false) : dumpToString(true);

        boolean found = false;
        boolean exact = false;
        assert (matchData != null);
        byte[] data = Key.makeKey(matchData);

        if (containsDuplicates) {
            /* It's a duplicate tree. */
            DIN duplicateRoot = (DIN) n;
            duplicateRoot.latch(cacheMode);
            releaseBIN();
            dupBin = (DBIN) databaseImpl.getTree().searchSubTree
                (duplicateRoot, data, Tree.SearchType.NORMAL,
                 -1, null, cacheMode);
            if (dupBin != null) {
                /* Find an exact match. */
                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 {

                    /*
                     * The first duplicate is greater than the target data.
                     * Set index so that a 'next' operation moves to the first
                     * duplicate.
                     */
                    dupIndex = -1;
                    found = !exactSearch;
                }
            }
        } else {
            /* Not a duplicate, but checking for both key and data match. */
            LN ln = (LN) n;

            /* Lock LN, check if deleted. */
            LockResult lockResult = lockLN(ln, lockType);

            /*
             * Note that during the lockLN call, this cursor may have been
             * adjusted to refer to an LN in a duplicate tree.  This happens in
             * the case where we entered with a non-duplicate tree LN and
             * during the lock call it was mutated to a duplicate tree.  The LN
             * is still the correct LN, but our cursor is now down in a
             * duplicate tree. [#14230].
             */

            ln = lockResult.getLN();

            if (ln == null) {
                found = !exactSearch;
            } else {

                /* Don't set abort LSN for read operation. [#13158] */

                /*
                 * The comparison logic below mimics IN.findEntry as used above
                 * for duplicates.
                 */
                int cmp = Key.compareKeys
                    (ln.getData(), data, databaseImpl.getDuplicateComparator());
                if (cmp == 0 || (cmp <= 0 && !exactSearch)) {
                    if (cmp == 0) {
                        exact = true;
                    }
                    found = true;
                } else {

                    /*
                     * The current record's data is greater than the target
                     * data.  Set index so that a 'next' operation moves to the
                     * current record.
                     */
                    if (dupBin == null) {
                        index--;
                    } else {
                        /* We may now be pointing at a dup tree. [#14230]. */
                        dupIndex--;
                    }
                    found = !exactSearch;
                }
            }
        }

        return (found ? FOUND : 0) |
            (exact ? EXACT_DATA : 0);
    }

    /*
     * Lock and copy current record into the key and data DatabaseEntry. Enter
     * with the BIN/DBIN latched.
     */
    private OperationStatus fetchCurrent(DatabaseEntry foundKey,
                                         DatabaseEntry foundData,
                                         LockType lockType,
                                         boolean first)
        throws DatabaseException {

        TreeWalkerStatsAccumulator treeStatsAccumulator =
            getTreeStatsAccumulator();

        boolean duplicateFetch = setTargetBin();
        if (targetBin == null) {
            return OperationStatus.NOTFOUND;
        }

        assert targetBin.isLatchOwnerForWrite();

        /*
         * Check the deleted flag in the BIN and make sure this isn't an empty
         * BIN.  The BIN could be empty by virtue of the compressor running the
         * size of this BIN to 0 but not having yet deleted it from the tree.
         *
         * The index may be negative if we're at an intermediate stage in an
         * higher level operation, and we expect a higher level method to do a
         * next or prev operation after this returns KEYEMPTY. [#11700]
         */
        if (targetIndex < 0 ||
            targetIndex >= targetBin.getNEntries() ||
            targetBin.isEntryKnownDeleted(targetIndex)) {
            /* Node is no longer present. */
            if (treeStatsAccumulator != null) {
                treeStatsAccumulator.incrementDeletedLNCount();
            }
            targetBin.releaseLatch();
            return OperationStatus.KEYEMPTY;
        }

        /*
         * If we encounter a pendingDeleted entry, add it to the compressor
         * queue.
         */
        if (targetBin.isEntryPendingDeleted(targetIndex)) {
            EnvironmentImpl envImpl = databaseImpl.getDbEnvironment();
            envImpl.addToCompressorQueue
                (targetBin, new Key(targetBin.getKey(targetIndex)), false);
        }

        /*
         * We don't need to fetch the LN if all these conditions are true:
         * 1. No locking is needed.
         * 2. The user has not requested that we return the data -- only the
         *    key may be returned, and it is available in the BIN.
         * 3. We don't need to fetch the node in order to determine whether to
         *    descend into the dup tree.
         */
        if (lockType == LockType.NONE &&
            (foundData == null ||
             (foundData.getPartial() &&
              foundData.getPartialLength() == 0)) &&
            (duplicateFetch ||
             !databaseImpl.getSortedDuplicates())) {
            if (targetBin.isEntryPendingDeleted(targetIndex)) {
                if (treeStatsAccumulator != null) {
                    treeStatsAccumulator.incrementDeletedLNCount();
                }
                return OperationStatus.KEYEMPTY;
            }
            if (foundKey != null) {
                setDbt(foundKey,
                       duplicateFetch ?
                       dupKey :
                       targetBin.getKey(targetIndex));
            }
            return OperationStatus.SUCCESS;
        }

        /* If fetchTarget returns null, a deleted LN was cleaned. */
        Node n;
        try {
            n = targetBin.fetchTarget(targetIndex);
        } catch (DatabaseException DE) {
            targetBin.releaseLatch();
            throw DE;
        }
        if (n == null) {
            if (treeStatsAccumulator != null) {
                treeStatsAccumulator.incrementDeletedLNCount();
            }
            targetBin.releaseLatch();
            return OperationStatus.KEYEMPTY;
        }

        /*
         * Note that since we have the BIN/DBIN latched, we can safely check
         * the node type. Any conversions from an LN to a dup tree must have
         * the bin latched.
         */
        addCursor(targetBin);
        if (n.containsDuplicates()) {
            assert !duplicateFetch;
            /* Descend down duplicate tree, doing latch coupling. */
            DIN duplicateRoot = (DIN) n;
            duplicateRoot.latch(cacheMode);
            targetBin.releaseLatch();
            if (positionFirstOrLast(first, duplicateRoot)) {
                try {
                    return fetchCurrent(foundKey, foundData, lockType, first);
                } catch (DatabaseException DE) {
                    releaseBINs();
                    throw DE;
                }
            } else {
                return OperationStatus.NOTFOUND;
            }
        }

        LN ln = (LN) n;

        assert TestHookExecute.doHookIfSet(testHook);

        /*
         * Lock the LN.  For dirty-read, the data of the LN can be set to null
         * at any time.  Cache the data in a local variable so its state does
         * not change before calling setDbt further below.
         */
        LockResult lockResult = lockLN(ln, lockType);
        try {
            ln = lockResult.getLN();
            byte[] lnData = (ln != null) ? ln.getData() : null;
            if (ln == null || lnData == null) {
                if (treeStatsAccumulator != null) {
                    treeStatsAccumulator.incrementDeletedLNCount();
                }
                return OperationStatus.KEYEMPTY;
            }

            /*
             * Don't set the abort LSN here since we are not logging yet, even
             * if this is a write lock.  Tree.insert depends on the fact that
             * the abortLSN is not already set for deleted items.
             */

            /*
             * Return the key from the targetBin because only the targetBin is
             * guaranteed to be latched by lockLN above, and the key is not
             * available as part of the LN.  [#15704]
             */
            if (foundKey != null) {
                duplicateFetch = setTargetBin();
                setDbt(foundKey, duplicateFetch ? dupKey :
                       targetBin.getKey(targetIndex));
            }

            /*
             * With a duplicate comparator configured, data values may also be
             * non-identical but compare as equal.  For the data parameter, we
             * return the LN data.  Although DBIN.getKey is guaranteed to be
             * transactionally correct, we return the LN data instead because
             * that works for duplicates and non-duplicates, and because the LN
             * is the source of truth.  [#15704]
             */
            if (foundData != null) {
                setDbt(foundData, lnData);
            }

            return OperationStatus.SUCCESS;
        } finally {
            releaseBINs();
        }
    }

    /**
     * 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 DBIN 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 ln the LN to be locked.
     * @param lockType the 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 DBIN 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 ln the LN to be locked.
     * @param lockType the 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;

        /*
         * Try a non-blocking lock first, to avoid unlatching.  If the default
         * is no-wait, use the standard lock method so
         * LockNotAvailableException is thrown; there is no need to try a
         * non-blocking lock twice.
         *
         * Even for dirty-read (LockType.NONE) we must call Locker.lock() since
         * it checks the locker state and may throw LockPreemptedException.
         */
        if (locker.getDefaultNoWait()) {
            try {
                lockResult = locker.lock
                    (ln.getNodeId(), lockType, true /*noWait*/, databaseImpl);
            } catch (LockConflictException e) {

                /*
                 * Release all latches.  Note that we catch
                 * LockConflictException for simplicity but we expect either
                 * LockNotAvailableException or LockNotGrantedException.
                 */
                releaseBINs();
                throw e;
            }
        } else {
            lockResult = locker.nonBlockingLock
                (ln.getNodeId(), lockType, databaseImpl);
        }
        if (lockResult.getLockGrant() != LockGrantType.DENIED) {
            lockResult.setLN(ln);
            return lockResult;
        }

        /*
         * Unlatch, get a blocking lock, latch, and get the current node from
         * the slot.  If the node ID changed while unlatched, revert the lock
         * and repeat.
         */
        while (true) {

            /* Save the node ID we're locking and request a lock. */
            long nodeId = ln.getNodeId();
            releaseBINs();
            lockResult = locker.lock
                (nodeId, lockType, false /*noWait*/, databaseImpl);

            /* Fetch the current node after locking. */
            latchBINs();
            setTargetBin();
            ln = (LN) targetBin.fetchTarget(targetIndex);

            if (ln != null && nodeId != ln.getNodeId()) {
                /* If the node ID changed, revert the lock and try again. */
                revertLock(nodeId, lockResult.getLockGrant());
                continue;
            } else {
                /* If null (cleaned) or locked correctly, return the LN. */
                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 dupRoot the duplicate root containing the DupCountLN to be
     * locked.
     * @param lockType the 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;

        /*
         * Try a non-blocking lock first, to avoid unlatching.  If the default
         * is no-wait, use the standard lock method so
         * LockNotAvailableException is thrown; there is no need to try a
         * non-blocking lock twice.
         */
        if (locker.getDefaultNoWait()) {
            try {
                lockResult = locker.lock
                    (ln.getNodeId(), lockType, true /*noWait*/, databaseImpl);
            } catch (LockConflictException e) {
                /*
                 * Release all latches.  Note that we catch
                 * LockConflictException for simplicity but we expect either
                 * LockNotAvailableException or LockNotGrantedException.
                 */
                dupRoot.releaseLatch();
                releaseBINs();
                throw e;
            }
        } else {
            lockResult = locker.nonBlockingLock
                (ln.getNodeId(), lockType, databaseImpl);
        }

        if (lockResult.getLockGrant() == LockGrantType.DENIED) {
            /* Release all latches. */
            dupRoot.releaseLatch();
            releaseBINs();
            /* Request a blocking lock. */
            lockResult = locker.lock
                (ln.getNodeId(), lockType, false /*noWait*/, databaseImpl);
            /* Reacquire all latches. */
            latchBIN();
            dupRoot = (DIN) bin.fetchTarget(index);
            dupRoot.latch(cacheMode);
            latchDBIN();
            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 isDBINLatched is true if the DBIN is currently latched.
     */
    public DIN getLatchedDupRoot(boolean isDBINLatched)
        throws DatabaseException {

        assert bin != null;
        assert bin.isLatchOwnerForWrite();
        assert index >= 0;

        DIN dupRoot = (DIN) bin.fetchTarget(index);

        if (isDBINLatched) {

            /*
             * The BIN and DBIN are currently latched and we need to latch the
             * dupRoot, which is between the BIN and DBIN in the tree.  First
             * try latching the dupRoot no-wait; if this works, we have latched
             * out of order, but in a way that does not cause deadlocks.  If we
             * don't get the no-wait latch, then release the DBIN latch and
             * latch in the proper order down the tree.
             */
            if (!dupRoot.latchNoWait(cacheMode)) {
                releaseDBIN();
                dupRoot.latch(cacheMode);
                latchDBIN();
            }
        } else {
            dupRoot.latch(cacheMode);
        }

        return dupRoot;
    }

    /**
     * Helper to return a Data DBT from a BIN.
     */
    public static 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);
        }
    }

    /*
     * For debugging. Verify that a BINs cursor set refers to the BIN.
     */
    private void verifyCursor(BIN bin)
        throws DatabaseException {

        if (!bin.getCursorSet().contains(this)) {
            throw new EnvironmentFailureException
                (databaseImpl.getDbEnvironment(),
                 EnvironmentFailureReason.UNEXPECTED_STATE,
                 "BIN cursorSet is inconsistent");
        }
    }

    /**
     * Calls checkCursorState and returns false is an exception is thrown.
     */
    private boolean assertCursorState(boolean mustBeInitialized) {
        try {
            checkCursorState(mustBeInitialized);
            return true;
        } catch (RuntimeException e) {
            return false;
        }
    }

    /**
     * Check that the cursor is open and optionally if it is initialized.
     *
     * @throws IllegalStateException via all Cursor methods that call
     * Cursor.checkState (all get and put methods, plus more).
     */
    public void checkCursorState(boolean mustBeInitialized) {
        switch (status) {
        case CURSOR_NOT_INITIALIZED:
            if (mustBeInitialized) {
                throw new IllegalStateException("Cursor not initialized.");
            }
            break;
        case CURSOR_INITIALIZED:
            if (DEBUG) {
                if (bin != null) {
                    verifyCursor(bin);
                }
                if (dupBin != null) {
                    verifyCursor(dupBin);
                }
            }
            break;
        case CURSOR_CLOSED:
            throw new IllegalStateException("Cursor has been closed.");
        default:
            throw EnvironmentFailureException.unexpectedState
                ("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 databaseImpl.
     */
    public void lockEofNode(LockType lockType)
        throws DatabaseException {

        locker.lock(databaseImpl.getEofNodeId(), lockType,
                    false /*noWait*/, databaseImpl);
    }

    /**
     * @throws EnvironmentFailureException if the underlying environment is
     * invalid.
     */
    public void checkEnv() {
        databaseImpl.getDbEnvironment().checkIfInvalid();
    }

    /**
     * Callback object for traverseDbWithCursor.
     */
    public interface WithCursor {

        /**
         * Called for each record in the databaseImpl.
         * @return true to continue or false to stop the enumeration.
         */
        boolean withCursor(CursorImpl cursor,
                           DatabaseEntry key,
                           DatabaseEntry data)
            throws DatabaseException;
    }

    /**
     * Enumerates all records in a databaseImpl non-transactionally and calls
     * the withCursor method for each record.  Stops the enumeration if the
     * callback returns false.
     *
     * @param db DatabaseImpl to traverse.
     *
     * @param lockType non-null LockType for reading records.
     *
     * @param allowEviction should normally be true to evict when performing
     * multiple operations, but may be false if eviction is disallowed in a
     * particular context.
     *
     * @param withCursor callback object.
     */
    public static void traverseDbWithCursor(DatabaseImpl db,
                                            LockType lockType,
                                            boolean allowEviction,
                                            WithCursor withCursor)
        throws DatabaseException {

        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        Locker locker = null;
        CursorImpl cursor = null;
        try {
            EnvironmentImpl envImpl = db.getDbEnvironment();
            locker = LockerFactory.getInternalReadOperationLocker(envImpl);
            cursor = new CursorImpl(db, locker);
            cursor.setAllowEviction(allowEviction);
            if (cursor.positionFirstOrLast(true,    // first
                                           null)) { // duplicateRoot
                OperationStatus status = cursor.getCurrentAlreadyLatched
                    (key, data, lockType, true); // first
                boolean done = false;
                while (!done) {

                    /*
                     * getCurrentAlreadyLatched may have returned non-SUCCESS
                     * if the first record is deleted, but we can call getNext
                     * below to move forward.
                     */
                    if (status == OperationStatus.SUCCESS) {
                        if (!withCursor.withCursor(cursor, key, data)) {
                            done = true;
                        }
                    }
                    if (!done) {
                        status = cursor.getNext(key, data, lockType,
                                                true,   // forward
                                                false); // alreadyLatched
                        if (status != OperationStatus.SUCCESS) {
                            done = true;
                        }
                    }
                }
            }
        } finally {
            if (cursor != null) {
                cursor.releaseBINs();
                cursor.close();
            }
            if (locker != null) {
                locker.operationEnd();
            }
        }
    }

    /**
     * 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));
    }

    /*
     * dumper
     */
    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) + ")";
        }
    }

    /*
     * dumper
     */
    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();
    }

    /*
     * For unit tests
     */
    public StatGroup getLockStats()
        throws DatabaseException {

        return locker.collectStats();
    }

    /**
     * Send trace messages to the java.util.logger. Don't rely on the logger
     * alone to conditionalize whether we send this message, we don't even want
     * to construct the message if the level is not enabled.
     */
    private void trace(Level level,
                       String changeType,
                       BIN theBin,
                       LN ln,
                       int lnIndex,
                       long oldLsn,
                       long newLsn) {
        EnvironmentImpl envImpl = databaseImpl.getDbEnvironment();
        if (envImpl.getLogger().isLoggable(level)) {
            StringBuffer sb = new StringBuffer();
            sb.append(changeType);
            sb.append(" bin=");
            sb.append(theBin.getNodeId());
            sb.append(" ln=");
            sb.append(ln.getNodeId());
            sb.append(" lnIdx=");
            sb.append(lnIndex);
            sb.append(" oldLnLsn=");
            sb.append(DbLsn.getNoFormatString(oldLsn));
            sb.append(" newLnLsn=");
            sb.append(DbLsn.getNoFormatString(newLsn));

            LoggerUtils.logMsg
                (envImpl.getLogger(), envImpl, level, sb.toString());
        }
    }

    /* For unit testing only. */
    public void setTestHook(TestHook hook) {
        testHook = hook;
    }

    /* Check that the target bin is latched. For use in assertions. */
    private boolean checkAlreadyLatched(boolean alreadyLatched) {
        if (alreadyLatched) {
            if (dupBin != null) {
                return dupBin.isLatchOwnerForWrite();
            } else if (bin != null) {
                return bin.isLatchOwnerForWrite();
            }
        }
        return true;
    }
}
TOP

Related Classes of com.sleepycat.je.dbi.CursorImpl$KeyChangeStatus

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.