/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved.
*
*/
package com.sleepycat.je;
import java.util.Collection;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.CursorImpl.LockStanding;
import com.sleepycat.je.dbi.CursorImpl.SearchMode;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DupKeyData;
import com.sleepycat.je.dbi.GetMode;
import com.sleepycat.je.dbi.PutMode;
import com.sleepycat.je.dbi.RangeConstraint;
import com.sleepycat.je.dbi.RangeRestartException;
import com.sleepycat.je.dbi.RecordVersion;
import com.sleepycat.je.dbi.TriggerManager;
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.CountEstimator;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.txn.BuddyLocker;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.LockerFactory;
import com.sleepycat.je.utilint.DatabaseUtil;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.Pair;
import com.sleepycat.je.utilint.ThroughputStatGroup;
import static com.sleepycat.je.EnvironmentFailureException.assertState;
/**
* A database cursor. Cursors are used for operating on collections of records,
* for iterating over a database, and for saving handles to individual records,
* so that they can be modified after they have been read.
*
*
Cursors which are opened with a transaction instance are transactional
* cursors and may be used by multiple threads, but only serially. That is,
* the application must serialize access to the handle. Non-transactional
* cursors, opened with a null transaction instance, may not be used by
* multiple threads.
*
* If the cursor is to be used to perform operations on behalf of a
* transaction, the cursor must be opened and closed within the context of that
* single transaction.
*
* Once the cursor {@link #close} method has been called, the handle may not
* be accessed again, regardless of the {@code close} method's success or
* failure, with one exception: the {@code close} method itself may be called
* any number of times to simplify error handling.
*
* To obtain a cursor with default attributes:
*
*
* Cursor cursor = myDatabase.openCursor(txn, null);
*
*
* To customize the attributes of a cursor, use a CursorConfig object.
*
*
* CursorConfig config = new CursorConfig();
* config.setReadUncommitted(true);
* Cursor cursor = myDatabase.openCursor(txn, config);
*
*
* Modifications to the database during a sequential scan will be reflected
* in the scan; that is, records inserted behind a cursor will not be returned
* while records inserted in front of a cursor will be returned.
*
* By default, a cursor is "sticky", meaning that the prior position is
* maintained by cursor movement operations, and the cursor stays at the
* prior position when {@code NOTFOUND} is returned or an exception is thrown.
* However, it is possible to configure a cursor as non-sticky to enable
* certain performance benefits. See {@link CursorConfig#setNonSticky} for
* details.
*
* Using Partial DatabaseEntry Parameters
*
* The {@link DatabaseEntry#setPartial DatabaseEntry Partial} property can
* be used to optimize in certain cases. This provides varying degrees of
* performance benefits that depend on the specific operation and use of {@code
* READ_UNCOMMITTED} isolation, as described below.
*
* When retrieving a record with a {@link Database} or {@link Cursor}
* method, if only the key is needed by the application then the retrieval of
* the data item can be suppressed using the Partial property. If {@code
* setPartial(0, 0, true)} is called for the {@code DatabaseEntry} passed as
* the data parameter, the data item will not be returned by the {@code
* Database} or {@code Cursor} method.
*
* Suppressing the return of the data item potentially has a large
* performance benefit. In this case, if the record data is not in the JE
* cache, it will not be read from disk. The performance benefit is
* potentially large because random access disk reads may be reduced.
* Examples use cases are:
*
* - Scanning all records in key order, when the data is not needed.
* - Skipping over records quickly with {@code READ_UNCOMMITTED} isolation to
* select records for further processing by examining the key value.
*
*
* Note that by "record data" we mean both the {@code data} parameter for a
* regular or primary DB, and the {@code pKey} parameter for a secondary DB.
* Also note that the performance advantage of a key-only operation does not
* apply to databases configured for duplicates. For a duplicates DB, the data
* is always available along with the key and does not have to be fetched
* separately.
*
* For information on specifying isolation modes, see {@link LockMode},
* {@link CursorConfig} and {@link TransactionConfig}.
*
* The Partial property may also be used to retrieve or update only a
* portion of a data item. This avoids copying the entire record between the
* JE cache and the application data parameter. However, this feature is not
* currently fully optimized, since the entire record is always read or written
* to the database, and the entire record is cached. A partial update may
* be performed only with {@link Cursor#putCurrent Cursor.putCurrent}.
*
* In limited cases, the Partial property may also be used to retrieve a
* partial key item. For example, a {@code DatabaseEntry} with a Partial
* property may be passed to {@link #getNext getNext}. However, in practice
* this has limited value since the entire key is usually needed by the
* application, and the benefit of copying a portion of the key is generally
* very small. Partial key items may not be passed to methods that use the key
* as an input parameter, for example, {@link #getSearchKey getSearchKey}. In
* general, the usefulness of partial key items is very limited.
*/
public class Cursor implements ForwardCursor {
private static final DatabaseEntry EMPTY_DUP_DATA =
new DatabaseEntry(new byte[0]);
static final DatabaseEntry NO_RETURN_DATA = new DatabaseEntry();
static {
NO_RETURN_DATA.setPartial(0, 0, true);
}
/**
* The CursorConfig used to configure this cursor.
*/
CursorConfig config;
/* User Transacational, or null if none. */
private Transaction transaction;
/**
* Handle under which this cursor was created; may be null when the cursor
* is used internally.
*/
private Database dbHandle;
/**
* Database implementation.
*/
private DatabaseImpl dbImpl;
/**
* The underlying cursor.
*/
CursorImpl cursorImpl; // Used by subclasses.
private boolean updateOperationsProhibited;
/* Attributes */
private boolean readUncommittedDefault;
private boolean serializableIsolationDefault;
private boolean nonSticky = false;
private CacheMode cacheMode;
/*
* For range searches, it establishes the upper bound (K2) of the search
* range via a function that returns false if a key is >= K2.
*/
private RangeConstraint rangeConstraint;
/* Used to access call counters. This is null for internal cursors. */
private ThroughputStatGroup thrput;
private Logger logger;
/**
* Creates a cursor for a given user transaction with
* retainNonTxnLocks=false.
*
* If txn is null, a non-transactional cursor will be created that
* releases locks for the prior operation when the next operation
* succeeds.
*/
Cursor(final Database dbHandle,
final Transaction txn,
CursorConfig cursorConfig) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
if (dbHandle != null) {
dbHandle.checkOpen("Can't access Database:");
}
/* Do not allow auto-commit when creating a user cursor. */
Locker locker = LockerFactory.getReadableLocker(
dbHandle, txn, cursorConfig.getReadCommitted());
init(dbHandle, dbHandle.getDatabaseImpl(), locker, cursorConfig,
false /*retainNonTxnLocks*/);
}
/**
* Creates a cursor for a given locker with retainNonTxnLocks=false.
*
* If locker is null or is non-transactional, a non-transactional cursor
* will be created that releases locks for the prior operation when the
* next operation succeeds.
*/
Cursor(final Database dbHandle, Locker locker, CursorConfig cursorConfig) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
if (dbHandle != null) {
dbHandle.checkOpen("Can't access Database:");
}
locker = LockerFactory.getReadableLocker(
dbHandle, locker, cursorConfig.getReadCommitted());
init(dbHandle, dbHandle.getDatabaseImpl(), locker, cursorConfig,
false /*retainNonTxnLocks*/);
}
/**
* Creates a cursor for a given locker and retainNonTxnLocks parameter.
*
* The locker parameter must be non-null. With this constructor, we use
* the given locker and retainNonTxnLocks parameter without applying any
* special rules for different lockers -- the caller must supply the
* correct locker and retainNonTxnLocks combination.
*/
Cursor(final Database dbHandle,
final Locker locker,
CursorConfig cursorConfig,
final boolean retainNonTxnLocks) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
if (dbHandle != null) {
dbHandle.checkOpen("Can't access Database:");
}
init(dbHandle, dbHandle.getDatabaseImpl(), locker, cursorConfig,
retainNonTxnLocks);
}
/**
* Creates a cursor for a given locker and retainNonTxnLocks parameter,
* without a Database handle.
*
* The locker parameter must be non-null. With this constructor, we use
* the given locker and retainNonTxnLocks parameter without applying any
* special rules for different lockers -- the caller must supply the
* correct locker and retainNonTxnLocks combination.
*/
Cursor(final DatabaseImpl databaseImpl,
final Locker locker,
CursorConfig cursorConfig,
final boolean retainNonTxnLocks) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
if (dbHandle != null) {
dbHandle.checkOpen("Can't access Database:");
}
init(null /*dbHandle*/, databaseImpl, locker, cursorConfig,
retainNonTxnLocks);
}
private void init(final Database dbHandle,
final DatabaseImpl databaseImpl,
final Locker locker,
final CursorConfig cursorConfig,
final boolean retainNonTxnLocks) {
assert locker != null;
/*
* Allow locker to perform "open cursor" actions, such as consistency
* checks for a non-transactional locker on a Replica.
*/
try {
locker.openCursorHook(databaseImpl);
} catch (RuntimeException e) {
locker.operationEnd();
throw e;
}
cursorImpl = new CursorImpl(
databaseImpl, locker, retainNonTxnLocks, isSecondaryCursor());
transaction = locker.getTransaction();
/* Perform eviction for user cursors. */
cursorImpl.setAllowEviction(true);
readUncommittedDefault =
cursorConfig.getReadUncommitted() ||
locker.isReadUncommittedDefault();
serializableIsolationDefault =
cursorImpl.getLocker().isSerializableIsolation();
/* Be sure to keep this logic in sync with checkUpdatesAllowed. */
updateOperationsProhibited =
locker.isReadOnly() ||
(dbHandle != null && !dbHandle.isWritable()) ||
(databaseImpl.isTransactional() && !locker.isTransactional()) ||
(databaseImpl.isReplicated() == locker.isLocalWrite());
this.dbImpl = databaseImpl;
if (dbHandle != null) {
this.dbHandle = dbHandle;
dbHandle.addCursor(this);
thrput = dbHandle.getEnvironment().
getEnvironmentImpl().getThroughputStatGroup();
}
this.config = cursorConfig;
this.logger = databaseImpl.getEnv().getLogger();
/*
* The non-sticky and cache mode properties are related. setCacheMode
* may modify non-sticky, so initialize in the following order.
*/
nonSticky = cursorConfig.getNonSticky();
setCacheMode(null);
}
/**
* Copy constructor.
*/
Cursor(final Cursor cursor, final boolean samePosition) {
readUncommittedDefault = cursor.readUncommittedDefault;
serializableIsolationDefault = cursor.serializableIsolationDefault;
updateOperationsProhibited = cursor.updateOperationsProhibited;
cursorImpl = cursor.cursorImpl.cloneCursor(samePosition);
dbImpl = cursor.dbImpl;
dbHandle = cursor.dbHandle;
if (dbHandle != null) {
dbHandle.addCursor(this);
}
config = cursor.config;
logger = dbImpl.getEnv().getLogger();
cacheMode = cursor.cacheMode;
nonSticky = cursor.nonSticky;
thrput = cursor.thrput;
}
boolean isSecondaryCursor() {
return false;
}
/**
* Sets non-sticky mode, if possible for the currently configured cache
* mode. See CursorImpl.beforeAdvance on cache mode restrictions.
*
* TODO: Make this private after removing DbInternal.setNonSticky.
*
* @see CursorConfig#setNonSticky
*/
void setNonSticky(final boolean nonSticky) {
this.nonSticky =
nonSticky &&
cacheMode != CacheMode.EVICT_BIN &&
cacheMode != CacheMode.MAKE_COLD;
}
/**
* Internal entrypoint.
*/
CursorImpl getCursorImpl() {
return cursorImpl;
}
/**
* Returns the Database handle associated with this Cursor.
*
* @return The Database handle associated with this Cursor.
*/
public Database getDatabase() {
return dbHandle;
}
/**
* Always returns non-null, while getDatabase() returns null if no handle
* is associated with this cursor.
*/
DatabaseImpl getDatabaseImpl() {
return dbImpl;
}
/**
* Returns this cursor's configuration.
*
* This may differ from the configuration used to open this object if
* the cursor existed previously.
*
* @return This cursor's configuration.
*/
public CursorConfig getConfig() {
try {
return config.clone();
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Returns the {@code CacheMode} used for subsequent operations performed
* using this cursor. If {@link #setCacheMode} has not been called with a
* non-null value, the configured Database or Environment default is
* returned.
*
* @return the {@code CacheMode} used for subsequent operations using
* this cursor.
*
* @see #setCacheMode
*/
public CacheMode getCacheMode() {
return cacheMode;
}
/**
* Sets the {@code CacheMode} used for subsequent operations performed
* using this cursor. This method may be used to override the defaults
* specified using {@link DatabaseConfig#setCacheMode} and {@link
* EnvironmentConfig#setCacheMode}.
*
* @param cacheMode is the {@code CacheMode} used for subsequent operations
* using this cursor, or null to configure the Database or Environment
* default.
*
* @see CacheMode for further details.
*/
public void setCacheMode(final CacheMode cacheMode) {
this.cacheMode =
(cacheMode != null) ? cacheMode : dbImpl.getDefaultCacheMode();
/* Reinitialize non-sticky after changing the cache mode. */
setNonSticky(nonSticky);
}
/**
* @hidden
* For internal use only.
* Used by KVStore.
*
* A RangeConstraint is used by search-range and next/previous methods to
* prevent keys that are not inside the range from being returned.
*
* This method is not yet part of the public API because it has not been
* designed with future-proofing or generality in mind, and has not been
* reviewed.
*/
public void setRangeConstraint(RangeConstraint rangeConstraint) {
if (dbImpl.getSortedDuplicates()) {
throw new UnsupportedOperationException("Not allowed with dups");
}
this.rangeConstraint = rangeConstraint;
}
private void setPrefixConstraint(final Cursor c, final byte[] keyBytes2) {
c.rangeConstraint = new RangeConstraint() {
public boolean inBounds(byte[] checkKey) {
return DupKeyData.compareMainKey(
checkKey, keyBytes2, dbImpl.getBtreeComparator()) == 0;
}
};
}
private void setPrefixConstraint(final Cursor c,
final DatabaseEntry key2) {
c.rangeConstraint = new RangeConstraint() {
public boolean inBounds(byte[] checkKey) {
return DupKeyData.compareMainKey(
checkKey, key2.getData(), key2.getOffset(),
key2.getSize(), dbImpl.getBtreeComparator()) == 0;
}
};
}
private boolean checkRangeConstraint(final DatabaseEntry key) {
assert key.getOffset() == 0;
assert key.getData().length == key.getSize();
if (rangeConstraint == null) {
return true;
}
return rangeConstraint.inBounds(key.getData());
}
/**
* Discards the cursor.
*
* The cursor handle may not be used again after this method has been
* called, regardless of the method's success or failure, with one
* exception: the {@code close} method itself may be called any number of
* times.
*
* WARNING: To guard against memory leaks, the application should
* discard all references to the closed handle. While BDB makes an effort
* to discard references from closed objects to the allocated memory for an
* environment, this behavior is not guaranteed. The safe course of action
* for an application is to discard all references to closed BDB
* objects.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*/
public void close()
throws DatabaseException {
try {
if (cursorImpl.isClosed()) {
return;
}
/*
* Do not call checkState here, to allow closing a cursor after an
* operation failure. [#17015]
*/
checkEnv();
cursorImpl.close();
if (dbHandle != null) {
dbHandle.removeCursor(this);
dbHandle = null;
}
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Returns a new cursor with the same transaction and locker ID as the
* original cursor.
*
* This is useful when an application is using locking and requires
* two or more cursors in the same thread of control.
*
* @param samePosition If true, the newly created cursor is initialized
* to refer to the same position in the database as the original cursor
* (if any) and hold the same locks (if any). If false, or the original
* cursor does not hold a database position and locks, the returned
* cursor is uninitialized and will behave like a newly created cursor.
*
* @return A new cursor with the same transaction and locker ID as the
* original cursor.
*
* @throws com.sleepycat.je.rep.DatabasePreemptedException in a replicated
* environment if the master has truncated, removed or renamed the
* database.
*
* @throws OperationFailureException if this exception occurred earlier and
* caused the transaction to be invalidated.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed.
*/
public Cursor dup(final boolean samePosition)
throws DatabaseException {
try {
checkState(false);
return new Cursor(this, samePosition);
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Deletes the key/data pair to which the cursor refers.
*
* When called on a cursor opened on a database that has been made into
* a secondary index, this method the key/data pair from the primary
* database and all secondary indices.
*
* The cursor position is unchanged after a delete, and subsequent calls
* to cursor functions expecting the cursor to refer to an existing key
* will fail.
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEMPTY
* OperationStatus.KEYEMPTY} if the key/pair at the cursor position has
* been deleted; otherwise, {@link
* com.sleepycat.je.OperationStatus#SUCCESS OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is transactional
* but this cursor was not opened with a non-null transaction parameter,
* or the database is read-only.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*/
public OperationStatus delete()
throws LockConflictException,
DatabaseException,
UnsupportedOperationException {
checkState(true);
trace(Level.FINEST, "Cursor.delete: ", null);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_DELETE_OFFSET);
}
return deleteInternal(dbImpl.getRepContext());
}
/**
* Stores a key/data pair into the database.
*
* If the put method succeeds, the cursor is always positioned to refer
* to the newly inserted item.
*
* If the key already appears in the database and duplicates are
* supported, the new data value is inserted at the correct sorted
* location, unless the new data value also appears in the database
* already. In the later case, although the given key/data pair compares
* equal to an existing key/data pair, the two records may not be identical
* if custom comparators are used, in which case the existing record will
* be replaced with the new record. If the key already appears in the
* database and duplicates are not supported, the data associated with
* the key will be replaced.
*
* @param key the key {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} operated on.
*
* @param data the data {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} stored.
*
* @return an OperationStatus for the operation.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is transactional
* but this cursor was not opened with a non-null transaction parameter,
* or the database is read-only.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus put(
final DatabaseEntry key,
final DatabaseEntry data)
throws DatabaseException, UnsupportedOperationException {
checkState(false);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
trace(Level.FINEST, "Cursor.put: ", key, data, null);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_PUT_OFFSET);
}
return putInternal(key, data, PutMode.OVERWRITE);
}
/**
* Stores a key/data pair into the database.
*
* If the putNoOverwrite method succeeds, the cursor is always
* positioned to refer to the newly inserted item.
*
* If the key already appears in the database, putNoOverwrite will
* return {@link com.sleepycat.je.OperationStatus#KEYEXIST
* OperationStatus.KEYEXIST}.
*
* @param key the key {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} operated on.
*
* @param data the data {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} stored.
*
* @return an OperationStatus for the operation.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is transactional
* but this cursor was not opened with a non-null transaction parameter,
* or the database is read-only.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus putNoOverwrite(
final DatabaseEntry key,
final DatabaseEntry data)
throws DatabaseException, UnsupportedOperationException {
checkState(false);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
trace(Level.FINEST, "Cursor.putNoOverwrite: ", key, data, null);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_PUTNOOVERWRITE_OFFSET);
}
return putInternal(key, data, PutMode.NO_OVERWRITE);
}
/**
* Stores a key/data pair into the database. The database must be
* configured for duplicates.
*
* If the putNoDupData method succeeds, the cursor is always positioned
* to refer to the newly inserted item.
*
* Insert the specified key/data pair into the database, unless a
* key/data pair comparing equally to it already exists in the database.
* If a matching key/data pair already exists in the database, {@link
* com.sleepycat.je.OperationStatus#KEYEXIST OperationStatus.KEYEXIST} is
* returned.
*
* @param key the key {@link com.sleepycat.je.DatabaseEntry DatabaseEntry}
* operated on.
*
* @param data the data {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} stored.
*
* @return an OperationStatus for the operation.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is transactional
* but this cursor was not opened with a non-null transaction parameter, or
* the database is read-only, or the database is not configured for
* duplicates.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus putNoDupData(
final DatabaseEntry key,
final DatabaseEntry data)
throws DatabaseException, UnsupportedOperationException {
checkState(false);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartialKey(key);
trace(Level.FINEST, "Cursor.putNoDupData: ", key, data, null);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_PUTNODUPDATA_OFFSET);
}
return putInternal(key, data, PutMode.NO_DUP_DATA);
}
/**
* Replaces the data in the key/data pair at the current cursor position.
*
* Overwrite the data of the key/data pair to which the cursor refers
* with the specified data item. This method will return
* OperationStatus.NOTFOUND if the cursor currently refers to an
* already-deleted key/data pair.
*
* For a database that does not support duplicates, the data may be
* changed by this method. If duplicates are supported, the data may be
* changed only if a custom partial comparator is configured and the
* comparator considers the old and new data to be equal (that is, the
* comparator returns zero). For more information on partial comparators
* see {@link DatabaseConfig#setDuplicateComparator}.
*
* If the old and new data are unequal according to the comparator, a
* {@link DuplicateDataException} is thrown. Changing the data in this
* case would change the sort order of the record, which would change the
* cursor position, and this is not allowed. To change the sort order of a
* record, delete it and then re-insert it.
*
* @param data - the data DatabaseEntry stored.
* A partial data item may be
* specified to optimize for partial data update.
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEMPTY
* OperationStatus.KEYEMPTY} if the key/pair at the cursor position has
* been deleted; otherwise, {@link
* com.sleepycat.je.OperationStatus#SUCCESS OperationStatus.SUCCESS}.
*
* @throws DuplicateDataException if the old and new data are not equal
* according to the configured duplicate comparator or default comparator.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is transactional
* but this cursor was not opened with a non-null transaction parameter,
* or the database is read-only.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus putCurrent(final DatabaseEntry data)
throws DatabaseException, UnsupportedOperationException {
checkState(true);
DatabaseUtil.checkForNullDbt(data, "data", true);
trace(Level.FINEST, "Cursor.putCurrent: ", null, data, null);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_PUTCURRENT_OFFSET);
}
return putInternal(null /*key*/, data, PutMode.CURRENT);
}
/**
* Returns the key/data pair to which the cursor refers.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEMPTY
* OperationStatus.KEYEMPTY} if the key/pair at the cursor position has
* been deleted; otherwise, {@link
* com.sleepycat.je.OperationStatus#SUCCESS OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getCurrent(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
try {
checkState(true);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getCurrent: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETCURRENT_OFFSET);
}
return getCurrentInternal(key, data, lockMode);
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Moves the cursor to the first key/data pair of the database, and returns
* that pair. If the first key has duplicate values, the first data item
* in the set of duplicates is returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getFirst(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getFirst: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETFIRST_OFFSET);
}
return position(key, data, lockMode, true);
}
/**
* Moves the cursor to the last key/data pair of the database, and returns
* that pair. If the last key has duplicate values, the last data item in
* the set of duplicates is returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getLast(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getLast: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETLAST_OFFSET);
}
return position(key, data, lockMode, false);
}
/**
* Moves the cursor to the next key/data pair and returns that pair.
*
* If the cursor is not yet initialized, move the cursor to the first
* key/data pair of the database, and return that pair. Otherwise, the
* cursor is moved to the next key/data pair of the database, and that pair
* is returned. In the presence of duplicate key values, the value of the
* key may not change.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getNext(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getNext: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETNEXT_OFFSET);
}
if (cursorImpl.isNotInitialized()) {
return position(key, data, lockMode, true);
} else {
return retrieveNext(key, data, lockMode, GetMode.NEXT);
}
}
/**
* If the next key/data pair of the database is a duplicate data record for
* the current key/data pair, moves the cursor to the next key/data pair of
* the database and returns that pair.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getNextDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(true);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getNextDup: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETNEXTDUP_OFFSET);
}
return retrieveNext(key, data, lockMode, GetMode.NEXT_DUP);
}
/**
* Moves the cursor to the next non-duplicate key/data pair and returns
* that pair. If the matching key has duplicate values, the first data
* item in the set of duplicates is returned.
*
* If the cursor is not yet initialized, move the cursor to the first
* key/data pair of the database, and return that pair. Otherwise, the
* cursor is moved to the next non-duplicate key of the database, and that
* key/data pair is returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getNextNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getNextNoDup: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETNEXTNODUP_OFFSET);
}
if (cursorImpl.isNotInitialized()) {
return position(key, data, lockMode, true);
} else {
return retrieveNext(key, data, lockMode, GetMode.NEXT_NODUP);
}
}
/**
* Moves the cursor to the previous key/data pair and returns that pair.
*
* If the cursor is not yet initialized, move the cursor to the last
* key/data pair of the database, and return that pair. Otherwise, the
* cursor is moved to the previous key/data pair of the database, and that
* pair is returned. In the presence of duplicate key values, the value of
* the key may not change.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getPrev(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getPrev: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETPREV_OFFSET);
}
if (cursorImpl.isNotInitialized()) {
return position(key, data, lockMode, false);
} else {
return retrieveNext(key, data, lockMode, GetMode.PREV);
}
}
/**
* If the previous key/data pair of the database is a duplicate data record
* for the current key/data pair, moves the cursor to the previous key/data
* pair of the database and returns that pair.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getPrevDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(true);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getPrevDup: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETPREVDUP_OFFSET);
}
return retrieveNext(key, data, lockMode, GetMode.PREV_DUP);
}
/**
* Moves the cursor to the previous non-duplicate key/data pair and returns
* that pair. If the matching key has duplicate values, the last data item
* in the set of duplicates is returned.
*
* If the cursor is not yet initialized, move the cursor to the last
* key/data pair of the database, and return that pair. Otherwise, the
* cursor is moved to the previous non-duplicate key of the database, and
* that key/data pair is returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getPrevNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsNoValRequired(key, data);
trace(Level.FINEST, "Cursor.getPrevNoDup: ", lockMode);
if (thrput != null) {
thrput.increment(ThroughputStatGroup.CURSOR_GETPREVNODUP_OFFSET);
}
if (cursorImpl.isNotInitialized()) {
return position(key, data, lockMode, false);
} else {
return retrieveNext(key, data, lockMode, GetMode.PREV_NODUP);
}
}
/**
* Skips forward a given number of key/data pairs and returns the number by
* which the cursor is moved.
*
* Without regard to performance, calling this method is equivalent to
* repeatedly calling {@link #getNext getNext} with {@link
* LockMode#READ_UNCOMMITTED} to skip over the desired number of key/data
* pairs, and then calling {@link #getCurrent getCurrent} with the {@code
* lockMode} parameter to return the final key/data pair.
*
* With regard to performance, this method is optimized to skip over
* key/value pairs using a smaller number of Btree operations. When there
* is no contention on the bottom internal nodes (BINs) and all BINs are in
* cache, the number of Btree operations is reduced by roughly two orders
* of magnitude, where the exact number depends on the {@link
* EnvironmentConfig#NODE_MAX_ENTRIES} setting. When there is contention
* on BINs or fetching BINs is required, the scan is broken up into smaller
* operations to avoid blocking other threads for long time periods.
*
* If the returned count is greater than zero, then the key/data pair at
* the new cursor position is also returned. If zero is returned, then
* there are no key/value pairs that follow the cursor position and a
* key/data pair is not returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param maxCount the maximum number of key/data pairs to skip, i.e., the
* maximum number by which the cursor should be moved; must be greater
* than zero.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return the number of key/data pairs skipped, i.e., the number by which
* the cursor has moved; if zero is returned, the cursor position is
* unchanged and the key/data pair is not returned.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public long skipNext(
final long maxCount,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(true);
if (maxCount <= 0) {
throw new IllegalArgumentException("maxCount must be positive: " +
maxCount);
}
trace(Level.FINEST, "Cursor.skipNext: ", lockMode);
return skipInternal(maxCount, true /*forward*/, key, data, lockMode);
}
/**
* Skips backward a given number of key/data pairs and returns the number
* by which the cursor is moved.
*
* Without regard to performance, calling this method is equivalent to
* repeatedly calling {@link #getPrev getPrev} with {@link
* LockMode#READ_UNCOMMITTED} to skip over the desired number of key/data
* pairs, and then calling {@link #getCurrent getCurrent} with the {@code
* lockMode} parameter to return the final key/data pair.
*
* With regard to performance, this method is optimized to skip over
* key/value pairs using a smaller number of Btree operations. When there
* is no contention on the bottom internal nodes (BINs) and all BINs are in
* cache, the number of Btree operations is reduced by roughly two orders
* of magnitude, where the exact number depends on the {@link
* EnvironmentConfig#NODE_MAX_ENTRIES} setting. When there is contention
* on BINs or fetching BINs is required, the scan is broken up into smaller
* operations to avoid blocking other threads for long time periods.
*
* If the returned count is greater than zero, then the key/data pair at
* the new cursor position is also returned. If zero is returned, then
* there are no key/value pairs that follow the cursor position and a
* key/data pair is not returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param maxCount the maximum number of key/data pairs to skip, i.e., the
* maximum number by which the cursor should be moved; must be greater
* than zero.
*
* @param key the key returned as output. Its byte array does not need to
* be initialized by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return the number of key/data pairs skipped, i.e., the number by which
* the cursor has moved; if zero is returned, the cursor position is
* unchanged and the key/data pair is not returned.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public long skipPrev(
final long maxCount,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(true);
if (maxCount <= 0) {
throw new IllegalArgumentException("maxCount must be positive: " +
maxCount);
}
trace(Level.FINEST, "Cursor.skipPrev: ", lockMode);
return skipInternal(maxCount, false /*forward*/, key, data, lockMode);
}
/**
* Moves the cursor to the given key of the database, and returns the datum
* associated with the given key. If the matching key has duplicate
* values, the first data item in the set of duplicates is returned.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key used as input. It must be initialized with a
* non-null byte array by the caller.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getSearchKey(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", false);
trace(Level.FINEST, "Cursor.getSearchKey: ", key, null, lockMode);
return search(key, data, lockMode, SearchMode.SET);
}
/**
* Moves the cursor to the closest matching key of the database, and
* returns the data item associated with the matching key. If the matching
* key has duplicate values, the first data item in the set of duplicates
* is returned.
*
* The returned key/data pair is for the smallest key greater than or
* equal to the specified key (as determined by the key comparison
* function), permitting partial key matches and range searches.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key used as input and returned as output. It must be
* initialized with a non-null byte array by the caller.
* A partial data item may be
* specified to optimize for key only or partial data retrieval.
*
* @param data the data returned as output. Its byte array does not need
* to be initialized by the caller.
*
* @param lockMode the locking attributes; if null, default attributes
* are used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getSearchKeyRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", false);
trace(Level.FINEST, "Cursor.getSearchKeyRange: ", key, null, lockMode);
return search(key, data, lockMode, SearchMode.SET_RANGE);
}
/**
* Moves the cursor to the specified key/data pair, where both the key and
* data items must match.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key used as input. It must be initialized with a
* non-null byte array by the caller.
*
* @param data the data used as input. It must be initialized with a
* non-null byte array by the caller.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getSearchBoth(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsValRequired(key, data);
trace(Level.FINEST, "Cursor.getSearchBoth: ", key, data, lockMode);
return search(key, data, lockMode, SearchMode.BOTH);
}
/**
* Moves the cursor to the specified key and closest matching data item of
* the database.
*
* In the case of any database supporting sorted duplicate sets, the
* returned key/data pair is for the smallest data item greater than or
* equal to the specified data item (as determined by the duplicate
* comparison function), permitting partial matches and range searches in
* duplicate data sets.
*
* In the case of databases that do not support sorted duplicate sets,
* this method is equivalent to getSearchBoth.
*
* In a replicated environment, an explicit transaction must have been
* specified when opening the cursor, unless read-uncommitted isolation is
* specified via the {@link CursorConfig} or {@link LockMode}
* parameter.
*
* @param key the key used as input. It must be initialized with a
* non-null byte array by the caller.
*
* @param data the data used as input and returned as output. It must be
* initialized with a non-null byte array by the caller.
*
* @param lockMode the locking attributes; if null, default attributes are
* used. {@link LockMode#READ_COMMITTED} is not allowed.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the non-transactional cursor was created in a different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getSearchBothRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode)
throws DatabaseException {
checkState(false);
checkArgsValRequired(key, data);
trace(Level.FINEST, "Cursor.getSearchBothRange: ", key, data,
lockMode);
if (!dbImpl.getSortedDuplicates()) {
return search(key, data, lockMode, SearchMode.BOTH);
}
return search(key, data, lockMode, SearchMode.BOTH_RANGE);
}
/**
* Returns a count of the number of data items for the key to which the
* cursor refers.
*
* If the database is configured for duplicates, the database is scanned
* internally, without taking any record locks, to count the number of
* non-deleted entries. Although the internal scan is more efficient under
* some conditions, the result is the same as if a cursor were used to
* iterate over the entries using {@link LockMode#READ_UNCOMMITTED}.
*
* If the database is not configured for duplicates, the count returned
* is always zero or one, depending on the record at the cursor position is
* deleted or not.
*
* The cost of this method is directly proportional to the number of
* records scanned.
*
* @return A count of the number of data items for the key to which the
* cursor refers.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*/
public int count()
throws DatabaseException {
checkState(true);
trace(Level.FINEST, "Cursor.count: ", null);
return countInternal();
}
/**
* Returns a rough estimate of the count of the number of data items for
* the key to which the cursor refers.
*
* If the database is configured for duplicates, a quick estimate of the
* number of records is computed using information in the Btree. Because
* the Btree is unbalanced, in some cases the estimate may be off by a
* factor of two or more. The estimate is accurate when the number of
* records is less than the configured {@link
* DatabaseConfig#setNodeMaxEntries NodeMaxEntries}.
*
* If the database is not configured for duplicates, the count returned
* is always zero or one, depending on the record at the cursor position is
* deleted or not.
*
* The cost of this method is fixed, rather than being proportional to
* the number of records scanned. Because its accuracy is variable, this
* method should normally be used when accuracy is not required, such as
* for query optimization, and a fixed cost operation is needed. For
* example, this method is used internally for determining the index
* processing order in a {@link JoinCursor}.
*
* @return an estimate of the count of the number of data items for the key
* to which the cursor refers.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* or the cursor is uninitialized (not positioned on a record), or the
* non-transactional cursor was created in a different thread.
*/
public long countEstimate()
throws DatabaseException {
checkState(true);
trace(Level.FINEST, "Cursor.countEstimate: ", null);
return countEstimateInternal();
}
/**
* Internal version of delete() that does no parameter checking. Notify
* triggers, update secondaries and enforce foreign key constraints.
*
* Note that this algorithm is duplicated in Database and Cursor for
* efficiency reasons: in Cursor delete we must separately fetch the key
* and data, while in Database delete we know the key and have to search
* anyway so we can get the old data when we search. The two algorithms
* need to be kept in sync.
*/
OperationStatus deleteInternal(final ReplicationContext repContext) {
checkUpdatesAllowed();
final boolean hasUserTriggers = (dbImpl.getTriggers() != null);
final boolean hasAssociations = (dbHandle != null) &&
dbHandle.hasSecondaryOrForeignKeyAssociations();
if (hasAssociations) {
try {
dbImpl.getEnv().getSecondaryAssociationLock().
readLock().lockInterruptibly();
} catch (InterruptedException e) {
throw new ThreadInterruptedException(
dbImpl.getEnv(), e);
}
}
try {
/* The key is needed if there are secondaries or triggers. */
final DatabaseEntry key;
if (hasAssociations || hasUserTriggers) {
key = new DatabaseEntry();
key.setData(cursorImpl.getCurrentKey());
} else {
key = null;
}
/*
* Get secondaries from the association and determine whether the
* old data is needed.
*/
final Collection secondaries;
final Collection fkSecondaries;
final boolean needOldData;
if (hasAssociations) {
secondaries = dbHandle.secAssoc.getSecondaries(key);
fkSecondaries = dbHandle.foreignKeySecondaries;
needOldData = hasUserTriggers ||
SecondaryDatabase.needOldDataForDelete(secondaries);
} else {
secondaries = null;
fkSecondaries = null;
needOldData = hasUserTriggers;
}
/*
* Get old data if needed. Even if the old data is not needed, if
* there are associations we call getCurrentInternal with a null
* oldData param to lock the record and find out if it's already
* deleted; this must be done before calling onForeignKeyDelete or
* updateSecondary.
*/
final DatabaseEntry oldData =
needOldData ? (new DatabaseEntry()) : null;
if (needOldData || hasAssociations) {
final OperationStatus status = getCurrentInternal(
key, oldData, LockMode.RMW);
if (status != OperationStatus.SUCCESS) {
return OperationStatus.KEYEMPTY;
}
}
/*
* Enforce foreign key constraints before secondary updates, so
* that ForeignKeyDeleteAction.ABORT is applied before deleting the
* secondary keys.
*/
final Locker locker = cursorImpl.getLocker();
if (fkSecondaries != null) {
for (final SecondaryDatabase secDb : fkSecondaries) {
secDb.onForeignKeyDelete(locker, key);
}
}
/*
* Update secondaries before actual deletion, so that a primary
* record always exists while secondary keys refer to it. This is
* relied on by secondary read-uncommitted.
*/
if (secondaries != null) {
for (final SecondaryDatabase secDb : secondaries) {
secDb.updateSecondary(locker, null, key, oldData, null);
}
}
/*
* The actual deletion.
*/
final OperationStatus deleteStatus = deleteNoNotify(repContext);
if (deleteStatus != OperationStatus.SUCCESS) {
return deleteStatus;
}
/* Run triggers after actual deletion. */
if (hasUserTriggers) {
TriggerManager.runDeleteTriggers(locker, dbImpl, key, oldData);
}
return OperationStatus.SUCCESS;
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
} finally {
if (hasAssociations) {
dbImpl.getEnv().getSecondaryAssociationLock().
readLock().unlock();
}
}
}
/**
* Delete at current position. Does not notify triggers (does not perform
* secondary updates).
*/
OperationStatus deleteNoNotify(final ReplicationContext repContext) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
/*
* No need to use a dup cursor, since this operation does not
* change the cursor position.
*/
beginUseExistingCursor();
final OperationStatus status = cursorImpl.deleteCurrentRecord(
repContext, thrput);
endUseExistingCursor();
return status;
}
}
/**
* Version of putInternal that allows passing an existing LN and does not
* interpret duplicates. Used for replication stream replay. Notifies
* triggers and prevents phantoms.
*/
OperationStatus putForReplay(
final DatabaseEntry key,
final DatabaseEntry data,
final LN ln,
final PutMode putMode,
final ReplicationContext repContext) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
assert putMode != PutMode.CURRENT;
return putNotify(key, data, ln, putMode, repContext);
}
}
/**
* Internal version of put that does no parameter checking. Interprets
* duplicates, notifies triggers, and prevents phantoms.
*/
OperationStatus putInternal(
final DatabaseEntry key,
final DatabaseEntry data,
final PutMode putMode) {
checkUpdatesAllowed();
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return putHandleDups(key, data, putMode);
}
if (putMode == PutMode.NO_DUP_DATA) {
throw new UnsupportedOperationException(
"Database is not configured for duplicate data.");
}
return putNoDups(key, data, putMode);
}
}
/**
* Interpret duplicates for the various 'putXXX' operations.
*/
private OperationStatus putHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final PutMode putMode) {
switch (putMode) {
case OVERWRITE:
return dupsPutOverwrite(key, data);
case NO_OVERWRITE:
return dupsPutNoOverwrite(key, data);
case NO_DUP_DATA:
return dupsPutNoDupData(key, data);
case CURRENT:
return dupsPutCurrent(data);
default:
throw EnvironmentFailureException.unexpectedState(
putMode.toString());
}
}
/**
* Interpret duplicates for the put() operation.
*/
private OperationStatus dupsPutOverwrite(
final DatabaseEntry key,
final DatabaseEntry data) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
return putNoDups(twoPartKey, EMPTY_DUP_DATA, PutMode.OVERWRITE);
}
/**
* Interpret duplicates for putNoOverwrite() operation.
*
* The main purpose of this method is to guarantee that when two threads
* call putNoOverwrite concurrently, only one of them will succeed. In
* other words, if putNoOverwrite is called for all dup insertions, there
* will always be at most one dup per key.
*
* Next key locking must be used to prevent two insertions, since there is
* no other way to block an insertion of dup Y in another thread, while
* inserting dup X in the current thread. This is tested by AtomicPutTest.
*
* Although this method does extra searching and locking compared to
* putNoOverwrite for a non-dup DB (or to putNoDupData for a dup DB), that
* is not considered a significant issue because this method is rarely, if
* ever, used by applications (for dup DBs that is). It exists primarily
* for compatibility with the DB core API.
*/
private OperationStatus dupsPutNoOverwrite(
final DatabaseEntry key,
final DatabaseEntry data) {
final DatabaseEntry key2 = new DatabaseEntry();
final DatabaseEntry data2 = new DatabaseEntry();
final Cursor c = dup(false /*samePosition*/);
try {
c.setNonSticky(true);
/* Lock next key (or EOF if none) exclusively, before we insert. */
setEntry(key, key2);
OperationStatus status = c.dupsGetSearchKeyRange(
key2, data2, LockMode.RMW);
if (status == OperationStatus.SUCCESS && key.equals(key2)) {
/* Key exists, no need for further checks. */
return OperationStatus.KEYEXIST;
}
if (status != OperationStatus.SUCCESS) {
/* No next key exists, lock EOF. */
c.cursorImpl.lockEof(LockType.WRITE);
}
/* While next key is locked, check for key existence again. */
setEntry(key, key2);
status = c.dupsGetSearchKey(key2, data2, LockMode.RMW);
if (status == OperationStatus.SUCCESS) {
return OperationStatus.KEYEXIST;
}
/* Insertion can safely be done now. */
status = c.dupsPutNoDupData(key, data);
if (status != OperationStatus.SUCCESS) {
return status;
}
/* We successfully inserted the first dup for the key. */
swapCursor(c);
return OperationStatus.SUCCESS;
} finally {
c.close();
}
}
/**
* Interpret duplicates for putNoDupData operation.
*/
private OperationStatus dupsPutNoDupData(
final DatabaseEntry key,
final DatabaseEntry data) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
return putNoDups(twoPartKey, EMPTY_DUP_DATA, PutMode.NO_OVERWRITE);
}
/**
* Interpret duplicates for putCurrent operation.
*
* Get old key/data, replace data portion, and put new key/data.
*
* Arguably we could skip the replacement if there is no user defined
* comparison function and the new data is the same.
*/
private OperationStatus dupsPutCurrent(final DatabaseEntry newData) {
final DatabaseEntry oldTwoPartKey = new DatabaseEntry();
/*
* Lock the LSN of the current slot in WRITE mode and extract the
* slot key.
*/
final OperationStatus status = getCurrentNoDups(
oldTwoPartKey, NO_RETURN_DATA, LockMode.RMW);
if (status != OperationStatus.SUCCESS) {
return status;
}
final DatabaseEntry key = new DatabaseEntry();
DupKeyData.split(oldTwoPartKey, key, null);
final DatabaseEntry newTwoPartKey = DupKeyData.combine(key, newData);
return putNoDups(newTwoPartKey, EMPTY_DUP_DATA, PutMode.CURRENT);
}
/**
* Eventually, all insertions/updates are happenning via this method.
*/
private OperationStatus putNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final PutMode putMode) {
final LN ln = (putMode == PutMode.CURRENT) ?
null :
LN.makeLN(dbImpl.getEnv(), data);
return putNotify(key, data, ln, putMode, dbImpl.getRepContext());
}
/**
* This single method is used for all put operations in order to notify
* triggers and perform secondary updates in one place. Prevents phantoms.
* Does not interpret duplicates.
*
* WARNING: When the cursor has no Database handle, which is true when
* called from the replication replayer, this method notifies user triggers
* but does not do secondary updates. This is correct for replication
* because secondary updates are part of the replication stream. However,
* it is fragile because other operations, when no Database handle is used,
* will not perform secondary updates. This isn't currently a problem
* because a Database handle is present for all user operations. But it is
* fragile and needs work.
*
* @param putMode One of OVERWRITE, NO_OVERWITE, CURRENT. (NO_DUPS_DATA
* has been converted to NO_OVERWRITE). Note: OVERWRITE may perform an
* insertion or an update, NO_OVERWRITE performs insertion only, and
* CURRENT updates the slot where the cursor is currently positioned at.
*
* @param key The new key value for the BIN slot S to be inserted/updated.
* Cannot be partial. For a no-dups DB, it is null if the putMode is
* CURRENT. For dups DBs it is a 2-part key: if the putMode is CURRENT,
* it combines the current primary key of slot S with the original,
* user-provided data; for OVERWRITE and NO_OVERWRITE, it combines the
* original, user-provided key and data. In case of update, "key" must
* compare equal to S.key (otherwise DuplicateDataException is thrown),
* but the 2 keys may not be identical if custom comparators are used.
* So, S.key will actually be replaced by "key".
*
* @param data The new data for the LN associated with the BIN slot. For
* dups DBs it is EMPTY_DUPS_DATA. Note: for dups DBs the original,
* user-provided "data" must not be partial.
*
* @param ln LN to be inserted, if insertion is allowed by putMode. null
* for CURRENT (since insertion is not allowed), not null for other modes.
*/
private OperationStatus putNotify(
DatabaseEntry key,
final DatabaseEntry data,
final LN ln,
final PutMode putMode,
final ReplicationContext repContext) {
final boolean hasUserTriggers = (dbImpl.getTriggers() != null);
final boolean hasAssociations = (dbHandle != null) &&
dbHandle.hasSecondaryOrForeignKeyAssociations();
if (hasAssociations) {
try {
dbImpl.getEnv().getSecondaryAssociationLock().
readLock().lockInterruptibly();
} catch (InterruptedException e) {
throw new ThreadInterruptedException(
dbImpl.getEnv(), e);
}
}
try {
final OperationStatus commitStatus;
final boolean inserted;
DatabaseEntry replaceKey = null;
if (putMode == PutMode.CURRENT) {
if (key == null) {
/*
* This is a no-dups DB. The slot key will not be affected
* by the update. However, if there are indexes/triggers,
* the value of the key is needed to update/apply the
* indexes/triggers after the update. So, it must be
* returned by the putCurrentNoNotify() call below.
* Furthermore, for indexes, the value of the key is needed
* before the update as well, to determine which indexes
* actually must be updated and whether the old data is
* also needed to do the index updates. So, we read the
* value of the key here by what is effectivelly a
* dirty-read.
*/
if (hasAssociations || hasUserTriggers) {
key = new DatabaseEntry();
/*
* Latch this.bin and make "key" point to the
* slot key; then unlatch this.bin.
*/
key.setData(cursorImpl.getCurrentKey());
}
} else {
/*
* This is a dups DB. The slot key must be replaced by the
* given 2-part key. We don't need the pre-update slot key.
*/
replaceKey = key;
}
}
/*
* - oldData: if needed, will be set to the LN data before the
* update.
* - newData: if needed, will be set to the full LN data after
* the update; may be different than newData only if newData
* is partial.
*/
DatabaseEntry oldData = null;
DatabaseEntry newData = null;
/*
* Get secondaries from the association and determine whether the
* old data and new data is needed.
*/
Collection secondaries = null;
if (hasAssociations || hasUserTriggers) {
if (data.getPartial()) {
newData = new DatabaseEntry();
}
if (hasUserTriggers) {
oldData = new DatabaseEntry();
}
if (hasAssociations) {
secondaries = dbHandle.secAssoc.getSecondaries(key);
if (oldData == null &&
SecondaryDatabase.needOldDataForUpdate(secondaries)) {
oldData = new DatabaseEntry();
}
}
}
/* Perform the actual put operation. */
if (putMode == PutMode.CURRENT) {
commitStatus = putCurrentNoNotify(
replaceKey, data, oldData, newData, repContext);
inserted = false;
} else {
final Pair result = putNoNotify(
key, data, ln, putMode, oldData, newData, repContext);
commitStatus = result.first();
inserted = result.second();
}
if (commitStatus != OperationStatus.SUCCESS) {
return commitStatus;
}
/* If returned data is null, this is an insertion not an update. */
if (oldData != null && oldData.getData() == null) {
oldData = null;
}
if (newData == null) {
newData = data;
}
/*
* Update secondaries and notify triggers. Pass newData, not data,
* since data may be partial.
*/
final Locker locker = cursorImpl.getLocker();
if (secondaries != null) {
for (final SecondaryDatabase secDb : secondaries) {
if (inserted || secDb.updateMayChangeSecondary()) {
secDb.updateSecondary(
locker, null, key, oldData, newData);
}
}
}
if (hasUserTriggers) {
TriggerManager.runPutTriggers(
locker, dbImpl, key, oldData, newData);
}
return OperationStatus.SUCCESS;
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
} finally {
if (hasAssociations) {
dbImpl.getEnv().getSecondaryAssociationLock().
readLock().unlock();
}
}
}
/**
* Search for the key and perform insertion or update. Does not notify
* triggers or perform secondary updates. Prevents phantoms.
*
* @param putMode is either OVERWRITE, NO_OEVERWRITE, or BLIND_INSERTION
*
* @param key The new key value for the BIN slot S to be inserted/updated.
* Cannot be partial. For dups DBs it is a 2-part key combining the
* original, user-provided key and data. In case of update, "key" must
* compare equal to S.key (otherwise DuplicateDataException is thrown),
* but the 2 keys may not be identical if custom comparators are used.
* So, S.key will actually be replaced by "key".
*
* @param data In case of update, the new data to (perhaps partially)
* replace the data of the LN associated with the BIN slot. For dups DBs
* it is EMPTY_DUPS_DATA. Note: for dups DBs the original, user-provided
* "data" must not be partial.
*
* @param ln is normally a new LN node that is created for insertion, and
* will be discarded if an update occurs. However, HA will pass an
* existing node.
*
* @param returnOldData To receive, in case of update, the old LN data
* (before the update). It is needed only by DBs with indexes/triggers;
* will be null otherwise.
*
* @param returnNewData To receive the full data of the new or updated LN.
* It is needed only by DBs with indexes/triggers and only if "data" is
* partial; will be null otherwise. Note: "returnNewData" may be different
* than "data" only if "data" is partial.
* @return pair of status and 'inserted' boolean.
*/
private Pair putNoNotify(
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 ln != null;
assert putMode != null;
assert putMode != PutMode.CURRENT;
Locker nextKeyLocker = null;
CursorImpl nextKeyCursor = null;
CursorImpl dup = null;
OperationStatus status = OperationStatus.NOTFOUND;
boolean success = false;
try {
/*
* If other transactions are serializable, lock the next key.
* BUG ???? What if a serializable txn starts after the check
* below returns false? At least, if this cursor is using a
* serializable txn, it SHOULD do next key locking unconditionally.
*/
Locker cursorLocker = cursorImpl.getLocker();
if (dbImpl.getEnv().getTxnManager().
areOtherSerializableTransactionsActive(cursorLocker)) {
/*
* nextKeyCursor is created with retainNonTxnLocks == true,
* and as a result, releaseNonTxnLocks() will not be called
* on nextKeyLocker when nextKeyCursor is reset or closed.
* That's why in the finally clause below we explicitly call
* nextKeyLocker.operationEnd()
*/
nextKeyLocker = BuddyLocker.createBuddyLocker(
dbImpl.getEnv(), cursorLocker);
nextKeyCursor = new CursorImpl(dbImpl, nextKeyLocker);
/* Perform eviction for user cursors. */
nextKeyCursor.setAllowEviction(true);
nextKeyCursor.lockNextKeyForInsert(key);
}
dup = beginMoveCursor(false /*samePosition*/);
/* Perform operation. */
Pair result = dup.insertOrUpdateRecord(
key, data, ln, putMode,
returnOldData, returnNewData, repContext, thrput);
status = result.first();
/* Note that status is used in the finally. */
success = true;
return result;
} finally {
try {
if (dup != null) {
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
if (nextKeyCursor != null) {
nextKeyCursor.close();
}
/* Release the next-key lock. */
if (nextKeyLocker != null) {
nextKeyLocker.operationEnd();
}
} catch (Exception e) {
if (success) {
throw e;
} else {
/*
* Log the exception thrown by the cleanup actions and
* allow the original exception to be thrown
*/
LoggerUtils.traceAndLogException(
dbImpl.getEnv(), "Cursor", "putNoNotify", "", e);
}
}
}
}
/**
* Update the data at the current position. No new LN, dup cursor, or
* phantom handling is needed. Does not interpret duplicates.
*
* @param key The new key value for the BIN slot S to be updated. Cannot
* be partial. For a no-dups DB, it is null. For dups DBs it is a 2-part
* key combining the current primary key of slot S with the original,
* user-provided data. "key" (if not null) must compare equal to S.key
* (otherwise DuplicateDataException is thrown), but the 2 keys may not
* be identical if custom comparators are used. So, S.key will actually
* be replaced by "key".
*
* @param data The new data to (perhaps partially) replace the data of the
* LN associated with the BIN slot. For dups DBs it is EMPTY_DUPS_DATA.
* Note: for dups DBs the original, user-provided "data" must not be
* partial.
*
* @param returnOldData To receive the old LN data (before the update).
* It is needed only by DBs with indexes/triggers; will be null otherwise.
*
* @param returnNewData To receive the full data of the updated LN.
* It is needed only by DBs with indexes/triggers and only if "data" is
* partial; will be null otherwise. Note: "returnNewData" may be different
* than "data" only if "data" is partial.
*/
private OperationStatus putCurrentNoNotify(
final DatabaseEntry key,
final DatabaseEntry data,
final DatabaseEntry returnOldData,
final DatabaseEntry returnNewData,
final ReplicationContext repContext) {
assert data != null;
beginUseExistingCursor();
final OperationStatus status = cursorImpl.updateCurrentRecord(
key, data, returnOldData, returnNewData, repContext, thrput);
endUseExistingCursor();
return status;
}
/**
* Returns the current key and data. There is no need to use a dup cursor
* or prevent phantoms.
*/
OperationStatus getCurrentInternal(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return getCurrentHandleDups(key, data, lockMode);
}
return getCurrentNoDups(key, data, lockMode);
}
}
/**
* Used to lock without returning key/data. When called with
* LockMode.READ_UNCOMMITTED, it simply checks for a deleted record.
*/
OperationStatus checkCurrent(final LockMode lockMode) {
return getCurrentNoDups(null, null, lockMode);
}
/**
* Interpret duplicates for getCurrent operation.
*/
private OperationStatus getCurrentHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationStatus status = getCurrentNoDups(
twoPartKey, NO_RETURN_DATA, lockMode);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
private OperationStatus getCurrentNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
boolean success = false;
OperationStatus status = OperationStatus.KEYEMPTY;
beginUseExistingCursor();
final LockType lockType = getLockType(lockMode, false);
try {
status = cursorImpl.lockAndGetCurrent(
key, data, lockType, lockMode == LockMode.READ_UNCOMMITTED_ALL,
false /*isLatched*/, false /*unlatch*/);
success = true;
} finally {
if (success &&
thrput != null &&
cursorImpl.getBIN() != null &&
cursorImpl.getBIN().isBINDelta()) {
thrput.increment(ThroughputStatGroup.BIN_DELTA_GETS_OFFSET);
}
cursorImpl.releaseBIN();
endUseExistingCursor();
}
return status;
}
/**
* Internal version of getFirst/getLast that does no parameter checking.
* Interprets duplicates.
*/
OperationStatus position(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final boolean first) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return positionHandleDups(key, data, lockMode, first);
}
return positionNoDups(key, data, lockMode, first);
}
}
/**
* Interpret duplicates for getFirst and getLast operations.
*/
private OperationStatus positionHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final boolean first) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationStatus status = positionNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, first);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationStatus positionNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final boolean first) {
try {
if (!isSerializableIsolation(lockMode)) {
return positionAllowPhantoms(
key, data, lockMode, false /*rangeLock*/, first);
}
/*
* Perform range locking to prevent phantoms and handle restarts.
*/
while (true) {
try {
/* Range lock the EOF node before getLast. */
if (!first) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
/* Perform operation. Use a range lock for getFirst. */
final OperationStatus status = positionAllowPhantoms(
key, data, lockMode, first /*rangeLock*/, first);
/*
* Range lock the EOF node when getFirst returns NOTFOUND.
*/
if (first && status != OperationStatus.SUCCESS) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
return status;
} catch (RangeRestartException e) {
continue;
}
}
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Positions without preventing phantoms.
*/
private OperationStatus positionAllowPhantoms(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final boolean rangeLock,
final boolean first) {
assert (key != null && data != null);
OperationStatus status = OperationStatus.NOTFOUND;
final CursorImpl dup = beginMoveCursor(false /*samePosition*/);
try {
/* Search for first or last slot. */
if (!dup.positionFirstOrLast(first)) {
/* Tree is empty. */
status = OperationStatus.NOTFOUND;
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
} else {
/*
* Found and latched first/last BIN in this tree.
* BIN may be empty.
*/
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(1);
}
final LockType lockType = getLockType(lockMode, rangeLock);
final boolean dirtyReadAll =
lockMode == LockMode.READ_UNCOMMITTED_ALL;
status = dup.lockAndGetCurrent(
key, data, lockType, dirtyReadAll,
true /*isLatched*/, false /*unlatch*/);
if (status != OperationStatus.SUCCESS) {
/*
* The BIN may be empty or the slot we're pointing at may
* be deleted.
*/
status = dup.getNext(
key, data, lockType, dirtyReadAll, first,
true /*isLatched*/, null /*rangeConstraint*/);
}
}
} finally {
dup.releaseBIN();
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
return status;
}
/**
* Retrieves the next or previous record. Prevents phantoms.
*/
OperationStatus retrieveNext(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final GetMode getMode) {
if (dbImpl.getSortedDuplicates()) {
return retrieveNextHandleDups(key, data, lockMode, getMode);
}
return retrieveNextNoDups(key, data, lockMode, getMode);
}
/**
* Interpret duplicates for getNext/Prev/etc operations.
*/
private OperationStatus retrieveNextHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final GetMode getMode) {
switch (getMode) {
case NEXT:
case PREV:
return dupsGetNextOrPrev(key, data, lockMode, getMode);
case NEXT_DUP:
return dupsGetNextOrPrevDup(key, data, lockMode, GetMode.NEXT);
case PREV_DUP:
return dupsGetNextOrPrevDup(key, data, lockMode, GetMode.PREV);
case NEXT_NODUP:
return dupsGetNextNoDup(key, data, lockMode);
case PREV_NODUP:
return dupsGetPrevNoDup(key, data, lockMode);
default:
throw EnvironmentFailureException.unexpectedState(
getMode.toString());
}
}
/**
* Interpret duplicates for getNext and getPrev.
*/
private OperationStatus dupsGetNextOrPrev(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final GetMode getMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationStatus status = retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, getMode);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
/**
* Interpret duplicates for getNextDup and getPrevDup.
*
* Move the cursor forward or backward by one record, and check the key
* prefix to detect going out of the bounds of the duplicate set.
*/
private OperationStatus dupsGetNextOrPrevDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final GetMode getMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
final Cursor c = dup(true /*samePosition*/);
try {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationStatus status = c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, getMode);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return OperationStatus.SUCCESS;
} finally {
c.close();
}
}
/**
* Interpret duplicates for getNextNoDup.
*
* Using a special comparator, search for first duplicate in the duplicate
* set following the one for the current key. For details see
* DupKeyData.NextNoDupComparator.
*/
private OperationStatus dupsGetNextNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
final Cursor c = dup(false /*samePosition*/);
try {
c.setNonSticky(true);
final Comparator searchComparator =
new DupKeyData.NextNoDupComparator(
dbImpl.getBtreeComparator());
final OperationStatus status = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.SET_RANGE,
searchComparator);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return OperationStatus.SUCCESS;
} finally {
c.close();
}
}
/**
* Interpret duplicates for getPrevNoDup.
*
* Move the cursor to the first duplicate in the duplicate set, then to the
* previous record. If this fails because all dups at the current position
* have been deleted, move the cursor backward to find the previous key.
*
* Note that we lock the first duplicate to enforce Serializable isolation.
*/
private OperationStatus dupsGetPrevNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
Cursor c = dup(false /*samePosition*/);
try {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
OperationStatus status = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.SET_RANGE,
null /*comparator*/);
if (status == OperationStatus.SUCCESS) {
c.rangeConstraint = null;
status = c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, GetMode.PREV);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return OperationStatus.SUCCESS;
}
} finally {
c.close();
}
c = dup(true /*samePosition*/);
try {
c.setNonSticky(true);
while (true) {
final OperationStatus status =
c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, GetMode.PREV);
if (status != OperationStatus.SUCCESS) {
return status;
}
if (!haveSameDupPrefix(twoPartKey, currentKey)) {
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return OperationStatus.SUCCESS;
}
}
} finally {
c.close();
}
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationStatus retrieveNextNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final GetMode getModeParam) {
final GetMode getMode;
switch (getModeParam) {
case NEXT_DUP:
case PREV_DUP:
return OperationStatus.NOTFOUND;
case NEXT_NODUP:
getMode = GetMode.NEXT;
break;
case PREV_NODUP:
getMode = GetMode.PREV;
break;
default:
getMode = getModeParam;
}
try {
if (!isSerializableIsolation(lockMode)) {
/*
* No need to prevent phantoms.
*/
assert (getMode == GetMode.NEXT || getMode == GetMode.PREV);
final CursorImpl dup = beginMoveCursor(true /*samePosition*/);
OperationStatus status = OperationStatus.NOTFOUND;
try {
status = dup.getNext(
key, data, getLockType(lockMode, false),
lockMode == LockMode.READ_UNCOMMITTED_ALL,
getMode.isForward(), false /*isLatched*/,
rangeConstraint);
return status;
} finally {
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
}
/*
* Perform range locking to prevent phantoms and handle restarts.
*/
while (true) {
try {
/* Get a range lock for 'prev' operations. */
if (!getMode.isForward()) {
rangeLockCurrentPosition();
}
/* Use a range lock if performing a 'next' operation. */
final LockType lockType =
getLockType(lockMode, getMode.isForward());
/* Do not modify key/data params until SUCCESS. */
final DatabaseEntry tryKey = cloneEntry(key);
final DatabaseEntry tryData = cloneEntry(data);
/* Perform the operation with a null rangeConstraint. */
OperationStatus status = retrieveNextCheckForInsertion(
tryKey, tryData, lockType, getMode);
if (getMode.isForward() &&
status != OperationStatus.SUCCESS) {
/* NEXT: lock the EOF node. */
cursorImpl.lockEof(LockType.RANGE_READ);
}
/* Finally check rangeConstraint. */
if (status == OperationStatus.SUCCESS &&
!checkRangeConstraint(tryKey)) {
status = OperationStatus.NOTFOUND;
}
/*
* Only overwrite key/data on SUCCESS, after all locking.
*/
if (status == OperationStatus.SUCCESS) {
setEntry(tryKey, key);
setEntry(tryData, data);
}
return status;
} catch (RangeRestartException e) {
continue;
}
}
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* For 'prev' operations, upgrades to a range lock at the current position.
* If there are no records at the current position, get a range lock on the
* next record or, if not found, on the logical EOF node. Do not modify
* the current cursor position, use a separate cursor.
*/
private void rangeLockCurrentPosition() {
final DatabaseEntry tempKey = new DatabaseEntry();
final DatabaseEntry tempData = new DatabaseEntry();
tempKey.setPartial(0, 0, true);
tempData.setPartial(0, 0, true);
OperationStatus status;
CursorImpl dup = cursorImpl.cloneCursor(true /*samePosition*/);
try {
status = dup.lockAndGetCurrent(
tempKey, tempData, LockType.RANGE_READ);
if (status != OperationStatus.SUCCESS) {
while (true) {
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
status = dup.getNext(
tempKey, tempData, LockType.RANGE_READ,
false /*dirtyReadAll*/, true /*forward*/,
false /*isLatched*/, null /*rangeConstraint*/);
if (cursorImpl.checkForInsertion(GetMode.NEXT, dup)) {
dup.close(cursorImpl);
dup = cursorImpl.cloneCursor(true /*samePosition*/);
continue;
}
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
break;
}
}
} finally {
dup.close(cursorImpl);
}
if (status != OperationStatus.SUCCESS) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
}
/**
* Retrieves and checks for insertions, for serializable isolation.
*/
private OperationStatus retrieveNextCheckForInsertion(
final DatabaseEntry key,
final DatabaseEntry data,
final LockType lockType,
final GetMode getMode) {
assert (key != null && data != null);
assert (getMode == GetMode.NEXT || getMode == GetMode.PREV);
while (true) {
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
/*
* Force cloning of the cursor because the caller may need to
* restart the operation from the previous position. In addition,
* checkForInsertion depends on having two CursorImpls for
* comparison, at the old and new position.
*/
final CursorImpl dup = beginMoveCursor(
true /*samePosition*/, true /*forceClone*/);
boolean doEndMoveCursor = true;
try {
final OperationStatus status = dup.getNext(
key, data, lockType, false /*dirtyReadAll*/,
getMode.isForward(), false /*isLatched*/,
null /*rangeConstraint*/);
if (!cursorImpl.checkForInsertion(getMode, dup)) {
doEndMoveCursor = false;
endMoveCursor(dup, status == OperationStatus.SUCCESS);
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
return status;
}
} finally {
if (doEndMoveCursor) {
endMoveCursor(dup, false);
}
}
}
}
private long skipInternal(
final long maxCount,
final boolean forward,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final LockType lockType = getLockType(lockMode, false);
synchronized (getTxnSynchronizer()) {
checkTxnState();
while (true) {
/*
* Force cloning of the cursor since we may need to restart
* the operation at the previous position.
*/
final CursorImpl dup = beginMoveCursor(
true /*samePosition*/, true /*forceClone*/);
boolean success = false;
try {
final long count = dup.skip(forward, maxCount,
null /*rangeConstraint*/);
if (count <= 0) {
return 0;
}
final OperationStatus status =
getCurrentWithCursorImpl(dup, key, data, lockType);
if (status == OperationStatus.KEYEMPTY) {
/* Retry if deletion occurs while unlatched. */
continue;
}
success = true;
return count;
} finally {
endMoveCursor(dup, success);
}
}
}
}
/**
* Convenience method that does lockAndGetCurrent, with and without dups,
* using a CursorImpl. Does no setup or save/restore of cursor state.
*/
private OperationStatus getCurrentWithCursorImpl(
final CursorImpl c,
final DatabaseEntry key,
final DatabaseEntry data,
final LockType lockType) {
if (!dbImpl.getSortedDuplicates()) {
return c.lockAndGetCurrent(key, data, lockType);
}
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationStatus status =
c.lockAndGetCurrent(twoPartKey, NO_RETURN_DATA, lockType);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
/**
* Performs search by key, data, or both. Prevents phantoms.
*/
OperationStatus search(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final SearchMode searchMode) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
switch (searchMode) {
case SET:
return dupsGetSearchKey(key, data, lockMode);
case SET_RANGE:
return dupsGetSearchKeyRange(key, data, lockMode);
case BOTH:
return dupsGetSearchBoth(key, data, lockMode);
case BOTH_RANGE:
return dupsGetSearchBothRange(key, data, lockMode);
default:
throw EnvironmentFailureException.unexpectedState(
searchMode.toString());
}
}
return searchNoDups(
key, data, lockMode, searchMode, null /*comparator*/);
}
}
/**
* Version of search that does not interpret duplicates. Used for
* replication stream replay. Prevents phantoms.
*/
OperationStatus searchForReplay(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final SearchMode searchMode) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
return searchNoDups(
key, data, lockMode, searchMode, null /*comparator*/);
}
}
/**
* Interpret duplicates for getSearchKey operation.
*
* Use key as prefix to find first duplicate using a range search. Compare
* result to prefix to see whether we went out of the bounds of the
* duplicate set, i.e., whether NOTFOUND should be returned.
*
* Even if the user-provided "key" exists in the DB, the twoPartKey built
* here out of "key" compares < any of the BIN-slot keys that comprise the
* duplicates-set of "key". So there is no way to get an exact key match
* by a BTree search. Instead, we do a constrained range search: we forbid
* the cursor to advance past the duplicates-set of "key" by using an
* appropriate range constraint.
*/
private OperationStatus dupsGetSearchKey(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry(
DupKeyData.makePrefixKey(key.getData(),
key.getOffset(),
key.getSize()));
final RangeConstraint savedRangeConstraint = rangeConstraint;
try {
setPrefixConstraint(this, key);
final OperationStatus status = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.SET_RANGE,
null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return OperationStatus.NOTFOUND;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
} finally {
rangeConstraint = savedRangeConstraint;
}
}
/**
* Interpret duplicates for getSearchKeyRange operation.
*
* Do range search for key prefix.
*/
private OperationStatus dupsGetSearchKeyRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry(
DupKeyData.makePrefixKey(key.getData(),
key.getOffset(),
key.getSize()));
final OperationStatus status = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.SET_RANGE,
null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
/**
* Interpret duplicates for getSearchBoth operation.
*
* Do exact search for combined key.
*/
private OperationStatus dupsGetSearchBoth(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
final OperationStatus status = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.BOTH,
null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return status;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
}
/**
* Interpret duplicates for getSearchBothRange operation.
*
* Do range search for combined key. Compare result to prefix to see
* whether we went out of the bounds of the duplicate set, i.e., whether
* NOTFOUND should be returned.
*/
private OperationStatus dupsGetSearchBothRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
final RangeConstraint savedRangeConstraint = rangeConstraint;
try {
setPrefixConstraint(this, key);
final OperationStatus status = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, SearchMode.SET_RANGE,
null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return OperationStatus.NOTFOUND;
}
DupKeyData.split(twoPartKey, key, data);
return OperationStatus.SUCCESS;
} finally {
rangeConstraint = savedRangeConstraint;
}
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationStatus searchNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final SearchMode searchMode,
final Comparator comparator) {
/*
* searchMode cannot be BOTH_RANGE, because for non-dups DBs BOTH_RANGE
* is converted to BOTH, and for dup DBs BOTH_RANGE is converted to
* SET_RANGE.
*/
assert(searchMode != SearchMode.BOTH_RANGE);
try {
if (!isSerializableIsolation(lockMode)) {
if (searchMode.isExactSearch()) {
assert(comparator == null);
return searchExact(key, data, lockMode, searchMode);
}
while (true) {
try {
return searchRange(key, data, lockMode, comparator);
} catch (RangeRestartException e) {
continue;
}
}
}
/*
* Perform range locking to prevent phantoms and handle restarts.
*/
while (true) {
OperationStatus result;
try {
/*
* Do not use a range lock for the initial search, but
* switch to a range lock when advancing forward.
*/
final LockType searchLockType;
final LockType advanceLockType;
searchLockType = getLockType(lockMode, false);
advanceLockType = getLockType(lockMode, true);
/* Do not modify key/data params until SUCCESS. */
final DatabaseEntry tryKey = cloneEntry(key);
final DatabaseEntry tryData = cloneEntry(data);
/*
* If the searchMode is SET or BOTH (i.e., we are looking
* for an exact key match) we do a artificial range search
* to range lock the next key. If an exact match for the
* search key is not found, we still want to advance to the
* next slot in order to RANGE lock it, but contrary to a
* normal range scan, we want to return NOTFOUND to the
* caller and we want to consider this as an operation
* failure so that the position of the cursor won't change,
* even though we advance to the following slot in order
* to range lock it. We achieve this by passing true for
* the checkForExactKey parameter.
*/
result = searchRangeSerializable(
tryKey, tryData, searchLockType, advanceLockType,
comparator, searchMode);
if (result == OperationStatus.SUCCESS) {
setEntry(tryKey, key);
setEntry(tryData, data);
}
return result;
} catch (RangeRestartException e) {
continue;
}
}
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Search for a "valid" BIN slot whose key is equal to the given "key".
* A slot is "valid" only if after locking it, neither its PD nor it KD
* flags are set. If no slot exists, return NOTFOUND. Otherwise, copy
* the key and the LN of the found slot into "key" and "data" respectively
* (if "key"/"data" request so) and return either NOTFOUND if searchMode
* == BOTH and "data" does not match the LN of the found slot, or SUCCESS
* otherwise.
*
* Note: On return from this method no latches are held by this cursor.
*
* Note: If the method returns NOTFOUND or raises an exception, any non-
* transactional locks acquired by this method are released.
*
* Note: On SUCCESS, if this is a sticky cursor, any non-transactional
* locks held by this cursor before calling this method are released.
*
* Note: this method is never called when the desired isolation is
* "serializable", because in order to do next-slot-locking, a range
* search is required.
*
* @param key It is used as the search key, as well as to receive the key
* of the BIN slot found by this method, if any. If the DB contains
* duplicates, the key is in the "two-part-key" format (see
* dbi/DupKeyData.java) so that it can be compared with the two-part keys
* stored in the BTree (which contain both a primary key and a data
* portion). The search key itself may or may not contain a data portion.
*
* @param data A DatabaseEntry to compare against the LN of the slot found
* by the search (if searchMode == BOTH) as well as to receive the data of
* that LN. If the DB contains duplicates, it is equal to NO_RETURN_DATA,
* because the LN will be emtpy (the full record is contained in the key).
*
* @param searchMode Either SET or BOTH.
*
* @return NOTFOUND if (a) no valid slot exists with a key == the search
* key, or (b) searchMode == BOTH and "data" does not match the LN of the
* found slot. SUCCESS otherwise.
*/
private OperationStatus searchExact(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final SearchMode searchMode) {
assert(key != null && data != null);
assert(searchMode == SearchMode.SET || searchMode == SearchMode.BOTH);
boolean success = false;
OperationStatus status = OperationStatus.NOTFOUND;
DatabaseEntry origData = new DatabaseEntry(
data.getData(), data.getOffset(), data.getSize());
final boolean dataRequested =
!data.getPartial() || data.getPartialLength() != 0;
final LockType lockType = getLockType(lockMode, false);
final boolean dirtyReadAll =
lockMode == LockMode.READ_UNCOMMITTED_ALL;
final CursorImpl dup = beginMoveCursor(false /*samePosition*/);
try {
/*
* Search for a BIN slot whose key is == the search key. If such a
* slot is found, lock it and check whether it is valid.
*/
if (dup.searchExact(
key, lockType, dirtyReadAll, dataRequested) == null) {
success = true;
return status;
}
/*
* The search found and locked a valid BIN slot whose key is
* equal to the search key. Copy into "data" the LN of this
* slot (if "data" requests so). Also if searchMode is BOTH,
* copy into "key" the key of the found slot (it may be
* different than the given key if a partial key comparator
* is used). Why don't we do this for SET as well ????
*/
dup.getCurrent((searchMode == SearchMode.SET ? null : key), data);
/* Check for data match, if asked so. */
if (searchMode == SearchMode.BOTH) {
if (checkDataMatch(origData, data)) {
status = OperationStatus.SUCCESS;
} else {
status = OperationStatus.NOTFOUND;
}
} else {
status = OperationStatus.SUCCESS;
}
success = true;
} finally {
if (success &&
thrput != null &&
dup.getBIN() != null &&
dup.getBIN().isBINDelta()) {
thrput.increment(ThroughputStatGroup.BIN_DELTA_GETS_OFFSET);
}
dup.releaseBIN();
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
return status;
}
/**
* Search for the 1st "valid" BIN slot whose key is in the range [K1, K2),
* where (a) K1 is a given key, (b) K2 is determined by
* this.rangeConstraint, or is +INFINITY if this.rangeConstraint == null,
* and (c) a slot is "valid" only if after locking it, neither its PD nor
* its KD flags are set.
*
* If such a slot is found, copy its key and its associated LN into "key"
* and "data" respectively (if "key"/"data" request so). Note that the
* fact that the slot is valid implies that it has been locked.
*
* Note: On return from this method no latches are held by this cursor.
*
* Note: If the method returns NOTFOUND or raises an exception, any non-
* transactional locks acquired by this method are released.
*
* Note: On SUCCESS, if this is a sticky cursor, any non-transactional
* locks held by this cursor before calling this method are released.
*
* @param key It is used as the search key, as well as to receive the key
* of the BIN slot found by this method, if any. If the DB contains
* duplicates, the key is in the "two-part-key" format (see
* dbi/DupKeyData.java) so that it can be compared with the two-part keys
* stored in the BTree (which contain both a primary key and a data
* portion). The search key itself may or may not contain a data portion.
*
* @param data A DatabaseEntry to receive the data of the LN associated
* with the found slot, if any. If the DB contains duplicates, it is equal
* to NO_RETURN_DATA, because the LN will be empty (the full record is
* contained in the key).
*
* @param comparator Comparator to use to compare the search key against
* the BTree keys.
*
* @return NOTFOUND if no valid slot exists in the [K1, K2) range; SUCCESS
* otherwise.
*
* @throws RangeRestartException if the search should be restarted by the
* caller.
*/
private OperationStatus searchRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
Comparator comparator)
throws RangeRestartException {
assert(key != null && data != null);
boolean success = false;
boolean incStats = (thrput != null);
OperationStatus status = OperationStatus.NOTFOUND;
final LockType lockType = getLockType(lockMode, false);
final boolean dirtyReadAll =
lockMode == LockMode.READ_UNCOMMITTED_ALL;
final CursorImpl dup = beginMoveCursor(false /*samePosition*/);
try {
/* Search for a BIN slot whose key is the max key <= K1. */
final int searchResult = dup.searchRange(key, comparator);
if ((searchResult & CursorImpl.FOUND) == 0) {
/* The tree is completely empty (has no nodes at all) */
success = true;
return status;
}
/*
* The search positioned dup on the BIN that should contain K1
* and this BIN is now latched. If the BIN does contain K1,
* dup.index points to K1's slot. Otherwise, dup.index points
* to the right-most slot whose key is < K1 (or dup.index is -1
* if K1 is < than all keys in the BIN). Note: if foundLast is
* true, dup is positioned on the very last slot of the BTree.
*/
final boolean exactKeyMatch =
((searchResult & CursorImpl.EXACT_KEY) != 0);
final boolean foundLast =
((searchResult & CursorImpl.FOUND_LAST) != 0);
/*
* If we found K1, lock the slot and check whether it is valid.
* If so, copy out its key and associated LN.
*/
if (exactKeyMatch) {
status = dup.lockAndGetCurrent(
key, data, lockType, dirtyReadAll,
true /*isLatched*/, false /*unlatch*/);
}
/*
* If K1 is not in the BTree or its slot is not valid, advance
* dup until (a) the rangeConstraint (if any) returns false, or
* (b) there are no more slots, or (c) we find a valid slot. If
* (c), check whether the slot key is < K1. This can happen if
* K1 was not in the BTree (so dup is now on a key K0 < K1) and
* another txn inserted new keys < K1 while we were trying to
* advance dup. If so, a RestartException is thrown. Otherwise,
* the slot key and LN are copied into "key" and "data" (if
* "key"/"data" request so).
*/
if (!exactKeyMatch || status == OperationStatus.KEYEMPTY) {
status = OperationStatus.NOTFOUND;
if (!foundLast) {
status = searchRangeAdvanceAndCheckKey(
dup, key, data, lockType, dirtyReadAll,
comparator, rangeConstraint);
/*
* Don't inc thput stats because the bin is released by
* searchRangeAdvanceAndCheckKey(). This is ok because
* searchRangeAdvanceAndCheckKey() will cause mutation
* to full bin anyway.
*/
incStats = false;
}
}
success = true;
} finally {
if (success &&
incStats &&
dup.getBIN() != null &&
dup.getBIN().isBINDelta()) {
thrput.increment(ThroughputStatGroup.BIN_DELTA_GETS_OFFSET);
}
dup.releaseBIN();
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
return status;
}
/**
* Search for the 1st "valid" BIN slot whose key is in the range [K1, K2),
* where (a) K1 is a given key, (b) K2 is determined by
* this.rangeConstraint, or is +INFINITY if this.rangeConstraint == null,
* and (c) a slot is "valid" only if after locking it, neither its PD nor
* its KD flags are set.
*
* If such a slot is found, copy its key and it associated LN into "key"
* and "data" respectively (if "key"/"data" request so). Note that the
* fact that the slot is valid implies that it has been locked. If the
* key of the found slot is == K1, it is locked in a non-range lock. If
* the key is > K1, the slot is locked in a range lock.
*
* If no slot is found, lock the EOF with a range lock.
*
* Note: On return from this method no latches are held by this cursor.
*
* Note: This Cursor's locker should be a Txn, so there are no non-
* transactional locks to be released.
*
* @param key It is used as the search key, as well as to receive the key
* of the BIN slot found by this method, if any. If the DB contains
* duplicates, the key is in the "two-part-key" format (see
* dbi/DupKeyData.java) so that it can be compared with the two-part keys
* stored in the BTree (which contain both a primary key and a data
* portion). The search key itself may or may not contain a data portion.
*
* @param data A DatabaseEntry to receive the data of the LN associated
* with the found slot, if any. If the DB contains duplicates, it is equal
* to NO_RETURN_DATA, because the LN will be emtpy (the full record is
* contained in the key).
*
* @param searchLockType LockType to use for locking the slot if its key
* is == search key. Normally, this is a READ or WRITE lock.
*
* @param advanceLockType LockType to use for locking the slot if its key
* is > search key. Normally, this is a READ_RANGE or WRITE_RANGE lock.
*
* @param comparator Comparator to use to compare the search key against
* the BTree keys.
*
* @param searchMode If SET or BOTH, we are actually looking for an exact
* match on K1. If so and K1 is not in the BTree, we want the cursor to
* advance temporarily to the next slot in order to range-lock it, but
* then return NOTFOUND. NOTFOUND is returned also if K1 is found, but
* searchMode is BOTH and the data associated with the K1 slot does not
* match the given data.
*
* @return NOTFOUND if no valid slot exists in the [K1, K2) range, or
* checkForExactKey == true and the key of the found slot is > K1; SUCCESS
* otherwise.
*
* @throws RangeRestartException if the search should be restarted by the
* caller.
*/
private OperationStatus searchRangeSerializable(
final DatabaseEntry key,
final DatabaseEntry data,
final LockType searchLockType,
final LockType advanceLockType,
final Comparator comparator,
final SearchMode searchMode)
throws RangeRestartException {
assert(key != null && data != null);
boolean success = false;
boolean incStats = (thrput != null);
OperationStatus status = OperationStatus.NOTFOUND;
boolean exactSearch = searchMode.isExactSearch();
boolean keyChange = false;
boolean mustLockEOF = false;
DatabaseEntry origData = null;
if (exactSearch) {
origData = new DatabaseEntry(
data.getData(), data.getOffset(), data.getSize());
}
final CursorImpl dup = beginMoveCursor(false /*samePosition*/);
try {
/* Search for a BIN slot whose key is the max key <= K1. */
final int searchResult = dup.searchRange(key, comparator);
if ((searchResult & CursorImpl.FOUND) != 0) {
/*
* The search positioned dup on the BIN that should contain K1
* and this BIN is now latched. If the BIN does contain K1,
* dup.index points to K1's slot. Otherwise, dup.index points
* to the right-most slot whose key is < K1 (or dup.index is -1
* if K1 is < than all keys in the BIN). Note: if foundLast is
* true, dup is positioned on the very last slot of the BTree.
*/
final boolean exactKeyMatch =
((searchResult & CursorImpl.EXACT_KEY) != 0);
final boolean foundLast =
((searchResult & CursorImpl.FOUND_LAST) != 0);
/*
* If we found K1, lock the slot and check whether it is valid.
* If so, copy out its key and associated LN.
*/
if (exactKeyMatch) {
status = dup.lockAndGetCurrent(
key, data, searchLockType, false /*dirtyReadAll*/,
true /*isLatched*/, false /*unlatch*/);
}
/*
* If K1 is not in the BTree or its slot is not valid, advance
* dup until (a) there are no more slots, or (b) we find a
* valid slot. If (b), check whether the slot key is < K1. This
* can happen if K1 was not in the BTree (so dup is now on a
* key K0 < K1) and another txn inserted new keys < K1 while we
* were trying to advance dup. If so, a RestartException is
* thrown. Otherwise, the slot key and LN are copied into "key"
* and "data" (if "key"/"data" request so).
*/
if (!exactKeyMatch || status == OperationStatus.KEYEMPTY) {
status = OperationStatus.NOTFOUND;
if (!foundLast) {
status = searchRangeAdvanceAndCheckKey(
dup, key, data, advanceLockType,
false /*dirtyReadAll*/, comparator,
null /*rangeConstraint*/);
keyChange = (status == OperationStatus.SUCCESS);
incStats = false;
}
mustLockEOF = (status != OperationStatus.SUCCESS);
}
/*
* Consider this search op a failure if we are actually looking
* for an exact key match and we didn't find the search key.
*/
if (status == OperationStatus.SUCCESS && exactSearch) {
if (keyChange) {
status = OperationStatus.NOTFOUND;
} else if (searchMode == SearchMode.BOTH) {
if (checkDataMatch(origData, data)) {
status = OperationStatus.SUCCESS;
} else {
status = OperationStatus.NOTFOUND;
}
}
}
/* Finally check rangeConstraint. */
if (status == OperationStatus.SUCCESS &&
!exactSearch &&
!checkRangeConstraint(key)) {
status = OperationStatus.NOTFOUND;
}
} else {
/* The tree is completely empty (has no nodes at all) */
mustLockEOF = true;
}
success = true;
} finally {
if (success &&
incStats &&
dup.getBIN() != null &&
dup.getBIN().isBINDelta()) {
thrput.increment(ThroughputStatGroup.BIN_DELTA_GETS_OFFSET);
}
dup.releaseBIN();
endMoveCursor(dup, status == OperationStatus.SUCCESS);
}
/*
* Lock the EOF node if no records follow the key.
*
* BUG ????? At this point no latches are held by this cursor, so
* another transaction can insert new slots at the end of the DB
* and then commit. I think the fix is to request the eof lock in
* non-blocking mode with the BIN latched and restart the search
* if the lock is denied.
*/
if (mustLockEOF) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
return status;
}
/*
* Helper method for searchRange and searchRangeSerializable
*
* @throws RangeRestartException if the search should be restarted by the
* caller.
*/
private OperationStatus searchRangeAdvanceAndCheckKey(
final CursorImpl dup,
final DatabaseEntry key,
final DatabaseEntry data,
final LockType lockType,
final boolean dirtyReadAll,
Comparator comparator,
final RangeConstraint rangeConstraint)
throws RangeRestartException {
if (comparator == null) {
comparator = dbImpl.getKeyComparator();
}
DatabaseEntry origKey = new DatabaseEntry(
key.getData(), key.getOffset(), key.getSize());
DatabaseEntry nextKey = key;
if (key.getPartial()) {
nextKey = new DatabaseEntry(
key.getData(), key.getOffset(), key.getSize());
}
OperationStatus status = dup.getNext(
nextKey, data, lockType, dirtyReadAll, true /*forward*/,
true /*isLatched*/, rangeConstraint);
/*
* Check whether the dup.getNext() landed on slot whose key is < K1.
* This can happen if K1 was not in the BTree (so before dup.getNext()
* is called, dup is on a key K0 < K1) and another txn inserted new
* keys < K1 while we were trying to advance dup. Such an insertion is
* possible because if dup must move to the next BIN, it releases all
* latches for a while, so the inserter can come in, split the current
* BIN and insert its keys on the right split-sibling. Finally, dup
* moves to the right split-sibling and lands on a wrong slot.
*/
if (status == OperationStatus.SUCCESS) {
int c = Key.compareKeys(nextKey, origKey, comparator);
if (c < 0) {
key.setData(origKey.getData(),
origKey.getOffset(),
origKey.getSize());
throw new RangeRestartException();
} else if (key.getPartial()) {
LN.setEntry(key, nextKey);
}
}
return status;
}
/**
* For a non-duplicates database, the data must match exactly when
* getSearchBoth or getSearchBothRange is called.
*/
private boolean checkDataMatch(
DatabaseEntry data1,
DatabaseEntry data2) {
final int size1 = data1.getSize();
final int size2 = data2.getSize();
if (size1 != size2) {
return false;
}
return Key.compareUnsignedBytes(
data1.getData(), data1.getOffset(), size1,
data2.getData(), data2.getOffset(), size2) == 0;
}
/**
* Counts duplicates without parameter checking. No need to dup the cursor
* because we never change the position.
*/
int countInternal() {
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return countHandleDups();
}
return countNoDups();
}
}
/**
* Count duplicates by skipping over the entries in the dup set key range.
*/
private int countHandleDups() {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
final Cursor c = dup(false /*samePosition*/);
try {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
/* Move cursor to first key in this dup set. */
OperationStatus status = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
SearchMode.SET_RANGE, null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return 0;
}
/* Skip over entries in the dup set. */
long count = 1 + c.cursorImpl.skip(
true /*forward*/, 0 /*maxCount*/, c.rangeConstraint);
if (count > Integer.MAX_VALUE) {
throw new IllegalStateException(
"count exceeded integer size: " + count);
}
return (int) count;
} finally {
c.close();
}
}
/**
* When there are no duplicates, the count is either 0 or 1, and is very
* cheap to determine.
*/
private int countNoDups() {
try {
beginUseExistingCursor();
final OperationStatus status = cursorImpl.lockAndGetCurrent(
null /*foundKey*/, null /*foundData*/, LockType.NONE);
endUseExistingCursor();
return (status == OperationStatus.SUCCESS) ? 1 : 0;
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Estimates duplicate count without parameter checking. No need to dup
* the cursor because we never change the position.
*/
long countEstimateInternal() {
if (dbImpl.getSortedDuplicates()) {
return countEstimateHandleDups();
}
return countNoDups();
}
/**
* Estimate duplicate count using the end point positions.
*/
private long countEstimateHandleDups() {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
final Cursor c1 = dup(false /*samePosition*/);
try {
c1.setNonSticky(true);
setPrefixConstraint(c1, currentKey);
/* Move cursor 1 to first key in this dup set. */
OperationStatus status = c1.searchNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
SearchMode.SET_RANGE, null /*comparator*/);
if (status != OperationStatus.SUCCESS) {
return 0;
}
/* Move cursor 2 to first key in the following dup set. */
final Cursor c2 = c1.dup(true /*samePosition*/);
try {
c2.setNonSticky(true);
status = c2.dupsGetNextNoDup(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED);
final boolean c2Inclusive;
if (status == OperationStatus.SUCCESS) {
c2Inclusive = false;
} else {
c2Inclusive = true;
/*
* There is no following dup set. Go to the last record in
* the database. If we land on a newly inserted dup set,
* go to the prev record until we find the last record in
* the original dup set.
*/
status = c2.positionNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
false /*first*/);
if (status != OperationStatus.SUCCESS) {
return 0;
}
while (!haveSameDupPrefix(twoPartKey, currentKey)) {
status = c2.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA,
LockMode.READ_UNCOMMITTED, GetMode.PREV);
if (status != OperationStatus.SUCCESS) {
return 0;
}
}
}
/* Estimate the count between the two cursor positions. */
return CountEstimator.count(
dbImpl, c1.cursorImpl, true, c2.cursorImpl, c2Inclusive);
} finally {
c2.close();
}
} finally {
c1.close();
}
}
/**
* Reads the primary data for a primary key that was retrieved from a
* secondary DB via this secondary cursor ("this" may also be a regular
* Cursor in the role of a secondary cursor). This method is in the
* Cursor class, rather than in SecondaryCursor, to support joins with
* plain Cursors [#21258].
*
* When SUCCESS is returned by this method, the caller should return
* SUCCESS. When KEYEMPTY is returned, the caller should treat this as a
* deleted record and either skip the record (in the case of position,
* search, and retrieveNext) or return KEYEMPTY (in the case of
* getCurrent). KEYEMPTY is only returned when read-uncommitted is used.
*
* @param priDb primary database as input.
*
* @param key secondary key as input.
*
* @param pKey key as input.
*
* @param data the data returned as output.
*
* @param lockMode the lock mode to use for the primary read; if null, use
* the default lock mode.
*
* @param secDirtyRead whether we used dirty-read for reading the secondary
* record. It is true if the user's configured isolation mode (or lockMode
* param) is dirty-read, or we used dirty-read for the secondary read to
* avoid deadlocks (this is done when the user's isolation mode is
* READ_COMMITTED or REPEATABLE_READ).
*
* @param lockPrimaryOnly If false, then we are not using dirty-read for
* secondary deadlock avoidance. If true, this secondary cursor's
* reference to the primary will be checked after the primary record has
* been locked.
*
* @return status plus primary record version. The status is SUCCESS if
* the primary was read successfully, or KEYEMPTY if using read-uncommitted
* and the primary has been deleted, or KEYEMPTY if using read-uncommitted
* and the primary has been updated and no longer contains the secondary
* key.
*
* @throws SecondaryIntegrityException to indicate a corrupt secondary
* reference if the primary record is not found and read-uncommitted is not
* used.
*/
Pair readPrimaryAfterGet(
final Database priDb,
final DatabaseEntry key,
final DatabaseEntry pKey,
DatabaseEntry data,
final LockMode lockMode,
final boolean secDirtyRead,
final boolean lockPrimaryOnly) {
final boolean priDirtyRead = isReadUncommittedMode(lockMode);
/*
* If we only lock the primary (and check the sec cursor), we must be
* using sec dirty-read for deadlock avoidance (whether or not the user
* requested dirty-read). Otherwise, we should be using sec dirty-read
* iff the user requested it.
*/
if (lockPrimaryOnly) {
assert secDirtyRead;
} else {
assert secDirtyRead == priDirtyRead;
}
/*
* There is no need to read the primary if no data is requested. In
* this case a lock on the secondary has been acquired (if the caller
* did not specify dirty-read).
*/
if (data.getPartial() && data.getPartialLength() == 0) {
data.setData(LogUtils.ZERO_LENGTH_BYTE_ARRAY);
return new Pair<>(OperationStatus.SUCCESS, null);
}
/*
* If partial data is requested along with read-uncommitted, then we
* must read all data in order to call the key creator below. [#14966]
*/
DatabaseEntry copyToPartialEntry = null;
if (priDirtyRead && data.getPartial()) {
copyToPartialEntry = data;
data = new DatabaseEntry();
}
/*
* Do not release non-transactional locks when reading the primary
* cursor. They are held until all locks for this operation are
* released by the secondary cursor. [#15573]
*/
final CursorImpl priCursor = new CursorImpl(
priDb.getDatabaseImpl(), cursorImpl.getLocker(),
true /*retainNonTxnLocks*/, false /*isSecondaryCursor*/);
try {
/*
* Do not rely on a default/null lock mode for dirty-read, since
* the primary cursor will not have the same default lock mode.
*/
final LockMode priLockMode;
if (priDirtyRead) {
if (lockMode == LockMode.READ_UNCOMMITTED_ALL) {
priLockMode = LockMode.READ_UNCOMMITTED_ALL;
} else {
priLockMode = LockMode.READ_UNCOMMITTED;
}
} else {
priLockMode = lockMode;
}
final LockType priLockType = getLockType(priLockMode, false);
final boolean dirtyReadAll =
priLockMode == LockMode.READ_UNCOMMITTED_ALL;
final boolean dataRequested =
!data.getPartial() || data.getPartialLength() != 0;
LockStanding priLockStanding = priCursor.searchExact(
pKey, priLockType, dirtyReadAll, dataRequested);
if (priLockStanding != null) {
priCursor.getCurrent(null, data);
}
priCursor.releaseBIN();
if (priLockStanding != null && lockPrimaryOnly) {
if (!checkReferenceToPrimary(pKey, priLockType)) {
priCursor.revertLock(priLockStanding);
priLockStanding = null;
}
}
if (priLockStanding == null) {
/*
* If using read-uncommitted and the primary is deleted, the
* primary must have been deleted after reading the secondary.
* We cannot verify this by checking if the secondary is
* deleted, because it may have been reinserted. Instead, we
* simply return KEYEMPTY to skip this record. [#22603]
*/
if (secDirtyRead) {
return new Pair<>(OperationStatus.KEYEMPTY, null);
}
/*
* When the primary is deleted, secondary keys are deleted
* first. So if the above check fails, we know the secondary
* reference is corrupt and retries will not be productive.
*/
throw dbHandle.secondaryRefersToMissingPrimaryKey(
cursorImpl.getLocker(), key, pKey);
}
/*
* If using read-uncommitted and the primary was found, check to
* see if primary was updated so that it no longer contains the
* secondary key. If it has been, return KEYEMPTY.
*/
if (priDirtyRead && checkForPrimaryUpdate(key, pKey, data)) {
return new Pair<>(OperationStatus.KEYEMPTY, null);
}
/*
* When a partial entry was requested but we read all the data,
* copy the requested partial data to the caller's entry. [#14966]
*/
if (copyToPartialEntry != null) {
LN.setEntry(copyToPartialEntry, data.getData());
}
return new Pair<>(
OperationStatus.SUCCESS,
priCursor.getCachedRecordVersion());
} finally {
priCursor.close();
}
}
/**
* Checks whether this secondary cursor still refers to the primary key.
*
* This is used for deadlock avoidance with secondary DBs. The initial
* secondary index read is done without locking. After the primary has
* been locked, we check here to insure that the primary/secondary
* relationship is still in place. If the secondary DB has duplicates, the
* key contains the sec/pri relationship and the presence of the record is
* sufficient to insure the sec/pri relationship. However, if the
* secondary DB does not allow duplicates, then the primary key (the data
* of the secondary record) must be compared to the original search key.
*/
private boolean checkReferenceToPrimary(
final DatabaseEntry matchKey,
final LockType lockType) {
assert lockType != LockType.NONE;
boolean refersToPrimary = true;
if (!cursorImpl.hasDuplicates()) {
final DatabaseEntry priData = new DatabaseEntry();
/* get the primary key value without taking locks. */
if (cursorImpl.lockAndGetCurrent(null, priData, LockType.NONE) !=
OperationStatus.SUCCESS) {
refersToPrimary = false;
} else {
if (!priData.equals(matchKey)) {
refersToPrimary = false;
}
}
}
if (refersToPrimary) {
/*
* To check whether the reference is still valid, because the
* primary is locked and the secondary can only be deleted after
* locking the primary, it is sufficient to check whether the
* secondary PD and KD flags are set. There is no need to lock the
* secondary, because it is protected from changes by the lock on
* the primary.
*
* If this technique were used with serialization isolation then
* checking the PD/KD flags wouldn't be sufficient -- locking the
* secondary would be necessary to prevent phantoms. With
* serializable isolation, a lock on the secondary record is
* acquired up front by SecondaryCursor.
*/
cursorImpl.latchBIN();
try {
final BIN bin = cursorImpl.getBIN();
final int index = cursorImpl.getIndex();
if (bin.isEntryPendingDeleted(index) ||
bin.isEntryKnownDeleted(index)) {
refersToPrimary = false;
}
} finally {
cursorImpl.releaseBIN();
}
}
return refersToPrimary;
}
/**
* Checks for a secondary corruption caused by a primary record update
* during a read-uncommitted read. Checking in this method is not possible
* because there is no secondary key creator available. It is overridden
* by SecondaryCursor.
*
* This method is in the Cursor class, rather than in SecondaryCursor, to
* support joins with plain Cursors [#21258].
*/
boolean checkForPrimaryUpdate(
final DatabaseEntry key,
final DatabaseEntry pKey,
final DatabaseEntry data) {
return false;
}
/**
* Returns whether the two keys have the same prefix.
*
* @param twoPartKey1 combined key with zero offset and size equal to the
* data array length.
*
* @param keyBytes2 combined key byte array.
*/
private boolean haveSameDupPrefix(
final DatabaseEntry twoPartKey1,
final byte[] keyBytes2) {
assert twoPartKey1.getOffset() == 0;
assert twoPartKey1.getData().length == twoPartKey1.getSize();
return DupKeyData.compareMainKey(
twoPartKey1.getData(), keyBytes2,
dbImpl.getBtreeComparator()) == 0;
}
/**
* Called to start an operation that potentially moves the cursor.
*
* If the cursor is not initialized already, the method simply returns
* this.cursorImpl. This avoids the overhead of cloning this.cursorImpl
* when this is a sticky cursor or forceClone is true.
*
* If the cursor is initialized, the actions taken here depend on whether
* cloning is required (either because this is a sticky cursor or
* because forceClone is true).
*
* (a) No cloning:
* - If same position is true, (1) the current LN (if any) is evicted, if
* the cachemode is EVICT_LN, and (2) non-txn locks are released, if
* retainNonTxnLocks is false. this.cursorImpl remains registered at its
* current BIN.
* - If same position is false, this.cursorImpl is "reset", i.e., (1) it is
* deregistered from its current position, (2) cachemode eviction is
* performed, (3) non-txn locks are released, if retainNonTxnLocks is
* false, and (4) this.cursorImpl is marked unintialized.
* - this.cursorImpl is returned.
*
* Note: In cases where only non-transactional locks are held, releasing
* them before the move prevents more than one lock from being held during
* a cursor move, which helps to avoid deadlocks.
*
* (b) Cloning:
* - this.cursorImpl is cloned.
* - If same position is true, the clone is registered at the same position
* as this.cursorImpl.
* - If same position is false, the clone is marked unitialized.
* - If this.cursorImpl uses a locker that may acquire non-txn locks and
* retainNonTxnLocks is false, the clone cursorImpl gets a new locker
* of the same kind as this.cursorImpl. This allows for the non-txn locks
* acquired by the clone to be released independently from the non-txn
* locks of this.cursorImpl.
* - The clone cursorImpl is returned.
*
* In all cases, critical eviction is performed, if necessary, before the
* method returns. This is done by CursorImpl.cloneCursor()/reset(), or is
* done here explicitly when the cursor is not cloned or reset.
*
* In all cases, the cursor returned must be passed to endMoveCursor() to
* close the correct cursor.
*
* @param samePosition If true, this cursor's position is used for the new
* cursor and addCursor is called on the new cursor; if non-sticky, this
* cursor's position is unchanged. If false, the new cursor will be
* uninitialized; if non-sticky, this cursor is reset.
*
* @param forceClone is true to clone an initialized cursor even if
* non-sticky is configured. Used when cloning is needed to support
* internal algorithms, namely when the algorithm may restart the operation
* and samePosition is true.
*
* @see CursorImpl#performCacheEviction for a description of how the
* cacheMode field is used. This method ensures that the correct cache
* mode is used before each operation.
*/
private CursorImpl beginMoveCursor(
final boolean samePosition,
final boolean forceClone) {
/*
* It don't make sense to force cloning if the new cursor will be
* uninitialized.
*/
assert !(forceClone && !samePosition);
/* Must set cache mode before calling criticalEviction or reset. */
cursorImpl.setCacheMode(cacheMode);
if (cursorImpl.isNotInitialized()) {
cursorImpl.criticalEviction();
return cursorImpl;
}
if (nonSticky && !forceClone) {
if (samePosition) {
cursorImpl.beforeAdvance();
} else {
cursorImpl.reset();
}
return cursorImpl;
}
final CursorImpl dup = cursorImpl.cloneCursor(samePosition);
dup.setClosingLocker(cursorImpl);
return dup;
}
private CursorImpl beginMoveCursor(final boolean samePosition) {
return beginMoveCursor(samePosition, false /*forceClone*/);
}
/**
* Called to end an operation that potentially moves the cursor.
*
* The actions taken here depend on whether cloning was done in
* beginMoveCursor() or not:
*
* (a) No cloning:
* - If the op is successfull, only critical eviction is done.
* - If the op is not successfull, this.cursorImpl is "reset", i.e.,
* (1) it is deregistered from its current position, (2) cachemode
* eviction is performed, (3) non-txn locks are released, if
* retainNonTxnLocks is false, and (4) this.cursorImpl is marked
* unintialized.
*
* (b) Cloning:
* - If the op is successful, this.cursorImpl is closed and then it is
* set to the clone cursorImpl.
* - If the op is not successfull, the clone cursorImpl is closed.
* - In either case, closing a cursorImpl involves deregistering it from
* its current position, performing cachemode eviction, releasing its
* non-transactional locks and closing its locker, if retainNonTxnLocks
* is false and the locker is not a Txn, and finally marking the
* cursorImpl as closed.
*
* In all cases, critical eviction is performed after each cursor operation.
* This is done by CursorImpl.reset() and close(), or is done here explicitly
* when the cursor is not cloned.
*/
private void endMoveCursor(final CursorImpl dup, final boolean success) {
dup.clearClosingLocker();
if (dup == cursorImpl) {
if (success) {
cursorImpl.criticalEviction();
} else {
cursorImpl.reset();
}
} else {
if (success) {
cursorImpl.close(dup);
cursorImpl = dup;
} else {
dup.close(cursorImpl);
}
}
}
/**
* Called to start an operation that does not move the cursor, and
* therefore does not clone the cursor. Either beginUseExistingCursor /
* endUseExistingCursor or beginMoveCursor / endMoveCursor must be used for
* each operation.
*/
private void beginUseExistingCursor() {
/* Must set cache mode before calling criticalEviction. */
cursorImpl.setCacheMode(cacheMode);
cursorImpl.criticalEviction();
}
/**
* Called to end an operation that does not move the cursor.
*/
private void endUseExistingCursor() {
cursorImpl.criticalEviction();
}
/**
* Swaps CursorImpl of this cursor and the other cursor given.
*/
private void swapCursor(Cursor other) {
final CursorImpl otherImpl = other.cursorImpl;
other.cursorImpl = this.cursorImpl;
this.cursorImpl = otherImpl;
}
boolean advanceCursor(final DatabaseEntry key, final DatabaseEntry data) {
return cursorImpl.advanceCursor(key, data);
}
private LockType getLockType(
final LockMode lockMode,
final boolean rangeLock) {
if (isReadUncommittedMode(lockMode)) {
return LockType.NONE;
} else if (lockMode == null || lockMode == LockMode.DEFAULT) {
return rangeLock ? LockType.RANGE_READ: LockType.READ;
} else if (lockMode == LockMode.RMW) {
return rangeLock ? LockType.RANGE_WRITE: LockType.WRITE;
} else if (lockMode == LockMode.READ_COMMITTED) {
throw new IllegalArgumentException(
lockMode.toString() + " not allowed with Cursor methods, " +
"use CursorConfig.setReadCommitted instead.");
} else {
assert false : lockMode;
return LockType.NONE;
}
}
/**
* Returns whether the given lock mode will cause a read-uncommitted when
* used with this cursor, taking into account the default cursor
* configuration.
*/
boolean isReadUncommittedMode(final LockMode lockMode) {
return (lockMode == LockMode.READ_UNCOMMITTED ||
lockMode == LockMode.READ_UNCOMMITTED_ALL ||
(readUncommittedDefault &&
(lockMode == null || lockMode == LockMode.DEFAULT)));
}
boolean isSerializableIsolation(final LockMode lockMode) {
return serializableIsolationDefault &&
!isReadUncommittedMode(lockMode);
}
void checkUpdatesAllowed() {
if (!updateOperationsProhibited) {
return;
}
final Locker locker = cursorImpl.getLocker();
final StringBuilder str = new StringBuilder(200);
str.append("Write operation is not allowed because ");
/* Be sure to keep this logic in sync with init(). */
if (locker.isReadOnly()) {
str.append("the Transaction is configured as read-only.");
} else if (dbHandle != null && !dbHandle.isWritable()) {
str.append("the Database is configured as read-only.");
} else if (dbImpl.isTransactional() && !locker.isTransactional()) {
str.append("a Transaction was not supplied to openCursor ");
str.append("and the Database is transactional.");
} else if (dbImpl.isReplicated() && locker.isLocalWrite()) {
str.append("the Database is replicated and Transaction is ");
str.append("configured as local-write.");
} else if (!dbImpl.isReplicated() && !locker.isLocalWrite()) {
str.append("the Database is not replicated and the ");
str.append("Transaction is not configured as local-write.");
} else {
assert false;
}
throw new UnsupportedOperationException(str.toString());
}
/**
* Note that this flavor of checkArgs allows the key and data to be null.
*/
static void checkArgsNoValRequired(
final DatabaseEntry key,
final DatabaseEntry data) {
DatabaseUtil.checkForNullDbt(key, "key", false);
DatabaseUtil.checkForNullDbt(data, "data", false);
}
/**
* Note that this flavor of checkArgs requires that the key and data are
* not null.
*/
static void checkArgsValRequired(
final DatabaseEntry key,
final DatabaseEntry data) {
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "data", true);
}
/**
* Checks the environment and cursor state.
*/
void checkState(final boolean mustBeInitialized) {
checkEnv();
if (dbHandle != null) {
dbHandle.checkOpen("Can't call Cursor method:");
}
cursorImpl.checkCursorState(
mustBeInitialized, false /*mustNotBeInitialized*/);
}
/**
* @throws EnvironmentFailureException if the underlying environment is
* invalid.
*/
void checkEnv() {
cursorImpl.checkEnv();
}
/**
* Returns an object used for synchronizing transactions that are used in
* multiple threads.
*
* For a transactional locker, the Transaction is returned to prevent
* concurrent access using this transaction from multiple threads. The
* Transaction.commit and abort methods are synchronized so they do not run
* concurrently with operations using the Transaction. Note that the Txn
* cannot be used for synchronization because locking order is BIN first,
* then Txn.
*
* For a non-transactional locker, 'this' is returned because no special
* blocking is needed. Other mechanisms are used to prevent
* non-transactional usage access by multiple threads (see ThreadLocker).
* In the future we may wish to use the getTxnSynchronizer for
* synchronizing non-transactional access as well; however, note that a new
* locker is created for each operation.
*/
private Object getTxnSynchronizer() {
return (transaction != null) ? transaction : this;
}
private void checkTxnState() {
if (transaction == null) {
return;
}
transaction.checkOpen();
transaction.getTxn().checkState(false /*calledByAbort*/);
}
/**
* Sends 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.
*/
void trace(
final Level level,
final String methodName,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
if (logger.isLoggable(level)) {
final StringBuilder sb = new StringBuilder();
sb.append(methodName);
traceCursorImpl(sb);
if (key != null) {
sb.append(" key=").append(key.dumpData());
}
if (data != null) {
sb.append(" data=").append(data.dumpData());
}
if (lockMode != null) {
sb.append(" lockMode=").append(lockMode);
}
LoggerUtils.logMsg(
logger, dbImpl.getEnv(), level, sb.toString());
}
}
/**
* Sends 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.
*/
void trace(
final Level level,
final String methodName,
final LockMode lockMode) {
if (logger.isLoggable(level)) {
final StringBuilder sb = new StringBuilder();
sb.append(methodName);
traceCursorImpl(sb);
if (lockMode != null) {
sb.append(" lockMode=").append(lockMode);
}
LoggerUtils.logMsg(
logger, dbImpl.getEnv(), level, sb.toString());
}
}
private void traceCursorImpl(final StringBuilder sb) {
sb.append(" locker=").append(cursorImpl.getLocker().getId());
sb.append(" bin=").append(cursorImpl.getCurrentNodeId());
sb.append(" idx=").append(cursorImpl.getIndex());
}
/**
* Clone entry contents in a new returned entry.
*/
private static DatabaseEntry cloneEntry(DatabaseEntry from) {
final DatabaseEntry to = new DatabaseEntry();
setEntry(from, to);
return to;
}
/**
* Copy entry contents to another entry.
*/
private static void setEntry(DatabaseEntry from, DatabaseEntry to) {
to.setPartial(from.getPartialOffset(), from.getPartialLength(),
from.getPartial());
to.setData(from.getData(), from.getOffset(), from.getSize());
}
}