blob: ed874249145f6ff0fd2af2785bfdfdcdc49757e5 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
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.config.EnvironmentParams;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.CursorImpl.LockStanding;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DupKeyData;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.ExpirationInfo;
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.SearchMode;
import com.sleepycat.je.dbi.TTL;
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;
/**
* 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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>To obtain a cursor with default attributes:</p>
*
* <blockquote><pre>
* Cursor cursor = myDatabase.openCursor(txn, null);
* </pre></blockquote>
*
* <p>To customize the attributes of a cursor, use a CursorConfig object.</p>
*
* <blockquote><pre>
* CursorConfig config = new CursorConfig();
* config.setReadUncommitted(true);
* Cursor cursor = myDatabase.openCursor(txn, config);
* </pre></blockquote>
*
* <p>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.</p>
*
* <p>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 the operation does not succeed. However, it is possible
* to configure a cursor as non-sticky to enable certain performance benefits.
* See {@link CursorConfig#setNonSticky} for details.</p>
*
* <h3><a name="partialEntry">Using Null and Partial DatabaseEntry
* Parameters</a></h3>
*
* <p>Null can be passed for DatabaseEntry output parameters if the value is
* not needed. The {@link DatabaseEntry#setPartial DatabaseEntry Partial}
* property can also be used to optimize in certain cases. These provide
* varying degrees of performance benefits that depend on the specific
* operation, as described below.</p>
*
* <p>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 by passing null. If null is passed as
* the data parameter, the data item will not be returned by the {@code
* Database} or {@code Cursor} method.</p>
*
* <p>Suppressing the return of the data item potentially has a large
* performance benefit. In this case, if the record data is not already 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:</p>
* <ul>
* <li>Scanning all records in key order, when the data is not needed.</li>
* <li>Skipping over records quickly with {@code READ_UNCOMMITTED} isolation to
* select records for further processing by examining the key value.</li>
* </ul>
*
* <p>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.
* However, 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.</p>
*
* <p>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 has less
* of a performance benefit than one might assume, 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}.</p>
*
* <p>A null or partial DatabaseEntry output parameter may also be used in
* other cases, for example, to retrieve a partial key item. 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.</p>
*
* <p>Historical note: Prior to JE 7.0, null could not be passed for output
* parameters. Instead, {@code DatabaseEntry.setPartial(0, 0, true)} was called
* for a data parameter to avoid reading the record's data. Now, null can be
* passed instead.</p>
*/
public class Cursor implements ForwardCursor {
static final ReadOptions DEFAULT_READ_OPTIONS = new ReadOptions();
static final WriteOptions DEFAULT_WRITE_OPTIONS = new WriteOptions();
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 defaultCacheMode;
private boolean includeInOpStats = true;
/*
* 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;
private Logger logger;
/**
* Creates a cursor for a given user transaction with
* retainNonTxnLocks=false.
*
* <p>If txn is null, a non-transactional cursor will be created that
* releases locks for the prior operation when the next operation
* succeeds.</p>
*/
Cursor(final Database dbHandle,
final Transaction txn,
CursorConfig cursorConfig) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
final DatabaseImpl dbImpl = dbHandle.checkOpen();
/* Do not allow auto-commit when creating a user cursor. */
Locker locker = LockerFactory.getReadableLocker(
dbHandle, txn, cursorConfig.getReadCommitted());
init(dbHandle, dbImpl, locker, cursorConfig,
false /*retainNonTxnLocks*/);
}
/**
* Creates a cursor for a given locker with retainNonTxnLocks=false.
*
* <p>If locker is null or is non-transactional, a non-transactional cursor
* will be created that releases locks for the prior operation when the
* next operation succeeds.</p>
*/
Cursor(final Database dbHandle, Locker locker, CursorConfig cursorConfig) {
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
/* Check that Database is open for internal Cursor usage. */
final DatabaseImpl dbImpl = dbHandle.checkOpen();
locker = LockerFactory.getReadableLocker(
dbHandle, locker, cursorConfig.getReadCommitted());
init(dbHandle, dbImpl, locker, cursorConfig,
false /*retainNonTxnLocks*/);
}
/**
* Creates a cursor for a given locker and retainNonTxnLocks parameter.
*
* <p>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.</p>
*/
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. */
final DatabaseImpl dbImpl = dbHandle.checkOpen();
init(dbHandle, dbImpl, locker, cursorConfig, retainNonTxnLocks);
}
/**
* Creates a cursor for a given locker and retainNonTxnLocks parameter,
* without a Database handle.
*
* <p>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.</p>
*/
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();
}
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();
/* Keep this logic in sync with updatesProhibitedException. */
updateOperationsProhibited =
locker.isReadOnly() ||
(dbHandle != null && !dbHandle.isWritable()) ||
(databaseImpl.isTransactional() && !locker.isTransactional()) ||
(databaseImpl.isReplicated() && locker.isLocalWrite() &&
!databaseImpl.getDbType().isMixedReplication()) ||
(!databaseImpl.isReplicated() && !locker.isLocalWrite());
this.dbImpl = databaseImpl;
if (dbHandle != null) {
this.dbHandle = dbHandle;
dbHandle.addCursor(this);
}
this.config = cursorConfig;
this.logger = databaseImpl.getEnv().getLogger();
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();
defaultCacheMode = cursor.defaultCacheMode;
nonSticky = cursor.nonSticky;
}
boolean isSecondaryCursor() {
return false;
}
/**
* Sets non-sticky mode.
*
* @see CursorConfig#setNonSticky
*/
void setNonSticky(final boolean nonSticky) {
this.nonSticky = nonSticky;
}
/**
* Use to prevent internal ops from appearing in operation stats.
*/
void excludeFromOpStats() {
includeInOpStats = false;
}
/**
* 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.
*
* <p>This may differ from the configuration used to open this object if
* the cursor existed previously.</p>
*
* @return This cursor's configuration.
*/
public CursorConfig getConfig() {
try {
return config.clone();
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Returns the default {@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} default used for subsequent operations
* using this cursor.
*/
public CacheMode getCacheMode() {
return defaultCacheMode;
}
/**
* Sets the {@code CacheMode} default 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}. Note that the default is always
* overridden by a non-null cache mode that is specified via
* {@link ReadOptions} or {@link WriteOptions}.
*
* @param cacheMode is the default {@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.defaultCacheMode =
(cacheMode != null) ? cacheMode : dbImpl.getDefaultCacheMode();
}
/**
* @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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*/
public void close() {
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.
*
* <p>This is useful when an application is using locking and requires
* two or more cursors in the same thread of control.</p>
*
* @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) {
try {
checkOpenAndState(false);
return new Cursor(this, samePosition);
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Deletes the record to which the cursor refers. When the database has
* associated secondary databases, this method also deletes the associated
* index records.
*
* <p>The cursor position is unchanged after a delete, and subsequent calls
* to cursor functions expecting the cursor to refer to an existing record
* will fail.</p>
*
* @param options the WriteOptions, or null to use default options.
*
* @return the OperationResult if the record is deleted, else null if the
* record at the cursor position has already been deleted.
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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.
*
* @since 7.0
*/
public OperationResult delete(final WriteOptions options) {
checkOpenAndState(true);
trace(Level.FINEST, "Cursor.delete: ", null);
final CacheMode cacheMode =
options != null ? options.getCacheMode() : null;
return deleteInternal(dbImpl.getRepContext(), cacheMode);
}
/**
* Deletes the record to which the cursor refers. When the database has
* associated secondary databases, this method also deletes the associated
* index records.
*
* <p>The cursor position is unchanged after a delete, and subsequent calls
* to cursor functions expecting the cursor to refer to an existing record
* will fail.</p>
*
* <p>Calling this method is equivalent to calling {@link
* #delete(WriteOptions)}.</p>
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEMPTY
* OperationStatus.KEYEMPTY} if the record at the cursor position has
* already been deleted; otherwise, {@link
* com.sleepycat.je.OperationStatus#SUCCESS OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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() {
final OperationResult result = delete(null);
return result == null ?
OperationStatus.KEYEMPTY : OperationStatus.SUCCESS;
}
/**
* Inserts or updates a record according to the specified {@link Put}
* type.
*
* <p>If the operation succeeds, the record will be locked according to the
* {@link ReadOptions#getLockMode() lock mode} specified, the cursor will
* be positioned on the record, and a non-null OperationResult will be
* returned. If the operation fails because the record already exists (or
* does not exist, depending on the putType), null is returned.</p>
*
* <p>When the database has associated secondary databases, this method
* also inserts or deletes associated index records as necessary.</p>
*
* <p>The following table lists each allowed operation. See the individual
* {@link Put} operations for more information.</p>
*
* <div><table border="1" summary="">
* <tr>
* <th>Put operation</th>
* <th>Description</th>
* <th>Returns null when?</th>
* <th>Other special rules</th>
* </tr>
* <tr>
* <td>{@link Put#OVERWRITE}</td>
* <td>Inserts or updates a record depending on whether a matching
* record is already present.</td>
* <td>Never returns null.</td>
* <td>Without duplicates, a matching record is one with the same key;
* with duplicates, it is one with the same key and data.</td>
* </tr>
* <tr>
* <td>{@link Put#NO_OVERWRITE}</td>
* <td>Inserts a record if a record with a matching key is not already
* present.</td>
* <td>When an existing record matches.</td>
* <td>If the database has duplicate keys, a record is inserted only if
* there are no records with a matching key.</td>
* </tr>
* <tr>
* <td>{@link Put#NO_DUP_DATA}</td>
* <td>Inserts a record in a database with duplicate keys if a record
* with a matching key and data is not already present.</td>
* <td>When an existing record matches.</td>
* <td>Without duplicates, this operation is not allowed.</td>
* </tr>
* <tr>
* <td>{@link Put#CURRENT}</td>
* <td>Updates the data of the record at the cursor position.</td>
* <td>When the record at the cursor position has been deleted.</td>
* <td>With duplicates, the data must be considered equal by the
* duplicate comparator, meaning that changing the data is only
* possible if a custom duplicate comparator is configured.
* <p>
* Cannot be used to update the key of an existing record and in
* fact the key parameter must be null.
* <p>
* A <a href="Cursor.html#partialEntry">partial data item</a> may be
* specified to optimize for partial data update.
* </td>
* </tr>
* </table></div>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>. Must be null when
* putType is {@code Put.CURRENT}.
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>. May be partial only when
* putType is {@code Put.CURRENT}.
*
* @param putType the Put operation type. May not be null.
*
* @param options the WriteOptions, or null to use default options.
*
* @return the OperationResult if the record is written, else null.
*
* @throws DuplicateDataException if putType is Put.CURRENT and the old and
* new data are not equal according to the configured duplicate comparator
* or default comparator.
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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 putType is Put.NO_DUP_DATA and 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.
* This includes passing a null putType, a null input key/data parameter,
* an input key/data parameter with a null data array, a partial key/data
* input parameter.
*
* @since 7.0
*/
public OperationResult put(
final DatabaseEntry key,
final DatabaseEntry data,
final Put putType,
final WriteOptions options) {
try {
checkOpen();
trace(
Level.FINEST, "Cursor.put: ", String.valueOf(putType),
key, data, null);
return putInternal(key, data, putType, options);
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Performs the put() operation except for state checking and tracing.
*
* Allows passing a throughput stat index so it can be called for Database
* and SecondaryCursor operations.
*/
OperationResult putInternal(
final DatabaseEntry key,
final DatabaseEntry data,
final Put putType,
WriteOptions options) {
DatabaseUtil.checkForNullParam(putType, "putType");
if (putType == Put.CURRENT) {
if (key != null) {
throw new IllegalArgumentException(
"The key must be null for Put.Current");
}
} else {
DatabaseUtil.checkForNullDbt(key, "key", true);
}
if (key != null) {
DatabaseUtil.checkForPartial(key, "key");
}
DatabaseUtil.checkForNullDbt(data, "data", true);
checkState(putType == Put.CURRENT /*mustBeInitialized*/);
if (options == null) {
options = DEFAULT_WRITE_OPTIONS;
}
return putInternal(
key, data, options.getCacheMode(),
ExpirationInfo.getInfo(options),
putType.getPutMode());
}
/**
* Stores a key/data pair into the database.
*
* <p>Calling this method is equivalent to calling {@link
* #put(DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#OVERWRITE}.</p>
*
* <p>If the put method succeeds, the cursor is positioned to refer to the
* newly inserted item.</p>
*
* <p>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.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>..
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @return {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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) {
final OperationResult result = put(key, data, Put.OVERWRITE, null);
EnvironmentFailureException.assertState(result != null);
return OperationStatus.SUCCESS;
}
/**
* Stores a key/data pair into the database.
*
* <p>Calling this method is equivalent to calling {@link
* #put(DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#NO_OVERWRITE}.</p>
*
* <p>If the putNoOverwrite method succeeds, the cursor is positioned to
* refer to the newly inserted item.</p>
*
* <p>If the key already appears in the database, putNoOverwrite will
* return {@link com.sleepycat.je.OperationStatus#KEYEXIST
* OperationStatus.KEYEXIST}.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>..
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEXIST
* OperationStatus.KEYEXIST} if the key already appears in the database,
* else {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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) {
final OperationResult result = put(
key, data, Put.NO_OVERWRITE, null);
return result == null ?
OperationStatus.KEYEXIST : OperationStatus.SUCCESS;
}
/**
* Stores a key/data pair into the database. The database must be
* configured for duplicates.
*
* <p>Calling this method is equivalent to calling {@link
* #put(DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#NO_DUP_DATA}.</p>
*
* <p>If the putNoDupData method succeeds, the cursor is positioned to
* refer to the newly inserted item.</p>
*
* <p>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.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>..
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @return {@link com.sleepycat.je.OperationStatus#KEYEXIST
* OperationStatus.KEYEXIST} if the key/data pair already appears in the
* database, else {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}
*
* @throws OperationFailureException if one of the <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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) {
final OperationResult result = put(
key, data, Put.NO_DUP_DATA, null);
return result == null ?
OperationStatus.KEYEXIST : OperationStatus.SUCCESS;
}
/**
* Replaces the data in the key/data pair at the current cursor position.
*
* <p>Calling this method is equivalent to calling {@link
* #put(DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#CURRENT}.</p>
*
* <p>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.</p>
*
* <p>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}.</p>
*
* <p>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.</p>
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>.
* A <a href="Cursor.html#partialEntry">partial data item</a> 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 <a
* href="../je/OperationFailureException.html#writeFailures">Write
* Operation Failures</a> 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) {
final OperationResult result = put(null, data, Put.CURRENT, null);
return result == null ?
OperationStatus.KEYEMPTY : OperationStatus.SUCCESS;
}
/**
* Moves the cursor to a record according to the specified {@link Get}
* type.
*
* <p>If the operation succeeds, the record at the resulting cursor
* position will be locked according to the {@link
* ReadOptions#getLockMode() lock mode} specified, the key and/or data will
* be returned via the (non-null) DatabaseEntry parameters, and a non-null
* OperationResult will be returned. If the operation fails because the
* record requested is not found, null is returned.</p>
*
* <p>The following table lists each allowed operation and whether the key
* and data parameters are <a href="DatabaseEntry.html#params">input or
* output parameters</a>. Also specified is whether the cursor must be
* initialized (positioned on a record) before calling this method. See the
* individual {@link Get} operations for more information.</p>
*
* <div><table border="1" summary="">
* <tr>
* <th>Get operation</th>
* <th>Description</th>
* <th>'key' parameter</th>
* <th>'data' parameter</th>
* <th>Cursor position<br>must be initialized?</th>
* </tr>
* <tr>
* <td>{@link Get#SEARCH}</td>
* <td>Searches using an exact match by key.</td>
* <td><a href="DatabaseEntry.html#inParam">input</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#SEARCH_BOTH}</td>
* <td>Searches using an exact match by key and data.</td>
* <td><a href="DatabaseEntry.html#inParam">input</a></td>
* <td><a href="DatabaseEntry.html#inParam">input</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#SEARCH_GTE}</td>
* <td>Searches using a GTE match by key.</td>
* <td><a href="DatabaseEntry.html#inParam">input/output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#SEARCH_BOTH_GTE}</td>
* <td>Searches using an exact match by key and a GTE match by data.</td>
* <td><a href="DatabaseEntry.html#inParam">input</a></td>
* <td><a href="DatabaseEntry.html#inParam">input/output</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#CURRENT}</td>
* <td>Accesses the current record</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>yes</td>
* </tr>
* <tr>
* <td>{@link Get#FIRST}</td>
* <td>Finds the first record in the database.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#LAST}</td>
* <td>Finds the last record in the database.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no</td>
* </tr>
* <tr>
* <td>{@link Get#NEXT}</td>
* <td>Moves to the next record.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no**</td>
* </tr>
* <tr>
* <td>{@link Get#NEXT_DUP}</td>
* <td>Moves to the next record with the same key.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>yes</td>
* </tr>
* <tr>
* <td>{@link Get#NEXT_NO_DUP}</td>
* <td>Moves to the next record with a different key.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no**</td>
* </tr>
* <tr>
* <td>{@link Get#PREV}</td>
* <td>Moves to the previous record.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no**</td>
* </tr>
* <tr>
* <td>{@link Get#PREV_DUP}</td>
* <td>Moves to the previous record with the same key.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>yes</td>
* </tr>
* <tr>
* <td>{@link Get#PREV_NO_DUP}</td>
* <td>Moves to the previous record with a different key.</td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td><a href="DatabaseEntry.html#outParam">output</a></td>
* <td>no**</td>
* </tr>
* </table></div>
*
* <p>** - For these 'next' and 'previous' operations the cursor may be
* uninitialized, in which case the cursor will be moved to the first or
* last record, respectively.</p>
*
* @param key the key input or output parameter, depending on getType.
*
* @param data the data input or output parameter, depending on getType.
*
* @param getType the Get operation type. May not be null.
*
* @param options the ReadOptions, or null to use default options.
*
* @return the OperationResult if the record requested is found, else null.
*
* @throws OperationFailureException if one of the <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the cursor or database has been closed,
* the cursor is uninitialized (not positioned on a record) and this is not
* permitted (see above), or the non-transactional cursor was created in a
* different thread.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
* This includes passing a null getType, a null input key/data parameter,
* an input key/data parameter with a null data array, a partial key/data
* input parameter, and specifying a {@link ReadOptions#getLockMode()
* lock mode} of READ_COMMITTED.
*
* @since 7.0
*/
public OperationResult get(
final DatabaseEntry key,
final DatabaseEntry data,
final Get getType,
ReadOptions options) {
try {
checkOpen();
if (options == null) {
options = DEFAULT_READ_OPTIONS;
}
final LockMode lockMode = options.getLockMode();
trace(
Level.FINEST, "Cursor.get: ", String.valueOf(getType),
key, data, lockMode);
return getInternal(key, data, getType, options, lockMode);
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Performs the get() operation except for state checking and tracing.
*
* The LockMode is passed because for Database operations it is sometimes
* different than ReadOptions.getLockMode.
*
* Allows passing a throughput stat index so it can be called for Database
* and SecondaryCursor operations.
*/
OperationResult getInternal(
DatabaseEntry key,
DatabaseEntry data,
Get getType,
final ReadOptions options,
final LockMode lockMode) {
DatabaseUtil.checkForNullParam(getType, "getType");
final CacheMode cacheMode = options.getCacheMode();
final SearchMode searchMode = getType.getSearchMode();
if (searchMode != null) {
checkState(false /*mustBeInitialized*/);
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForPartial(key, "key");
if (searchMode.isDataSearch() ||
(searchMode == SearchMode.ANY_RANGE &&
dbImpl.getSortedDuplicates())) {
DatabaseUtil.checkForNullDbt(data, "data", true);
DatabaseUtil.checkForPartial(data, "data");
} else {
if (data == null) {
data = NO_RETURN_DATA;
}
}
return search(key, data, lockMode, cacheMode, searchMode, true);
}
if (key == null) {
key = NO_RETURN_DATA;
}
if (data == null) {
data = NO_RETURN_DATA;
}
GetMode getMode = getType.getGetMode();
if (getType.getAllowNextPrevUninitialized() &&
cursorImpl.isNotInitialized()) {
assert getMode != null;
getType = getMode.isForward() ? Get.FIRST : Get.LAST;
getMode = null;
}
if (getMode != null) {
checkState(true /*mustBeInitialized*/);
return retrieveNext(key, data, lockMode, cacheMode, getMode);
}
if (getType == Get.CURRENT) {
checkState(true /*mustBeInitialized*/);
return getCurrentInternal(key, data, lockMode, cacheMode);
}
assert getType == Get.FIRST || getType == Get.LAST;
checkState(false /*mustBeInitialized*/);
return position(key, data, lockMode, cacheMode, getType == Get.FIRST);
}
/**
* Returns the key/data pair to which the cursor refers.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#CURRENT}.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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 getCurrent(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
final OperationResult result = get(
key, data, Get.CURRENT, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.KEYEMPTY : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#FIRST}.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.FIRST, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#LAST}.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.LAST, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Moves the cursor to the next key/data pair and returns that pair.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#NEXT}.</p>
*
* <p>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.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.NEXT, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#NEXT_DUP}.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.NEXT_DUP, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#NEXT_NO_DUP}.</p>
*
* <p>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.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.NEXT_NO_DUP, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Moves the cursor to the previous key/data pair and returns that pair.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#PREV}.</p>
*
* <p>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.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.PREV, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#PREV_DUP}.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.PREV_DUP, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#PREV_NO_DUP}.</p>
*
* <p>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.</p>
*
* @param key the key returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.PREV_NO_DUP, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Skips forward a given number of key/data pairs and returns the number by
* which the cursor is moved.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
checkOpenAndState(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, null);
}
/**
* Skips backward a given number of key/data pairs and returns the number
* by which the cursor is moved.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
checkOpenAndState(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, null);
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH}.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.SEARCH, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* 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.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH_GTE}.</p>
*
* <p>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.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a> and returned as output.
*
* @param data the data returned as
* <a href="DatabaseEntry.html#outParam">output</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.SEARCH_GTE, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Moves the cursor to the specified key/data pair, where both the key and
* data items must match.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH_BOTH}.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.SEARCH_BOTH, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Moves the cursor to the specified key and closest matching data item of
* the database.
*
* <p>Calling this method is equivalent to calling {@link
* #get(DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH_BOTH_GTE}.</p>
*
* <p>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.</p>
*
* <p>In the case of databases that do not support sorted duplicate sets,
* this method is equivalent to getSearchBoth.</p>
*
* @param key the key used as
* <a href="DatabaseEntry.html#inParam">input</a>.
*
* @param data the data used as
* <a href="DatabaseEntry.html#inParam">input</a> and returned as output.
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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) {
final OperationResult result = get(
key, data, Get.SEARCH_BOTH_GTE,
DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Returns a count of the number of data items for the key to which the
* cursor refers.
*
* <p>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}.</p>
*
* <p>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.</p>
*
* <p>The cost of this method is directly proportional to the number of
* records scanned.</p>
*
* @return A count of the number of data items for the key to which the
* cursor refers.
*
* @throws OperationFailureException if one of the <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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() {
checkOpenAndState(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.
*
* <p>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}.</p>
*
* <p>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.</p>
*
* <p>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}.</p>
*
* @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 <a
* href="OperationFailureException.html#readFailures">Read Operation
* Failures</a> 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() {
checkOpenAndState(true);
trace(Level.FINEST, "Cursor.countEstimate: ", null);
return countEstimateInternal();
}
/**
* Version of deleteInternal that does not check disk limits. Used for
* replication stream replay.
*
* Notifies triggers and prevents phantoms. Note that although deleteNotify
* is called, secondaries are not populated because this cursor is internal
* and has no associated Database handle.
*/
OperationResult deleteWithRepContext(final ReplicationContext repContext) {
return deleteNotify(repContext, null);
}
/**
* Internal version of delete() that does no parameter checking. Notify
* triggers, update secondaries and enforce foreign key constraints.
*/
OperationResult deleteInternal(final ReplicationContext repContext,
final CacheMode cacheMode) {
checkUpdatesAllowed();
return deleteNotify(repContext, cacheMode);
}
/**
* Implementation of deleteInternal that does not check disk limits.
*
* 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.
*/
private OperationResult deleteNotify(final ReplicationContext repContext,
final CacheMode cacheMode) {
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<SecondaryDatabase> secondaries;
final Collection<SecondaryDatabase> 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 only if needed. Even if the old data is not
* needed, if there are associations we must lock the record with
* RMW before calling onForeignKeyDelete.
*/
final DatabaseEntry oldData =
needOldData ? (new DatabaseEntry()) : null;
final OperationResult readResult;
if (needOldData || hasAssociations) {
readResult = getCurrentInternal(
key, oldData, LockMode.RMW, cacheMode);
if (readResult == null) {
return null;
}
} else {
readResult = null;
}
/*
* Enforce foreign key constraints first, so that
* ForeignKeyDeleteAction.ABORT is applied before deletions.
*/
final Locker locker = cursorImpl.getLocker();
if (fkSecondaries != null) {
for (final SecondaryDatabase secDb : fkSecondaries) {
secDb.onForeignKeyDelete(locker, key, cacheMode);
}
}
/*
* The actual deletion.
*/
final OperationResult deleteResult =
deleteNoNotify(cacheMode, repContext);
if (deleteResult == null) {
return null;
}
/*
* Update secondaries after actual deletion, so that replica replay
* will lock the primary before the secondaries. This locking order
* is required for secondary deadlock avoidance.
*/
if (secondaries != null) {
int nWrites = 0;
for (final SecondaryDatabase secDb : secondaries) {
nWrites += secDb.updateSecondary(
locker, null /*cursor*/, dbImpl, cursorImpl, key,
oldData, null /*newData*/, cacheMode,
0 /*expirationTime*/, false /*expirationUpdated*/,
readResult.getExpirationTime());
}
cursorImpl.setNSecondaryWrites(nWrites);
}
/* Run triggers after actual deletion. */
if (hasUserTriggers) {
TriggerManager.runDeleteTriggers(locker, dbImpl, key, oldData);
}
return deleteResult;
} 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).
*/
OperationResult deleteNoNotify(final CacheMode cacheMode,
final ReplicationContext repContext) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
/*
* No need to use a dup cursor, since this operation does not
* change the cursor position.
*/
beginUseExistingCursor(cacheMode);
final OperationResult result =
cursorImpl.deleteCurrentRecord(repContext);
if (result != null && includeInOpStats) {
dbImpl.getEnv().incDeleteOps(dbImpl);
}
endUseExistingCursor();
return result;
}
}
/**
* Version of putInternal that allows passing an existing LN, does not
* interpret duplicates, and does not check disk limits. Used for
* replication stream replay.
*
* Notifies triggers and prevents phantoms. Note that although putNotify
* is called, secondaries are not populated because this cursor is internal
* and has no associated Database handle.
*/
OperationResult putForReplay(
final DatabaseEntry key,
final DatabaseEntry data,
final LN ln,
final int expiration,
final boolean expirationInHours,
final PutMode putMode,
final ReplicationContext repContext) {
assert putMode != PutMode.CURRENT;
final ExpirationInfo expInfo = new ExpirationInfo(
expiration, expirationInHours, true /*updateExpiration*/);
synchronized (getTxnSynchronizer()) {
checkTxnState();
return putNotify(
key, data, ln, null, expInfo, putMode, repContext);
}
}
OperationResult putWithRepContext(
final DatabaseEntry key,
final DatabaseEntry data,
final PutMode putMode,
final ReplicationContext repContext) {
final LN ln = LN.makeLN(dbImpl.getEnv(), data);
synchronized (getTxnSynchronizer()) {
checkTxnState();
return putNotify(
key, data, ln, null, null, putMode, repContext);
}
}
/**
* Internal version of put that does no parameter checking. Interprets
* duplicates, notifies triggers, and prevents phantoms.
*/
OperationResult putInternal(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo,
final PutMode putMode) {
checkUpdatesAllowed(expInfo);
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return putHandleDups(
key, data, cacheMode, expInfo, putMode);
}
if (putMode == PutMode.NO_DUP_DATA) {
throw new UnsupportedOperationException(
"Database is not configured for duplicate data.");
}
return putNoDups(
key, data, cacheMode, expInfo, putMode);
}
}
/**
* Interpret duplicates for the various 'putXXX' operations.
*/
private OperationResult putHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo,
final PutMode putMode) {
switch (putMode) {
case OVERWRITE:
return dupsPutOverwrite(key, data, cacheMode, expInfo);
case NO_OVERWRITE:
return dupsPutNoOverwrite(key, data, cacheMode, expInfo);
case NO_DUP_DATA:
return dupsPutNoDupData(key, data, cacheMode, expInfo);
case CURRENT:
return dupsPutCurrent(data, cacheMode, expInfo);
default:
throw EnvironmentFailureException.unexpectedState(
putMode.toString());
}
}
/**
* Interpret duplicates for the put() operation.
*/
private OperationResult dupsPutOverwrite(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
return putNoDups(
twoPartKey, EMPTY_DUP_DATA, cacheMode, expInfo,
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 OperationResult dupsPutNoOverwrite(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo) {
final DatabaseEntry key2 = new DatabaseEntry();
final DatabaseEntry data2 = new DatabaseEntry();
try (final Cursor c = dup(false /*samePosition*/)) {
c.setNonSticky(true);
/* Lock next key (or EOF if none) exclusively, before we insert. */
setEntry(key, key2);
OperationResult result = c.dupsGetSearchKeyRange(
key2, data2, LockMode.RMW, cacheMode);
if (result != null && key.equals(key2)) {
/* Key exists, no need for further checks. */
return null;
}
if (result == null) {
/* No next key exists, lock EOF. */
c.cursorImpl.lockEof(LockType.WRITE);
}
/* While next key is locked, check for key existence again. */
setEntry(key, key2);
result = c.dupsGetSearchKey(key2, data2, LockMode.RMW, cacheMode);
if (result != null) {
return null;
}
/* Insertion can safely be done now. */
result = c.dupsPutNoDupData(key, data, cacheMode, expInfo);
if (result == null) {
return null;
}
/* We successfully inserted the first dup for the key. */
swapCursor(c);
return result;
}
}
/**
* Interpret duplicates for putNoDupData operation.
*/
private OperationResult dupsPutNoDupData(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
return putNoDups(
twoPartKey, EMPTY_DUP_DATA, cacheMode, expInfo,
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 OperationResult dupsPutCurrent(
final DatabaseEntry newData,
final CacheMode cacheMode,
final ExpirationInfo expInfo) {
final DatabaseEntry oldTwoPartKey =
new DatabaseEntry(cursorImpl.getCurrentKey());
final DatabaseEntry key = new DatabaseEntry();
DupKeyData.split(oldTwoPartKey, key, null);
final DatabaseEntry newTwoPartKey = DupKeyData.combine(key, newData);
return putNoDups(
newTwoPartKey, EMPTY_DUP_DATA, cacheMode, expInfo,
PutMode.CURRENT);
}
/**
* Eventually, all insertions/updates are happening via this method.
*/
private OperationResult putNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final CacheMode cacheMode,
final ExpirationInfo expInfo,
final PutMode putMode) {
final LN ln = (putMode == PutMode.CURRENT) ?
null :
LN.makeLN(dbImpl.getEnv(), data);
return putNotify(
key, data, ln, cacheMode, expInfo, 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 OperationResult putNotify(
DatabaseEntry key,
final DatabaseEntry data,
final LN ln,
final CacheMode cacheMode,
ExpirationInfo expInfo,
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 OperationResult result;
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 effectively 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<SecondaryDatabase> 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();
}
/*
* Even if the TTL is not specified or changed, we need the
* ExpirationUpdated and OldExpirationTime for the
* secondary update.
*/
if (expInfo == null) {
expInfo = new ExpirationInfo(0, false, false);
}
}
}
/* Perform the actual put operation. */
if (putMode == PutMode.CURRENT) {
result = putCurrentNoNotify(
replaceKey, data, oldData, newData, cacheMode,
expInfo, repContext);
} else {
result = putNoNotify(
key, data, ln, cacheMode, expInfo, putMode,
oldData, newData, repContext);
}
if (result == null) {
return null;
}
/*
* If returned data is null, then
* 1. this is an insertion not an update, or
* 2. an expired LN was purged and the data could not be fetched.
*
* The latter case is acceptable because the old data is needed
* only to delete secondary records, and if the LN expired then
* those secondary records will also expire naturally. The old
* expirationTime is passed to updateSecondary below, which will
* prevent secondary integrity errors.
*/
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) {
int nWrites = 0;
for (final SecondaryDatabase secDb : secondaries) {
if (!result.isUpdate() ||
secDb.updateMayChangeSecondary()) {
nWrites += secDb.updateSecondary(
locker, null, dbImpl, cursorImpl, key,
oldData, newData, cacheMode,
result.getExpirationTime(),
expInfo.getExpirationUpdated(),
expInfo.getOldExpirationTime());
}
}
cursorImpl.setNSecondaryWrites(nWrites);
}
if (hasUserTriggers) {
TriggerManager.runPutTriggers(
locker, dbImpl, key, oldData, newData);
}
return result;
} 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 OperationResult where isUpdate() distinguishes insertions and
* updates.
*/
private OperationResult putNoNotify(
final DatabaseEntry key,
final DatabaseEntry data,
final LN ln,
final CacheMode cacheMode,
final ExpirationInfo expInfo,
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;
OperationResult result = null;
boolean success = false;
try {
final EnvironmentImpl envImpl = dbImpl.getEnv();
/*
* 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 (envImpl.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(
envImpl, cursorLocker);
nextKeyCursor = new CursorImpl(dbImpl, nextKeyLocker);
/* Perform eviction for user cursors. */
nextKeyCursor.setAllowEviction(true);
nextKeyCursor.lockNextKeyForInsert(key);
}
dup = beginMoveCursor(false /*samePosition*/, cacheMode);
/* Perform operation. */
result = dup.insertOrUpdateRecord(
key, data, ln, expInfo, putMode,
returnOldData, returnNewData, repContext);
if (includeInOpStats) {
if (result == null) {
if (putMode == PutMode.NO_OVERWRITE) {
envImpl.incInsertFailOps(dbImpl);
}
} else {
if (!result.isUpdate()) {
envImpl.incInsertOps(dbImpl);
} else {
envImpl.incUpdateOps(dbImpl);
}
}
}
/* Note that status is used in the finally. */
success = true;
return result;
} finally {
try {
if (dup != null) {
endMoveCursor(dup, result != null);
}
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 OperationResult putCurrentNoNotify(
final DatabaseEntry key,
final DatabaseEntry data,
final DatabaseEntry returnOldData,
final DatabaseEntry returnNewData,
final CacheMode cacheMode,
final ExpirationInfo expInfo,
final ReplicationContext repContext) {
assert data != null;
beginUseExistingCursor(cacheMode);
final OperationResult result = cursorImpl.updateCurrentRecord(
key, data, expInfo, returnOldData, returnNewData, repContext);
if (result != null && includeInOpStats) {
dbImpl.getEnv().incUpdateOps(dbImpl);
}
endUseExistingCursor();
return result;
}
/**
* Returns the current key and data. There is no need to use a dup cursor
* or prevent phantoms.
*/
OperationResult getCurrentInternal(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
return getCurrentHandleDups(key, data, lockMode, cacheMode);
}
return getCurrentNoDups(key, data, lockMode, cacheMode);
}
}
/**
* Used to lock without returning key/data. When called with
* LockMode.READ_UNCOMMITTED, it simply checks for a deleted record.
*/
OperationResult checkCurrent(final LockMode lockMode,
final CacheMode cacheMode) {
return getCurrentNoDups(null, null, lockMode, cacheMode);
}
/**
* Interpret duplicates for getCurrent operation.
*/
private OperationResult getCurrentHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationResult result = getCurrentNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
private OperationResult getCurrentNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
boolean success = false;
beginUseExistingCursor(cacheMode);
final LockType lockType = getLockType(lockMode, false);
try {
final OperationResult result = cursorImpl.lockAndGetCurrent(
key, data, lockType, lockMode == LockMode.READ_UNCOMMITTED_ALL,
false /*isLatched*/, false /*unlatch*/);
success = true;
return result;
} finally {
if (success &&
!dbImpl.isInternalDb() &&
cursorImpl.getBIN() != null &&
cursorImpl.getBIN().isBINDelta()) {
dbImpl.getEnv().incBinDeltaGets();
}
cursorImpl.releaseBIN();
endUseExistingCursor();
}
}
/**
* Internal version of getFirst/getLast that does no parameter checking.
* Interprets duplicates.
*/
OperationResult position(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final boolean first) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
final OperationResult result;
if (dbImpl.getSortedDuplicates()) {
result = positionHandleDups(
key, data, lockMode, cacheMode, first);
} else {
result = positionNoDups(
key, data, lockMode, cacheMode, first);
}
if (result != null && includeInOpStats) {
dbImpl.getEnv().incPositionOps(dbImpl);
}
return result;
}
}
/**
* Interpret duplicates for getFirst and getLast operations.
*/
private OperationResult positionHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final boolean first) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationResult result = positionNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode, first);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationResult positionNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final boolean first) {
try {
if (!isSerializableIsolation(lockMode)) {
return positionAllowPhantoms(
key, data, lockMode, cacheMode, 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 OperationResult result = positionAllowPhantoms(
key, data, lockMode, cacheMode, first /*rangeLock*/,
first);
/*
* Range lock the EOF node when getFirst returns null.
*/
if (first && result == null) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
return result;
} catch (RangeRestartException e) {
// continue
}
}
} catch (Error E) {
dbImpl.getEnv().invalidate(E);
throw E;
}
}
/**
* Positions without preventing phantoms.
*/
private OperationResult positionAllowPhantoms(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final boolean rangeLock,
final boolean first) {
assert (key != null && data != null);
OperationResult result = null;
final CursorImpl dup =
beginMoveCursor(false /*samePosition*/, cacheMode);
try {
/* Search for first or last slot. */
if (!dup.positionFirstOrLast(first)) {
/* Tree is empty. */
result = null;
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;
result = dup.lockAndGetCurrent(
key, data, lockType, dirtyReadAll,
true /*isLatched*/, false /*unlatch*/);
if (result == null) {
/*
* The BIN may be empty or the slot we're pointing at may
* be deleted.
*/
result = dup.getNext(
key, data, lockType, dirtyReadAll, first,
true /*isLatched*/, null /*rangeConstraint*/);
}
}
} finally {
dup.releaseBIN();
endMoveCursor(dup, result != null);
}
return result;
}
/**
* Retrieves the next or previous record. Prevents phantoms.
*/
OperationResult retrieveNext(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final GetMode getMode) {
synchronized (getTxnSynchronizer()) {
final OperationResult result;
if (dbImpl.getSortedDuplicates()) {
result = retrieveNextHandleDups(
key, data, lockMode, cacheMode, getMode);
} else {
result = retrieveNextNoDups(
key, data, lockMode, cacheMode, getMode);
}
if (result != null && includeInOpStats) {
dbImpl.getEnv().incPositionOps(dbImpl);
}
return result;
}
}
/**
* Interpret duplicates for getNext/Prev/etc operations.
*/
private OperationResult retrieveNextHandleDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final GetMode getMode) {
switch (getMode) {
case NEXT:
case PREV:
return dupsGetNextOrPrev(key, data, lockMode, cacheMode, getMode);
case NEXT_DUP:
return dupsGetNextOrPrevDup(
key, data, lockMode, cacheMode, GetMode.NEXT);
case PREV_DUP:
return dupsGetNextOrPrevDup(
key, data, lockMode, cacheMode, GetMode.PREV);
case NEXT_NODUP:
return dupsGetNextNoDup(key, data, lockMode, cacheMode);
case PREV_NODUP:
return dupsGetPrevNoDup(key, data, lockMode, cacheMode);
default:
throw EnvironmentFailureException.unexpectedState(
getMode.toString());
}
}
/**
* Interpret duplicates for getNext and getPrev.
*/
private OperationResult dupsGetNextOrPrev(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final GetMode getMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationResult result = retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode, getMode);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* 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 OperationResult dupsGetNextOrPrevDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final GetMode getMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
try (final Cursor c = dup(true /*samePosition*/)) {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
final DatabaseEntry twoPartKey = new DatabaseEntry();
final OperationResult result = c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode, getMode);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return result;
}
}
/**
* 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 OperationResult dupsGetNextNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
try (final Cursor c = dup(false /*samePosition*/)) {
c.setNonSticky(true);
final Comparator<byte[]> searchComparator =
new DupKeyData.NextNoDupComparator(
dbImpl.getBtreeComparator());
final OperationResult result = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, searchComparator);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return result;
}
}
/**
* 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 OperationResult dupsGetPrevNoDup(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final byte[] currentKey = cursorImpl.getCurrentKey();
final DatabaseEntry twoPartKey = DupKeyData.removeData(currentKey);
Cursor c = dup(false /*samePosition*/);
try {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
OperationResult result = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, null /*comparator*/);
if (result != null) {
c.rangeConstraint = null;
result = c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
GetMode.PREV);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return result;
}
} finally {
c.close();
}
c = dup(true /*samePosition*/);
try {
c.setNonSticky(true);
while (true) {
final OperationResult result =
c.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
GetMode.PREV);
if (result == null) {
return null;
}
if (!haveSameDupPrefix(twoPartKey, currentKey)) {
DupKeyData.split(twoPartKey, key, data);
swapCursor(c);
return result;
}
}
} finally {
c.close();
}
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationResult retrieveNextNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final GetMode getModeParam) {
final GetMode getMode;
switch (getModeParam) {
case NEXT_DUP:
case PREV_DUP:
return null;
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*/, cacheMode);
OperationResult result = null;
try {
result = dup.getNext(
key, data, getLockType(lockMode, false),
lockMode == LockMode.READ_UNCOMMITTED_ALL,
getMode.isForward(), false /*isLatched*/,
rangeConstraint);
return result;
} finally {
endMoveCursor(dup, result != null);
}
}
/*
* 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. */
OperationResult result = retrieveNextCheckForInsertion(
tryKey, tryData, lockType, cacheMode, getMode);
if (getMode.isForward() && result == null) {
/* NEXT: lock the EOF node. */
cursorImpl.lockEof(LockType.RANGE_READ);
}
/* Finally check rangeConstraint. */
if (result != null && !checkRangeConstraint(tryKey)) {
result = null;
}
/*
* Only overwrite key/data on SUCCESS, after all locking.
*/
if (result != null) {
setEntry(tryKey, key);
setEntry(tryData, data);
}
return result;
} 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);
OperationResult result;
CursorImpl dup = cursorImpl.cloneCursor(true /*samePosition*/);
try {
result = dup.lockAndGetCurrent(
tempKey, tempData, LockType.RANGE_READ);
if (result == null) {
while (true) {
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
result = 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 (result == null) {
cursorImpl.lockEof(LockType.RANGE_READ);
}
}
/**
* Retrieves and checks for insertions, for serializable isolation.
*/
private OperationResult retrieveNextCheckForInsertion(
final DatabaseEntry key,
final DatabaseEntry data,
final LockType lockType,
final CacheMode cacheMode,
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*/, cacheMode);
boolean doEndMoveCursor = true;
try {
final OperationResult result = dup.getNext(
key, data, lockType, false /*dirtyReadAll*/,
getMode.isForward(), false /*isLatched*/,
null /*rangeConstraint*/);
if (!cursorImpl.checkForInsertion(getMode, dup)) {
doEndMoveCursor = false;
endMoveCursor(dup, result != null);
if (LatchSupport.TRACK_LATCHES) {
LatchSupport.expectBtreeLatchesHeld(0);
}
return result;
}
} 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 CacheMode cacheMode) {
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*/, cacheMode);
boolean success = false;
try {
final long count = dup.skip(forward, maxCount,
null /*rangeConstraint*/);
if (count <= 0) {
return 0;
}
final OperationResult result =
getCurrentWithCursorImpl(dup, key, data, lockType);
if (result == null) {
/* 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 OperationResult 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 OperationResult result =
c.lockAndGetCurrent(twoPartKey, NO_RETURN_DATA, lockType);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* Performs search by key, data, or both. Prevents phantoms.
*/
OperationResult search(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
SearchMode searchMode,
final boolean countOpStat) {
synchronized (getTxnSynchronizer()) {
final OperationResult result;
checkTxnState();
if (dbImpl.getSortedDuplicates()) {
switch (searchMode) {
case SET:
result = dupsGetSearchKey(key, data, lockMode, cacheMode);
break;
case SET_RANGE:
result = dupsGetSearchKeyRange(
key, data, lockMode, cacheMode);
break;
case BOTH:
result = dupsGetSearchBoth(key, data, lockMode, cacheMode);
break;
case BOTH_RANGE:
result = dupsGetSearchBothRange(
key, data, lockMode, cacheMode);
break;
case ANY_RANGE:
result = dupsGetSearchAnyRange(
key, data, lockMode, cacheMode);
break;
default:
throw EnvironmentFailureException.unexpectedState(
searchMode.toString());
}
} else {
if (searchMode == SearchMode.BOTH_RANGE) {
searchMode = SearchMode.BOTH;
} else if (searchMode == SearchMode.ANY_RANGE) {
searchMode = SearchMode.SET_RANGE;
}
result = searchNoDups(
key, data, lockMode, cacheMode, searchMode,
null /*comparator*/);
}
if (countOpStat && includeInOpStats) {
if (result != null) {
dbImpl.getEnv().incSearchOps(dbImpl);
} else {
dbImpl.getEnv().incSearchFailOps(dbImpl);
}
}
return result;
}
}
/**
* Version of search that does not interpret duplicates. Used for
* replication stream replay. Prevents phantoms.
*/
OperationResult searchForReplay(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final SearchMode searchMode) {
synchronized (getTxnSynchronizer()) {
checkTxnState();
return searchNoDups(
key, data, lockMode, cacheMode, 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 OperationResult dupsGetSearchKey(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry(
DupKeyData.makePrefixKey(key.getData(),
key.getOffset(),
key.getSize()));
final RangeConstraint savedRangeConstraint = rangeConstraint;
try {
setPrefixConstraint(this, key);
final OperationResult result = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, null /*comparator*/);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
} finally {
rangeConstraint = savedRangeConstraint;
}
}
/**
* Interpret duplicates for getSearchKeyRange operation.
*
* Do range search for key prefix.
*/
private OperationResult dupsGetSearchKeyRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = new DatabaseEntry(
DupKeyData.makePrefixKey(key.getData(),
key.getOffset(),
key.getSize()));
final OperationResult result = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, null /*comparator*/);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* Interpret duplicates for getSearchBoth operation.
*
* Do exact search for combined key.
*/
private OperationResult dupsGetSearchBoth(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
final OperationResult result = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode, SearchMode.BOTH,
null /*comparator*/);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* 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
* null should be returned.
*/
private OperationResult dupsGetSearchBothRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
final RangeConstraint savedRangeConstraint = rangeConstraint;
try {
setPrefixConstraint(this, key);
final OperationResult result = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, null /*comparator*/);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
} finally {
rangeConstraint = savedRangeConstraint;
}
}
/**
* Interpret duplicates for Get.SEARCH_ANY_GTE operation.
*
* Do range search for combined key.
*/
private OperationResult dupsGetSearchAnyRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode) {
final DatabaseEntry twoPartKey = DupKeyData.combine(key, data);
final OperationResult result = searchNoDups(
twoPartKey, NO_RETURN_DATA, lockMode, cacheMode,
SearchMode.SET_RANGE, null /*comparator*/);
if (result == null) {
return null;
}
DupKeyData.split(twoPartKey, key, data);
return result;
}
/**
* Does not interpret duplicates. Prevents phantoms.
*/
private OperationResult searchNoDups(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final SearchMode searchMode,
final Comparator<byte[]> 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);
/*
* searchMode cannot be ANY_RANGE, because ANY_RANGE is always
* converted to SET_RANGE.
*/
assert(searchMode != SearchMode.ANY_RANGE);
try {
if (!isSerializableIsolation(lockMode)) {
if (searchMode.isExactSearch()) {
assert(comparator == null);
return searchExact(
key, data, lockMode, cacheMode, searchMode);
}
while (true) {
try {
return searchRange(
key, data, lockMode, cacheMode, comparator);
} catch (RangeRestartException e) {
// continue
}
}
}
/*
* Perform range locking to prevent phantoms and handle restarts.
*/
while (true) {
OperationResult 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, cacheMode, searchMode);
if (result != null) {
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 OperationResult searchExact(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
final SearchMode searchMode) {
assert(key != null && data != null);
assert(searchMode == SearchMode.SET || searchMode == SearchMode.BOTH);
boolean success = false;
OperationResult result = null;
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*/, cacheMode);
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 null;
}
/*
* 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 copy into "key" the key of
* the found slot if a partial key comparator is used, since then
* it may be different than the given key.
*/
result = dup.getCurrent(
dbImpl.allowsKeyUpdates() ? key : null, data);
/* Check for data match, if asked so. */
if (result != null &&
searchMode == SearchMode.BOTH &&
!checkDataMatch(origData, data)) {
result = null;
}
success = true;
} finally {
if (success &&
!dbImpl.isInternalDb() &&
dup.getBIN() != null &&
dup.getBIN().isBINDelta()) {
dbImpl.getEnv().incBinDeltaGets();
}
dup.releaseBIN();
endMoveCursor(dup, result != null);
}
return result;
}
/**
* 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 OperationResult searchRange(
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode,
final CacheMode cacheMode,
Comparator<byte[]> comparator)
throws RangeRestartException {
assert(key != null && data != null);
boolean success = false;
boolean incStats = !dbImpl.isInternalDb();
OperationResult result = null;
final LockType lockType = getLockType(lockMode, false);
final boolean dirtyReadAll =
lockMode == LockMode.READ_UNCOMMITTED_ALL;
final CursorImpl dup =
beginMoveCursor(false /*samePosition*/, cacheMode);
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 null;
}
/*
* 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) {
result = 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 || result == null) {
result = null;
if (!foundLast) {
result = 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()) {
dbImpl.getEnv().incBinDeltaGets();
}
dup.releaseBIN();
endMoveCursor(dup, result != null);
}
return result;
}
/**
* 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 OperationResult searchRangeSerializable(
final DatabaseEntry key,
final DatabaseEntry data,
final LockType searchLockType,
final LockType advanceLockType,
final Comparator<byte[]> comparator,
final CacheMode cacheMode,
final SearchMode searchMode)
throws RangeRestartException {
assert(key != null && data != null);
boolean success = false;
boolean incStats = !dbImpl.isInternalDb();
OperationResult result = null;
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*/, cacheMode);
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) {
result = 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 || result == null) {
result = null;
if (!foundLast) {
result = searchRangeAdvanceAndCheckKey(
dup, key, data, advanceLockType,
false /*dirtyReadAll*/, comparator,
null /*rangeConstraint*/);
keyChange = (result != null);
incStats = false;
}
mustLockEOF = (result == null);
}
/*
* 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 (result != null && exactSearch) {
if (keyChange) {
result = null;
} else if (searchMode == SearchMode.BOTH &&
!checkDataMatch(origData, data)) {
result = null;
}
}
/* Finally check rangeConstraint. */
if (result != null &&
!exactSearch &&
!checkRangeConstraint(key)) {
result = null;
}
} 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()) {
dbImpl.getEnv().incBinDeltaGets();
}
dup.releaseBIN();
endMoveCursor(dup, result != null);
}
/*
* 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 result;
}
/*
* Helper method for searchRange and searchRangeSerializable
*
* @throws RangeRestartException if the search should be restarted by the
* caller.
*/
private OperationResult searchRangeAdvanceAndCheckKey(
final CursorImpl dup,
final DatabaseEntry key,
final DatabaseEntry data,
final LockType lockType,
final boolean dirtyReadAll,
Comparator<byte[]> 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());
}
OperationResult result = 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 (result != null) {
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 result;
}
/**
* 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);
try (final Cursor c = dup(false /*samePosition*/)) {
c.setNonSticky(true);
setPrefixConstraint(c, currentKey);
/* Move cursor to first key in this dup set. */
OperationResult result = c.searchNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
CacheMode.UNCHANGED, SearchMode.SET_RANGE, null /*comparator*/);
if (result == null) {
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;
}
}
/**
* When there are no duplicates, the count is either 0 or 1, and is very
* cheap to determine.
*/
private int countNoDups() {
try {
beginUseExistingCursor(CacheMode.UNCHANGED);
final OperationResult result = cursorImpl.lockAndGetCurrent(
null /*foundKey*/, null /*foundData*/, LockType.NONE);
endUseExistingCursor();
return (result != null) ? 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);
try (final Cursor c1 = dup(false /*samePosition*/)) {
c1.setNonSticky(true);
setPrefixConstraint(c1, currentKey);
/* Move cursor 1 to first key in this dup set. */
OperationResult result = c1.searchNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
CacheMode.UNCHANGED, SearchMode.SET_RANGE,
null /*comparator*/);
if (result == null) {
return 0;
}
/* Move cursor 2 to first key in the following dup set. */
try (Cursor c2 = c1.dup(true /*samePosition*/)) {
c2.setNonSticky(true);
result = c2.dupsGetNextNoDup(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
CacheMode.UNCHANGED);
final boolean c2Inclusive;
if (result != null) {
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.
*/
result = c2.positionNoDups(
twoPartKey, NO_RETURN_DATA, LockMode.READ_UNCOMMITTED,
CacheMode.UNCHANGED, false /*first*/);
if (result == null) {
return 0;
}
while (!haveSameDupPrefix(twoPartKey, currentKey)) {
result = c2.retrieveNextNoDups(
twoPartKey, NO_RETURN_DATA,
LockMode.READ_UNCOMMITTED, CacheMode.UNCHANGED,
GetMode.PREV);
if (result == null) {
return 0;
}
}
}
/* Estimate the count between the two cursor positions. */
return CountEstimator.count(
dbImpl, c1.cursorImpl, true, c2.cursorImpl, c2Inclusive);
}
}
}
/**
* 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 a true status is returned by this method, the caller should return
* a successful result. When false 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 failure/null (in the case
* of getCurrent). False can be returned only when read-uncommitted is used
* or the primary record has expired.
*
* @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.
*
* @param verifyPrimary If true, we are only checking integrity and we read
* the primary even though the data is not requested.
*
* @param locker is the Locker to use for accessing the primary record.
*
* @param secDb is the Database handle of the secondary database. Note
* that the dbHandle field may be null and should not be used by this
* method.
*
* @param secAssoc is the SecondaryAssociation of the secondary database.
* It is used to check whether the secondary database is still in the
* SecondaryAssociation before throwing SecondaryIntegrityException. If
* not, we will not throw SecondaryIntegrityException.
*
* @return true if the primary was read successfully, or false in one of
* the following cases:
* + When using read-uncommitted and the primary has been deleted.
* + When using read-uncommitted and the primary has been updated and no
* longer contains the secondary key.
* + When the primary record has expired (whether or not read-uncommitted
* is used).
*
* @throws SecondaryIntegrityException to indicate a corrupt secondary
* reference if the primary record is deleted (as opposed to expired) and
* read-uncommitted is not used.
*/
boolean readPrimaryAfterGet(
final Database priDb,
final DatabaseEntry key,
final DatabaseEntry pKey,
DatabaseEntry data,
final LockMode lockMode,
final boolean secDirtyRead,
final boolean lockPrimaryOnly,
final boolean verifyPrimary,
final Locker locker,
final Database secDb,
final SecondaryAssociation secAssoc) {
final boolean priDirtyRead = isReadUncommittedMode(lockMode);
final DatabaseImpl priDbImpl = priDb.getDbImpl();
/*
* 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 && !priDirtyRead;
} else {
assert secDirtyRead == priDirtyRead;
}
final boolean dataRequested =
!data.getPartial() || data.getPartialLength() > 0;
/*
* In most cases, there is no need to read the primary if no data is
* requested. In these case a lock on the secondary has been
* acquired (if the caller did not specify dirty-read).
*
* But for btree verification, we need to check whether the primary
* record still exists without requesting the data.
*/
if (!dataRequested && !verifyPrimary) {
data.setData(LogUtils.ZERO_LENGTH_BYTE_ARRAY);
return true;
}
/*
* 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(
priDbImpl, locker, true /*retainNonTxnLocks*/,
false /*isSecondaryCursor*/);
try {
final LockType priLockType = getLockType(lockMode, false);
final boolean dirtyReadAll =
lockMode == LockMode.READ_UNCOMMITTED_ALL;
LockStanding priLockStanding = priCursor.searchExact(
pKey, priLockType, dirtyReadAll, dataRequested);
try {
if (priLockStanding != null) {
if (priCursor.getCurrent(null, data) == null) {
priCursor.revertLock(priLockStanding);
priLockStanding = null;
}
}
} finally {
priCursor.releaseBIN();
}
if (priLockStanding != null && lockPrimaryOnly) {
if (!ensureReferenceToPrimary(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. [#22603]
*
* If the secondary is expired (within TTL clock tolerance),
* then the record must have expired after the secondary read.
*
* In either case, return false to skip this record.
*/
if (secDirtyRead || cursorImpl.isProbablyExpired()) {
return false;
}
/*
* TODO: whether we need to do the following check for all
* usage scenarios of readPrimaryAfterGet. If true, we
* may get the SecondaryAssociation by the secDb.
*
* If secDb has been removed from SecondaryAssociation, the
* operations on the primary database after removing it
* may cause an inconsistency between the secondary record and
* the corresponding primary record. For this case, just return
* false to skip this record.
*/
if (secAssoc != null) {
boolean stillExist = false;
for (SecondaryDatabase db : secAssoc.getSecondaries(pKey)) {
if (db == secDb) {
stillExist = true;
break;
}
}
if (!stillExist) {
return false;
}
}
/*
* When the primary is deleted, secondary keys are deleted
* first. So if the above check fails, we know the secondary
* reference is corrupt.
*/
throw secDb.secondaryRefersToMissingPrimaryKey(
locker, priDb, key, pKey, cursorImpl.getExpirationTime());
}
/*
* 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 false.
*/
if (priDirtyRead && checkForPrimaryUpdate(key, pKey, data)) {
return false;
}
/*
* 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());
}
/* Copy primary record info to secondary cursor. */
cursorImpl.setPriInfo(priCursor);
if (includeInOpStats) {
priDbImpl.getEnv().incSearchOps(priDbImpl);
}
return true;
} finally {
priCursor.close();
}
}
/**
* Checks whether this secondary cursor still refers to the primary key,
* and locks the secondary record if necessary.
*
* 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. There are two cases:
*
* 1. If the secondary DB has duplicates, the key contains the sec/pri
* relationship and the presence of the secondary record (that is not
* deleted) is sufficient to insure the sec/pri relationship.
*
* 2. If the secondary DB does not allow duplicates, then the primary key
* (the data of the secondary record) must additionally be compared to
* the original search key. This detects the case where the secondary
* record was updated to refer to a different primary key.
*
* In addition, this method locks the secondary record if it would expire
* within {@link EnvironmentParams#ENV_TTL_MAX_TXN_TIME}. This is needed to
* support repeatable-read. The lock prevents expiration of the secondary.
*/
private boolean ensureReferenceToPrimary(
final DatabaseEntry matchPriKey,
final LockType lockType) {
assert lockType != LockType.NONE;
/*
* 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 serializable 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.isDeleted(index)) {
return false;
}
final EnvironmentImpl envImpl = dbImpl.getEnv();
/* Additionally, lock the secondary if it expires soon. */
final long expirationTime = TTL.expirationToSystemTime(
bin.getExpiration(index), bin.isExpirationInHours());
if (envImpl.expiresWithin(
expirationTime, envImpl.getTtlMaxTxnTime())) {
cursorImpl.lockLN(lockType);
}
} finally {
cursorImpl.releaseBIN();
}
/*
* If there are no duplicates, check the secondary data (primary key).
* No need to actually lock (use LockType.NONE) since the primary lock
* protects the secondary from changes.
*/
if (!cursorImpl.hasDuplicates()) {
final DatabaseEntry secData = new DatabaseEntry();
if (cursorImpl.lockAndGetCurrent(
null, secData, LockType.NONE) == null) {
return false;
}
if (!secData.equals(matchPriKey)) {
return false;
}
}
return true;
}
/**
* 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 so dictates, 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 uninitialized.
* - 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 uninitialized.
* - 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#performCacheModeEviction for a description of how the
* cache mode is used. This method ensures that the correct cache mode
* is used before each operation.
*/
private CursorImpl beginMoveCursor(
final boolean samePosition,
final boolean forceClone,
final CacheMode cacheMode) {
/*
* 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 != null ? cacheMode : defaultCacheMode);
if (cursorImpl.isNotInitialized()) {
cursorImpl.criticalEviction();
return cursorImpl;
}
if (nonSticky && !forceClone) {
if (samePosition) {
cursorImpl.beforeNonStickyOp();
} else {
cursorImpl.reset();
}
return cursorImpl;
}
final CursorImpl dup = cursorImpl.cloneCursor(samePosition);
dup.setClosingLocker(cursorImpl);
return dup;
}
private CursorImpl beginMoveCursor(final boolean samePosition,
final CacheMode cacheMode) {
return beginMoveCursor(samePosition, false /*forceClone*/, cacheMode);
}
/**
* 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.afterNonStickyOp();
} 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(final CacheMode cacheMode) {
/* Must set cache mode before calling criticalEviction. */
cursorImpl.setCacheMode(
cacheMode != null ? cacheMode : defaultCacheMode);
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);
}
private void checkUpdatesAllowed(final ExpirationInfo expInfo) {
checkUpdatesAllowed();
if (dbImpl.isReplicated() &&
expInfo != null && expInfo.expiration > 0) {
/* Throws IllegalStateException if TTL is not available. */
dbImpl.getEnv().checkTTLAvailable();
}
}
private void checkUpdatesAllowed() {
if (updateOperationsProhibited) {
throw updatesProhibitedException(cursorImpl.getLocker());
}
if (!dbImpl.getDbType().isInternal()) {
final String diskLimitViolation =
dbImpl.getEnv().getDiskLimitViolation();
if (diskLimitViolation != null) {
throw new DiskLimitException(
cursorImpl.getLocker(), diskLimitViolation);
}
}
}
private UnsupportedOperationException updatesProhibitedException(
final Locker locker) {
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() &&
!dbImpl.getDbType().isMixedReplication()) {
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());
}
/**
* Checks the cursor state.
*/
void checkState(final boolean mustBeInitialized) {
cursorImpl.checkCursorState(
mustBeInitialized, false /*mustNotBeInitialized*/);
}
/**
* Checks the environment, DB handle, and cursor state.
*/
void checkOpenAndState(final boolean mustBeInitialized) {
checkEnv();
checkOpen();
checkState(mustBeInitialized);
}
/**
* Checks the environment and DB handle.
*/
void checkOpen() {
checkEnv();
if (dbHandle != null) {
dbHandle.checkOpen();
}
}
/**
* @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 String getOrPutType,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
if (logger.isLoggable(level)) {
final StringBuilder sb = new StringBuilder();
sb.append(methodName);
sb.append(getOrPutType);
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 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());
}
}