com.sleepycat.je.Database Maven / Gradle / Ivy
/*-
* 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.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.GetMode;
import com.sleepycat.je.dbi.SearchMode;
import com.sleepycat.je.dbi.TriggerManager;
import com.sleepycat.je.evictor.OffHeapCache;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.txn.HandleLocker;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.LockerFactory;
import com.sleepycat.je.utilint.DatabaseUtil;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.LoggerUtils;
/**
* A database handle.
*
* Database attributes are specified in the {@link
* com.sleepycat.je.DatabaseConfig DatabaseConfig} class. Database handles are
* free-threaded and may be used concurrently by multiple threads.
*
* To open an existing database with default attributes:
*
*
* Environment env = new Environment(home, null);
* Database myDatabase = env.openDatabase(null, "mydatabase", null);
*
*
* To create a transactional database that supports duplicates:
*
*
* DatabaseConfig dbConfig = new DatabaseConfig();
* dbConfig.setTransactional(true);
* dbConfig.setAllowCreate(true);
* dbConfig.setSortedDuplicates(true);
* Database db = env.openDatabase(txn, "mydatabase", dbConfig);
*
*/
public class Database implements Closeable {
static final CursorConfig DEFAULT_CURSOR_CONFIG =
CursorConfig.DEFAULT.clone().setNonSticky(true);
static final CursorConfig READ_COMMITTED_CURSOR_CONFIG =
CursorConfig.READ_COMMITTED.clone().setNonSticky(true);
/*
* DbState embodies the Database handle state.
*/
enum DbState {
OPEN, CLOSED, INVALID, PREEMPTED, CORRUPTED
}
/*
* The current state of the handle. When the state is non-open, the
* databaseImpl should not be accessed, since the databaseImpl is set to
* null during close. This check does not guarantee that an NPE will not
* occur, since we do not synchronize -- the state could change after
* checking it and before accessing the databaseImpl. But the check makes
* an NPE unlikely.
*/
private volatile DbState state;
/* Effectively final since an open DB cannot be renamed. */
private String name;
/* The DatabasePreemptedException cause when state == PREEMPTED. */
private volatile OperationFailureException preemptedCause;
/* The SecondaryIntegrityException cause when state == CORRUPTED. */
private volatile SecondaryIntegrityException corruptedCause;
/*
* The envHandle field cannot be declared as final because it is
* initialized by methods called by the ctor. However, after construction
* it is non-null and should be treated as final.
*/
Environment envHandle;
/*
* The databaseImpl field is set to null during close to avoid OOME. It
* should normally only be accessed via the checkOpen and getDbImpl
* methods. It is guaranteed to be non-null if state == DbState.OPEN.
*/
private DatabaseImpl databaseImpl;
/*
* Used to store per-Database handle properties: allow create,
* exclusive create, read only and use existing config. Other Database-wide
* properties are stored in DatabaseImpl.
*/
DatabaseConfig configuration;
/* True if this handle permits write operations; */
private boolean isWritable;
/* Record how many open cursors on this database. */
private final AtomicInteger openCursors = new AtomicInteger();
/*
* Locker that owns the NameLN lock held while the Database is open.
*
* The handleLocker field is set to null during close to avoid OOME. It
* is only accessed during close, while synchronized, so checking for null
* is unnecessary.
*/
private HandleLocker handleLocker;
/*
* If a user-supplied SecondaryAssociation is configured, this field
* contains it. Otherwise, it contains an internal SecondaryAssociation
* that uses the simpleAssocSecondaries to store associations between a
* single primary and its secondaries.
*/
SecondaryAssociation secAssoc;
Collection simpleAssocSecondaries;
/*
* Secondaries whose keys have values contrained to the primary keys in
* this database.
*/
Collection foreignKeySecondaries;
final Logger logger;
/**
* Creates a database but does not open or fully initialize it. Is
* protected for use in compat package.
*/
Database(final Environment env) {
this.envHandle = env;
logger = getEnv().getLogger();
}
/**
* Creates a database, called by Environment.
*/
DatabaseImpl initNew(final Environment env,
final Locker locker,
final String databaseName,
final DatabaseConfig dbConfig) {
dbConfig.validateForNewDb();
init(env, dbConfig, databaseName);
/* Make the databaseImpl. */
databaseImpl = getEnv().getDbTree().createDb(
locker, databaseName, dbConfig, handleLocker);
databaseImpl.addReferringHandle(this);
return databaseImpl;
}
/**
* Opens a database, called by Environment.
*/
void initExisting(final Environment env,
final Locker locker,
final DatabaseImpl dbImpl,
final String databaseName,
final DatabaseConfig dbConfig) {
/*
* Make sure the configuration used for the open is compatible with the
* existing databaseImpl.
*/
validateConfigAgainstExistingDb(locker, databaseName, dbConfig,
dbImpl);
init(env, dbConfig, databaseName);
this.databaseImpl = dbImpl;
dbImpl.addReferringHandle(this);
}
private void init(final Environment env,
final DatabaseConfig config,
final String databaseName) {
assert handleLocker != null;
envHandle = env;
configuration = config.clone();
isWritable = !configuration.getReadOnly();
secAssoc = makeSecondaryAssociation();
state = DbState.OPEN;
name = databaseName;
}
SecondaryAssociation makeSecondaryAssociation() {
foreignKeySecondaries = new CopyOnWriteArraySet<>();
if (configuration.getSecondaryAssociation() != null) {
if (configuration.getSortedDuplicates()) {
throw new IllegalArgumentException(
"Duplicates not allowed for a primary database");
}
simpleAssocSecondaries = Collections.emptySet();
return configuration.getSecondaryAssociation();
}
simpleAssocSecondaries = new CopyOnWriteArraySet<>();
return new SecondaryAssociation() {
public boolean isEmpty() {
return simpleAssocSecondaries.isEmpty();
}
public Database getPrimary(@SuppressWarnings("unused")
DatabaseEntry primaryKey) {
return Database.this;
}
public Collection
getSecondaries(@SuppressWarnings("unused")
DatabaseEntry primaryKey) {
return simpleAssocSecondaries;
}
};
}
/**
* Used to remove references to this database from other objects, when this
* database is closed. We don't remove references from cursors or
* secondaries here, because it's an error to close a database before its
* cursors and to close a primary before its secondaries.
*/
void removeReferringAssociations() {
envHandle.removeReferringHandle(this);
}
/**
* Sees if this new handle's configuration is compatible with the
* pre-existing database.
*/
private void validateConfigAgainstExistingDb(Locker locker,
final String databaseName,
final DatabaseConfig config,
final DatabaseImpl dbImpl) {
/*
* The sortedDuplicates, temporary, and replicated properties are
* persistent and immutable. But they do not need to be specified if
* the useExistingConfig property is set.
*/
if (!config.getUseExistingConfig()) {
validatePropertyMatches(
"sortedDuplicates", dbImpl.getSortedDuplicates(),
config.getSortedDuplicates());
validatePropertyMatches(
"temporary", dbImpl.isTemporary(),
config.getTemporary());
/* Only check replicated if the environment is replicated. */
if (getEnv().isReplicated()) {
validatePropertyMatches(
"replicated", dbImpl.isReplicated(),
config.getReplicated());
}
}
/*
* The transactional and deferredWrite properties are kept constant
* while any handles are open, and set when the first handle is opened.
* But if an existing handle is open and the useExistingConfig property
* is set, then they do not need to be specified.
*/
if (dbImpl.hasOpenHandles()) {
if (!config.getUseExistingConfig()) {
validatePropertyMatches(
"transactional", dbImpl.isTransactional(),
config.getTransactional());
validatePropertyMatches(
"deferredWrite", dbImpl.isDurableDeferredWrite(),
config.getDeferredWrite());
}
} else {
dbImpl.setTransactional(config.getTransactional());
dbImpl.setDeferredWrite(config.getDeferredWrite());
if (config.getDeferredWrite()) {
mutateDeferredWriteBINDeltas(dbImpl);
}
}
/*
* If this database handle uses the existing config, we shouldn't
* search for and write any changed attributes to the log.
*/
if (config.getUseExistingConfig()) {
return;
}
/* Write any changed, persistent attributes to the log. */
boolean dbImplModified = false;
/* Only re-set the comparators if the override is allowed. */
if (config.getOverrideBtreeComparator()) {
dbImplModified |= dbImpl.setBtreeComparator(
config.getBtreeComparator(),
config.getBtreeComparatorByClassName());
}
if (config.getOverrideDuplicateComparator()) {
dbImplModified |= dbImpl.setDuplicateComparator(
config.getDuplicateComparator(),
config.getDuplicateComparatorByClassName());
}
dbImplModified |= dbImpl.setTriggers(locker,
databaseName,
config.getTriggers(),
config.getOverrideTriggers());
/* Check if KeyPrefixing property is updated. */
boolean newKeyPrefixing = config.getKeyPrefixing();
if (newKeyPrefixing != dbImpl.getKeyPrefixing()) {
dbImplModified = true;
if (newKeyPrefixing) {
dbImpl.setKeyPrefixing();
} else {
dbImpl.clearKeyPrefixing();
}
}
/*
* Check if NodeMaxEntries properties are updated.
*/
int newNodeMaxEntries = config.getNodeMaxEntries();
if (newNodeMaxEntries != 0 &&
newNodeMaxEntries != dbImpl.getNodeMaxTreeEntries()) {
dbImplModified = true;
dbImpl.setNodeMaxTreeEntries(newNodeMaxEntries);
}
/* Do not write LNs in a read-only environment. Also see [#15743]. */
EnvironmentImpl envImpl = getEnv();
if (dbImplModified && !envImpl.isReadOnly()) {
/* Write a new NameLN to the log. */
try {
envImpl.getDbTree().updateNameLN(locker, dbImpl.getName(),
null);
} catch (LockConflictException e) {
throw new IllegalStateException(
"DatabaseConfig properties may not be updated when the " +
"database is already open; first close other open " +
"handles for this database.", e);
}
/* Dirty the root. */
envImpl.getDbTree().modifyDbRoot(dbImpl);
}
/* CacheMode is changed for all handles, but is not persistent. */
dbImpl.setCacheMode(config.getCacheMode());
}
/**
* Mutate BIN-deltas to full BINs for the given DW DB. [#25999]
*
* Any BIN-deltas in cache must be mutated to full BINs, since
* BIN-deltas are not allowed in DW mode. This can be expensive, and is
* only a workaround for the underlying problem that BIN-deltas are not
* supported in DW mode. The mutation is necessary when a db transitions
* from non-DW to DW mode. In that case there may be BIN-deltas in
* cache, even if the DB has not yet been opened since BIN-deltas may
* be placed in the Btree by recovery. This method is not optimized
* because ultimately BIN-deltas will be supported in DW mode or DW mode
* will be discontinued.
*
* At the time this method is called, the DW flag on this object has
* already been set. Therefore, this workaround doesn't guarantee that
* internal operations on the db (e.g., compression or eviction) won't
* operation on a BIN-delta in a DW DB. Therefore, we cannot assume/assert
* that a cached BIN-delta does not exist in a DW DB. Such assertions were
* removed as part of this workaround.
*
* Setting the DW flag before mutating the BINs is necessary to ensure
* that no new BIN-deltas appear in cache during this process. BINs loaded
* into cache for a DW db are mutated to full BINs in IN.postFetchInit.
*
* @see BIN#shouldLogDelta
*/
private void mutateDeferredWriteBINDeltas(DatabaseImpl dbImpl) {
final OffHeapCache ohCache = getEnv().getOffHeapCache();
for (final IN in : getEnv().getInMemoryINs()) {
if (in.getDatabase() != dbImpl) {
continue;
}
in.latchNoUpdateLRU();
try {
if (in.isBIN()) {
in.mutateToFullBIN(false);
continue;
}
if (ohCache == null ||
in.getNormalizedLevel() != 2) {
continue;
}
for (int i = 0; i < in.getNEntries(); i += 1) {
if (in.getOffHeapBINId(i) < 0) {
continue;
}
final IN child = in.loadIN(i, CacheMode.UNCHANGED);
if (child == null) {
continue;
}
child.latchNoUpdateLRU();
try {
child.mutateToFullBIN(false);
} finally {
child.releaseLatch();
}
}
} finally {
in.releaseLatch();
}
}
}
/**
* @throws IllegalArgumentException via Environment.openDatabase and
* openSecondaryDatabase.
*/
private void validatePropertyMatches(final String propName,
final boolean existingValue,
final boolean newValue) {
if (newValue != existingValue) {
throw new IllegalArgumentException(
"You can't open a Database with a " + propName +
" configuration of " + newValue +
" if the underlying database was created with a " +
propName + " setting of " + existingValue + '.');
}
}
/**
* Discards the database handle.
*
* When closing the last open handle for a deferred-write database, any
* cached database information is flushed to disk as if {@link #sync} were
* called.
*
* The database handle should not be closed while any other handle that
* refers to it is not yet closed; for example, database handles should not
* be closed while cursor handles into the database remain open, or
* transactions that include operations on the database have not yet been
* committed or aborted. Specifically, this includes {@link
* com.sleepycat.je.Cursor Cursor} and {@link com.sleepycat.je.Transaction
* Transaction} handles.
*
* When multiple threads are using the {@link com.sleepycat.je.Database
* Database} handle concurrently, only a single thread may call this
* method.
*
* When called on a database that is the primary database for a secondary
* index, the primary database should be closed only after all secondary
* indices which reference it have been closed.
*
* The database handle may not be accessed again after this method is
* called, regardless of the method's success or failure, with one
* exception: the {@code close} method itself may be called any number of
* times.
*
* WARNING: To guard against memory leaks, the application should
* discard all references to the closed handle. While BDB makes an effort
* to discard references from closed objects to the allocated memory for an
* environment, this behavior is not guaranteed. The safe course of action
* for an application is to discard all references to closed BDB
* objects.
*
* @see DatabaseConfig#setDeferredWrite DatabaseConfig.setDeferredWrite
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if cursors associated with this database
* are still open.
*/
public void close() {
try {
closeInternal(true /*doSyncDw*/, true /*deleteTempDb*/,
DbState.CLOSED, null /*preemptedException*/);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/*
* This method is private for now because it is incomplete. To fully
* implement it we must clear all dirty nodes for the database that is
* closed, since otherwise they will be flushed during the next checkpoint.
*/
@SuppressWarnings("unused")
private void closeNoSync() {
try {
closeInternal(false /*doSyncDw*/, true /*deleteTempDb*/,
DbState.CLOSED, null /*preemptedException*/);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Marks the handle as preempted when the handle lock is stolen by the HA
* replayer, during replay of a naming operation (remove, truncate or
* rename). This causes DatabasePreemptedException to be thrown on all
* subsequent use of the handle or cursors opened on this handle. [#17015]
*/
synchronized void setPreempted(final String dbName, final String msg) {
/*
* Return silently when the DB is closed, because the calling thread is
* performing an DbTree operation, and a "database closed" exception
* should not be thrown there.
*/
final DatabaseImpl dbImpl = databaseImpl;
if (dbImpl == null) {
return;
}
final OperationFailureException preemptedException =
dbImpl.getEnv().createDatabasePreemptedException(
msg, dbName, this);
closeInternal(false /*doSyncDw*/, false /*deleteTempDb*/,
DbState.PREEMPTED, preemptedException);
}
synchronized void setCorrupted(SecondaryIntegrityException sie) {
if (state != DbState.OPEN) {
return;
}
corruptedCause = sie;
state = DbState.CORRUPTED;
}
boolean isCorrupted() {
return corruptedCause != null;
}
SecondaryIntegrityException getCorruptedCause() {
return corruptedCause;
}
/**
* Invalidates the handle when the transaction used to open the database
* is aborted.
*
* Note that this method (unlike close) does not perform sync and removal
* of DW DBs. A DW DB cannot be transactional.
*/
synchronized void invalidate() {
closeInternal(false /*doSyncDw*/, false /*deleteTempDb*/,
DbState.INVALID, null /*preemptedException*/);
}
EnvironmentImpl getEnv() {
return envHandle.getNonNullEnvImpl();
}
private void closeInternal(
final boolean doSyncDw,
final boolean deleteTempDb,
final DbState newState,
final OperationFailureException preemptedException) {
/*
* We acquire the SecondaryAssociationLatch exclusively because
* associations are changed by removeReferringAssociations, and
* operations using the associations should not run concurrently with
* close.
*/
try {
final EnvironmentImpl envImpl = getEnv();
try {
envImpl.getSecondaryAssociationLock().
writeLock().lockInterruptibly();
} catch (InterruptedException e) {
throw new ThreadInterruptedException(envImpl, e);
}
try {
closeInternalWork(
doSyncDw, deleteTempDb, newState, preemptedException);
} finally {
envImpl.getSecondaryAssociationLock().writeLock().unlock();
}
} finally {
minimalClose(newState, preemptedException);
}
}
/**
* Nulls-out indirect references to the environment, to allow GC. It also
* sets the state to the given non-open state, if the state is currently
* open. We must set the state to non-open before setting references to
* null.
*
* The app may hold the Database references longer than expected. In
* particular during an Environment re-open we need to give GC a fighting
* chance while handles from two environments are temporarily referenced.
*
* Note that this is needed even when the db or env is invalid.
*/
synchronized void minimalClose(
final DbState newState,
final OperationFailureException preemptedException) {
assert newState != DbState.OPEN;
if (state == DbState.OPEN) {
state = newState;
preemptedCause = preemptedException;
}
databaseImpl = null;
handleLocker = null;
}
private void closeInternalWork(
final boolean doSyncDw,
final boolean deleteTempDb,
final DbState newState,
final OperationFailureException preemptedException) {
assert newState != DbState.OPEN;
final StringBuilder handleRefErrors = new StringBuilder();
RuntimeException triggerException = null;
final DatabaseImpl dbImpl;
synchronized (this) {
/*
* Do nothing if handle was previously closed.
*
* When the state is set to CLOSED, INVALID and PREEMPTED, the
* database has been closed. So for these states, do not close the
* database again.
*
* The CORRUPTED state is set when SecondaryIntegrityException is
* thrown, but the database is not closed at that time.
* This state is currently only set for a secondary database.
* For this state, we want to let the user close the database.
*
* Besides, if the database was not opened, just return.
*/
if (state == DbState.CLOSED || state == DbState.INVALID ||
state == DbState.PREEMPTED || state == null) {
return;
}
/*
* databaseImpl and handleLocker are set to null only while
* synchronized, at which time the state is also changed to
* non-open. So they should not be null here.
*/
dbImpl = databaseImpl;
assert dbImpl != null;
assert handleLocker != null;
/*
* Check env only after checking for closed db, to mimic close()
* behavior for Cursors, etc, and avoid unnecessary exception
* handling. [#21264]
*/
final EnvironmentImpl envImpl = checkEnv();
/*
* The state should be changed ASAP during close, so that
* addCursor and removeCursor will see the updated state ASAP.
*/
state = newState;
preemptedCause = preemptedException;
/*
* Throw an IllegalStateException if there are open cursors or
* associated secondaries.
*/
if (newState == DbState.CLOSED) {
if (openCursors.get() != 0) {
handleRefErrors.append(" ").
append(openCursors.get()).
append(" open cursors.");
}
if (simpleAssocSecondaries != null &&
simpleAssocSecondaries.size() > 0) {
handleRefErrors.append(" ").
append(simpleAssocSecondaries.size()).
append(" associated SecondaryDatabases.");
}
if (foreignKeySecondaries != null &&
foreignKeySecondaries.size() > 0) {
handleRefErrors.append(" ").
append(foreignKeySecondaries.size()).
append(
" associated foreign key SecondaryDatabases.");
}
}
trace(Level.FINEST, "Database.close: ", null, null);
removeReferringAssociations();
dbImpl.removeReferringHandle(this);
envImpl.getDbTree().releaseDb(dbImpl);
/*
* If the handle was preempted, we mark the locker as
* only-abortable with the DatabasePreemptedException. If
* the handle locker is a user txn, this causes the
* DatabasePreemptedException to be thrown if the user
* attempts to commit, or continue to use, the txn, rather
* than throwing a LockConflictException. [#17015]
*/
if (newState == DbState.PREEMPTED) {
handleLocker.setOnlyAbortable(preemptedException);
}
/*
* Tell our protecting txn that we're closing. If this type
* of transaction doesn't live beyond the life of the
* handle, it will release the db handle lock.
*/
if (newState == DbState.CLOSED) {
if (isWritable() && (dbImpl.noteWriteHandleClose() == 0)) {
try {
TriggerManager.runCloseTriggers(handleLocker, dbImpl);
} catch (RuntimeException e) {
triggerException = e;
}
}
handleLocker.operationEnd(true);
} else {
handleLocker.operationEnd(false);
}
}
/*
* Notify the database when a handle is closed. This should not be
* done while synchronized since it may perform database removal or
* sync. Statements above are synchronized but this section must not
* be.
*
* Note that handleClosed may throw an exception, so any following code
* may not be executed.
*/
dbImpl.handleClosed(doSyncDw, deleteTempDb);
/* Throw exceptions for previously encountered problems. */
if (handleRefErrors.length() > 0) {
throw new IllegalStateException(
"Database closed while still referenced by other handles." +
handleRefErrors.toString());
}
if (triggerException != null) {
throw triggerException;
}
}
/**
* Flushes any cached information for this database to disk; only
* applicable for deferred-write databases.
* Note that deferred-write databases are automatically flushed to disk
* when the {@link #close} method is called.
*
* @see DatabaseConfig#setDeferredWrite DatabaseConfig.setDeferredWrite
*
* @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 UnsupportedOperationException if this is not a deferred-write
* database, or this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*/
public void sync() {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
trace(Level.FINEST, "Database.sync", null, null, null, null);
dbImpl.sync(true);
}
/**
* Opens a sequence in the database.
*
* @param txn For a transactional database, an explicit transaction may
* be specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key The key {@link DatabaseEntry} of the sequence.
*
* @param config The sequence attributes. If null, default attributes are
* used.
*
* @return a new Sequence handle.
*
* @throws SequenceExistsException if the sequence record already exists
* and the {@code SequenceConfig ExclusiveCreate} parameter is true.
*
* @throws SequenceNotFoundException if the sequence record does not exist
* and the {@code SequenceConfig AllowCreate} parameter is false.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs. If the sequence does not exist and the {@link
* SequenceConfig#setAllowCreate AllowCreate} parameter is true, then one
* of the Write
* Operation Failures may also occur.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only, or
* this database is configured for duplicates.
*
* @throws IllegalStateException if the Sequence record is deleted by
* another thread during this method invocation, or the database has been
* closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified,
* for example, an invalid {@code SequenceConfig} parameter.
*/
public Sequence openSequence(final Transaction txn,
final DatabaseEntry key,
final SequenceConfig config) {
try {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
checkOpen();
trace(Level.FINEST, "Database.openSequence", txn, key, null, null);
return new Sequence(this, txn, key, config);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Removes the sequence from the database. This method should not be
* called if there are open handles on this sequence.
*
* @param txn For a transactional database, an explicit transaction may be
* specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key The key {@link com.sleepycat.je.DatabaseEntry
* DatabaseEntry} of the sequence.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*/
public void removeSequence(final Transaction txn,
final DatabaseEntry key) {
try {
delete(txn, key);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Returns a cursor into the database.
*
* @param txn the transaction used to protect all operations performed with
* the cursor, or null if the operations should not be transaction
* protected. If the database is non-transactional, null must be
* specified. For a transactional database, the transaction is optional
* for read-only access and required for read-write access.
*
* @param cursorConfig The cursor attributes. If null, default attributes
* are used.
*
* @return A database 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 database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified,
* for example, an invalid {@code CursorConfig} parameter.
*/
public Cursor openCursor(final Transaction txn,
CursorConfig cursorConfig) {
try {
checkEnv();
checkOpen();
if (cursorConfig == null) {
cursorConfig = CursorConfig.DEFAULT;
}
if (cursorConfig.getReadUncommitted() &&
cursorConfig.getReadCommitted()) {
throw new IllegalArgumentException(
"Only one may be specified: " +
"ReadCommitted or ReadUncommitted");
}
trace(Level.FINEST, "Database.openCursor", txn, cursorConfig);
return newDbcInstance(txn, cursorConfig);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Create a DiskOrderedCursor to iterate over the records in 'this'
* Database. Because the retrieval is based on Log Sequence Number (LSN)
* order rather than key order, records are returned in unsorted order in
* exchange for generally faster retrieval. LSN order approximates disk
* sector order.
*
* See {@link DiskOrderedCursor} for more details and a description of the
* consistency guarantees provided by the scan.
*
* WARNING: After calling this method, deletion of log files by
* the JE log cleaner will be disabled until {@link
* DiskOrderedCursor#close()} is called. To prevent unbounded growth of
* disk usage, be sure to call {@link DiskOrderedCursor#close()} to
* re-enable log file deletion.
*/
public DiskOrderedCursor openCursor(DiskOrderedCursorConfig cursorConfig) {
try {
checkEnv();
checkOpen();
if (cursorConfig == null) {
cursorConfig = DiskOrderedCursorConfig.DEFAULT;
}
trace(Level.FINEST, "Database.openForwardCursor",
null, cursorConfig);
Database[] dbs = new Database[1];
dbs[0] = this;
return new DiskOrderedCursor(dbs, cursorConfig);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Is overridden by SecondaryDatabase.
*/
Cursor newDbcInstance(final Transaction txn,
final CursorConfig cursorConfig) {
return new Cursor(this, txn, cursorConfig);
}
/**
* @hidden
* For internal use only.
*
* @deprecated in favor of {@link #populateSecondaries(Transaction,
* DatabaseEntry, DatabaseEntry, long, CacheMode)}.
*/
public void populateSecondaries(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data) {
populateSecondaries(txn, key, data, 0 /*expirationTime*/, null);
}
/**
* @hidden
* For internal use only.
*
* Given the {@code key}, {@code data} and {@code expirationTime} for a
* locked primary DB record, update the corresponding secondary database
* (index) records, for secondaries enabled for incremental population.
*
* The secondaries associated the primary record are determined by calling
* {@link SecondaryAssociation#getSecondaries}. For each of these
* secondaries, {@link SecondaryDatabase#isIncrementalPopulationEnabled} is
* called to determine whether incremental population is enabled. If so,
* appropriate secondary records are inserted and deleted so that the
* index accurately reflects the current state of the primary record.
*
* Note that for a given primary record, this method will not modify the
* secondary database if the secondary has already been updated for the
* primary record, due to concurrent primary write operations. Due to this
* behavior, certain integrity checks are not performed as documented in
* {@link SecondaryDatabase#startIncrementalPopulation}.
*
* The primary record must be locked (read or write locked) when this
* method is called. Therefore, the caller should not use dirty-read to
* read the primary record. The simplest way to ensure that the primary
* record is locked is to use a cursor to read primary records, and call
* this method while the cursor is still positioned on the primary record.
*
* It is the caller's responsibility to pass all primary records to this
* method that contain index keys for a secondary DB being incrementally
* populated, before calling {@link
* SecondaryDatabase#endIncrementalPopulation} on that secondary DB.
*
* @param txn is the transaction to be used to write secondary records. If
* null and the secondary DBs are transactional, auto-commit will be used.
* @param key is the key of the locked primary record.
*
* @param data is the data of the locked primary record.
*
* @param expirationTime the expiration time of the locked primary record.
* This can be obtained from {@link OperationResult#getExpirationTime()}
* when reading the primary record.
*
* @param cacheMode the CacheMode to use, or null for the Database default.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
* This includes passing a null input key parameter, an input key parameter
* with a null data array, a null input data parameter, or an input data
* parameter with a null data array.
*/
public void populateSecondaries(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
final long expirationTime,
CacheMode cacheMode) {
try {
checkEnv();
DatabaseUtil.checkForNullDbt(key, "key", true);
DatabaseUtil.checkForNullDbt(data, "true", true);
final DatabaseImpl dbImpl = checkOpen();
trace(Level.FINEST, "populateSecondaries", null, key, data, null);
final Collection secondaries =
secAssoc.getSecondaries(key);
final Locker locker = LockerFactory.getWritableLocker(
envHandle, txn, dbImpl.isInternalDb(), isTransactional(),
dbImpl.isReplicated()); // autoTxnIsReplicated
boolean success = false;
if (cacheMode == null) {
cacheMode = dbImpl.getDefaultCacheMode();
}
try {
for (final SecondaryDatabase secDb : secondaries) {
if (secDb.isIncrementalPopulationEnabled()) {
secDb.updateSecondary(
locker, null /*cursor*/, dbImpl /*priDb*/,
null /*priCursor*/, key /*priKey*/,
null /*oldData*/, data /*newData*/,
cacheMode, expirationTime, false /*expirationUpdated*/,
expirationTime);
}
}
success = true;
} finally {
locker.operationEnd(success);
}
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Removes records with a given key from the database. In the presence of
* duplicate keys, all records associated with the given key will be
* removed. When the database has associated secondary databases, this
* method also deletes the associated index records.
*
* @param txn For a transactional database, an explicit transaction may
* be specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input.
*
* @param options the WriteOptions, or null to use default options.
*
* @return the OperationResult if the record is deleted, else null if the
* given key was not found in the database.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
* This includes passing a null input key parameter, an input key parameter
* with a null data array, or a partial key input parameter.
*
* @since 7.0
*/
public OperationResult delete(final Transaction txn,
final DatabaseEntry key,
final WriteOptions options) {
try {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
trace(Level.FINEST, "Database.delete", txn, key, null, null);
final CacheMode cacheMode =
options != null ? options.getCacheMode() : null;
OperationResult result = null;
final Locker locker = LockerFactory.getWritableLocker(
envHandle, txn,
dbImpl.isInternalDb(),
isTransactional(),
dbImpl.isReplicated()); // autoTxnIsReplicated
try {
result = deleteInternal(locker, key, cacheMode);
} finally {
if (locker != null) {
locker.operationEnd(result != null);
}
}
return result;
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Internal version of delete() that does no parameter checking. Notify
* triggers, update secondaries and enforce foreign key constraints.
* Deletes all duplicates.
*/
OperationResult deleteInternal(final Locker locker,
final DatabaseEntry key,
final CacheMode cacheMode) {
final DatabaseEntry noData = new DatabaseEntry();
noData.setPartial(0, 0, true);
try (final Cursor cursor = new Cursor(this, locker, null)) {
cursor.setNonSticky(true);
/* Do not count NEXT_DUP ops. */
cursor.excludeFromOpStats();
final LockMode lockMode =
cursor.isSerializableIsolation(LockMode.RMW) ?
LockMode.RMW : LockMode.READ_UNCOMMITTED_ALL;
OperationResult searchResult = cursor.search(
key, noData, lockMode, cacheMode, SearchMode.SET, false);
final DatabaseImpl dbImpl = getDbImpl();
OperationResult anyResult = null;
while (searchResult != null) {
final OperationResult deleteResult = cursor.deleteInternal(
dbImpl.getRepContext(), cacheMode);
if (deleteResult != null) {
dbImpl.getEnv().incDeleteOps(dbImpl);
anyResult = deleteResult;
}
if (!dbImpl.getSortedDuplicates()) {
break;
}
searchResult = cursor.retrieveNext(
key, noData, lockMode, cacheMode, GetMode.NEXT_DUP);
}
if (anyResult == null) {
dbImpl.getEnv().incDeleteFailOps(dbImpl);
}
return anyResult;
}
}
/**
* Removes records with a given key from the database. In the presence of
* duplicate keys, all records associated with the given key will be
* removed. When the database has associated secondary databases, this
* method also deletes the associated index records.
*
* Calling this method is equivalent to calling {@link
* #delete(Transaction, DatabaseEntry, WriteOptions)}.
*
* @param txn For a transactional database, an explicit transaction may
* be specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input.
*
* @return The method will return {@link
* com.sleepycat.je.OperationStatus#NOTFOUND OperationStatus.NOTFOUND} if
* the given key is not found in the database; otherwise {@link
* com.sleepycat.je.OperationStatus#SUCCESS OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
* This includes passing a null input key parameter, an input key parameter
* with a null data array, or a partial key input parameter.
*/
public OperationStatus delete(final Transaction txn,
final DatabaseEntry key) {
final OperationResult result = delete(txn, key, null);
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Retrieves a record according to the specified {@link Get} type.
*
* If the operation succeeds, the record 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.
*
* The following table lists each allowed operation and whether the key
* and data parameters are input or
* output parameters. See the individual {@link Get} operations for
* more information.
*
*
*
* Get operation
* Description
* 'key' parameter
* 'data' parameter
*
*
* {@link Get#SEARCH}
* Searches using an exact match by key.
* input
* output
*
*
* {@link Get#SEARCH_BOTH}
* Searches using an exact match by key and data.
* input
* input
*
*
*
* @param txn For a transactional database, an explicit transaction may be
* specified to transaction-protect the operation, or null may be specified
* to perform the operation without transaction protection. For a
* non-transactional database, null must be specified.
*
* @param key the key input parameter.
*
* @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 Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @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, and a partial
* key/data input parameter.
*
* @since 7.0
*/
public OperationResult get(
final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
final Get getType,
ReadOptions options) {
try {
checkEnv();
checkOpen();
if (options == null) {
options = Cursor.DEFAULT_READ_OPTIONS;
}
LockMode lockMode = options.getLockMode();
trace(
Level.FINEST, "Database.get", String.valueOf(getType), txn,
key, null, lockMode);
checkLockModeWithoutTxn(txn, lockMode);
final CursorConfig cursorConfig;
if (lockMode == LockMode.READ_COMMITTED) {
cursorConfig = READ_COMMITTED_CURSOR_CONFIG;
lockMode = null;
} else {
cursorConfig = DEFAULT_CURSOR_CONFIG;
}
OperationResult result = null;
final Locker locker = LockerFactory.getReadableLocker(
this, txn, cursorConfig.getReadCommitted());
try {
try (final Cursor cursor =
new Cursor(this, locker, cursorConfig)) {
result = cursor.getInternal(
key, data, getType, options, lockMode);
}
} finally {
locker.operationEnd(result != null);
}
return result;
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Retrieves the key/data pair with the given key. If the matching key has
* duplicate values, the first data item in the set of duplicates is
* returned. Retrieval of duplicates requires the use of {@link Cursor}
* operations.
*
* Calling this method is equivalent to calling {@link
* #get(Transaction, DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH}.
*
* @param txn For a transactional database, an explicit transaction may be
* specified to transaction-protect the operation, or null may be specified
* to perform the operation without transaction protection. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input.
*
* @param data the data returned as
* output.
*
* @param lockMode the locking attributes; if null, default attributes are
* used.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus get(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
LockMode lockMode) {
final OperationResult result = get(
txn, key, data, Get.SEARCH, DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Retrieves the key/data pair with the given key and data value, that is,
* both the key and data items must match.
*
* Calling this method is equivalent to calling {@link
* #get(Transaction, DatabaseEntry, DatabaseEntry, Get, ReadOptions)} with
* {@link Get#SEARCH_BOTH}.
*
* @param txn For a transactional database, an explicit transaction may be
* specified to transaction-protect the operation, or null may be specified
* to perform the operation without transaction protection. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input.
*
* @param data the data used as
* input.
*
* @param lockMode the locking attributes; if null, default attributes are
* used.
*
* @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
* OperationStatus.NOTFOUND} if no matching key/data pair is found;
* otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public OperationStatus getSearchBoth(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
LockMode lockMode) {
final OperationResult result = get(
txn, key, data, Get.SEARCH_BOTH,
DbInternal.getReadOptions(lockMode));
return result == null ?
OperationStatus.NOTFOUND : OperationStatus.SUCCESS;
}
/**
* Inserts or updates a record according to the specified {@link Put}
* type.
*
* 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.
*
* When the database has associated secondary databases, this method
* also inserts or deletes associated index records as necessary.
*
* The following table lists each allowed operation. See the individual
* {@link Put} operations for more information.
*
*
*
* Put operation
* Description
* Returns null when?
* Other special rules
*
*
* {@link Put#OVERWRITE}
* Inserts or updates a record depending on whether a matching
* record is already present.
* Never returns null.
* Without duplicates, a matching record is one with the same key;
* with duplicates, it is one with the same key and data.
*
*
* {@link Put#NO_OVERWRITE}
* Inserts a record if a record with a matching key is not already
* present.
* When an existing record matches.
* If the database has duplicate keys, a record is inserted only if
* there are no records with a matching key.
*
*
* {@link Put#NO_DUP_DATA}
* Inserts a record in a database with duplicate keys if a record
* with a matching key and data is not already present.
* When an existing record matches.
* Without duplicates, this operation is not allowed.
*
*
*
* @param txn For a transactional database, an explicit transaction may be
* specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input.
*
* @param data the data used as
* input.
*
* @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 OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if the database is read-only, or
* putType is Put.NO_DUP_DATA and the database is not configured for
* duplicates.
*
* @throws IllegalStateException if the database has been closed.
*
* @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, or when putType is Put.CURRENT.
*
* @since 7.0
*/
public OperationResult put(
final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
final Put putType,
final WriteOptions options) {
try {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
if (putType == Put.CURRENT) {
throw new IllegalArgumentException(
"putType may not be Put.CURRENT");
}
OperationResult result = null;
trace(
Level.FINEST, "Database.put", String.valueOf(putType), txn,
key, data, null);
final Locker locker = LockerFactory.getWritableLocker(
envHandle, txn,
dbImpl.isInternalDb(),
isTransactional(),
dbImpl.isReplicated()); // autoTxnIsReplicated
try {
try (final Cursor cursor =
new Cursor(this, locker, DEFAULT_CURSOR_CONFIG)) {
result = cursor.putInternal(key, data, putType, options);
}
} finally {
locker.operationEnd(result != null);
}
return result;
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Stores the key/data pair into the database.
*
* Calling this method is equivalent to calling {@link
* #put(Transaction, DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#OVERWRITE}.
*
* If the key already appears in the database and duplicates are not
* configured, the data associated with the key will be replaced. If the
* key already appears in the database and sorted duplicates are
* configured, the new data value is inserted at the correct sorted
* location.
*
* @param txn For a transactional database, an explicit transaction may be
* specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input..
*
* @param data the data used as
* input.
*
* @return {@link com.sleepycat.je.OperationStatus#SUCCESS
* OperationStatus.SUCCESS}.
*
* @throws OperationFailureException if one of the Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*/
public OperationStatus put(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data) {
final OperationResult result = put(
txn, key, data, Put.OVERWRITE, null);
EnvironmentFailureException.assertState(result != null);
return OperationStatus.SUCCESS;
}
/**
* Stores the key/data pair into the database if the key does not already
* appear in the database.
*
* Calling this method is equivalent to calling {@link
* #put(Transaction, DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#NO_OVERWRITE}.
*
* This method will return {@link
* com.sleepycat.je.OperationStatus#KEYEXIST OpeationStatus.KEYEXIST} if
* the key already exists in the database, even if the database supports
* duplicates.
*
* @param txn For a transactional database, an explicit transaction may be
* specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input..
*
* @param data the data used as
* input.
*
* @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 Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*/
public OperationStatus putNoOverwrite(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data) {
final OperationResult result = put(
txn, key, data, Put.NO_OVERWRITE, null);
return result == null ?
OperationStatus.KEYEXIST : OperationStatus.SUCCESS;
}
/**
* Stores the key/data pair into the database if it does not already appear
* in the database.
*
* Calling this method is equivalent to calling {@link
* #put(Transaction, DatabaseEntry, DatabaseEntry, Put, WriteOptions)} with
* {@link Put#NO_DUP_DATA}.
*
* This method may only be called if the underlying database has been
* configured to support sorted duplicates.
*
* @param txn For a transactional database, an explicit transaction may be
* specified, or null may be specified to use auto-commit. For a
* non-transactional database, null must be specified.
*
* @param key the key used as
* input..
*
* @param data the data used as
* input.
*
* @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 Write
* Operation Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws UnsupportedOperationException if this database is not configured
* for duplicates, or this database is read-only.
*
* @throws IllegalStateException if the database has been closed.
*/
public OperationStatus putNoDupData(final Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data) {
final OperationResult result = put(
txn, key, data, Put.NO_DUP_DATA, null);
return result == null ?
OperationStatus.KEYEXIST : OperationStatus.SUCCESS;
}
/**
* Creates a specialized join cursor for use in performing equality or
* natural joins on secondary indices.
*
* Each cursor in the cursors
array must have been
* initialized to refer to the key on which the underlying database should
* be joined. Typically, this initialization is done by calling {@link
* Cursor#getSearchKey Cursor.getSearchKey}.
*
* Once the cursors have been passed to this method, they should not be
* accessed or modified until the newly created join cursor has been
* closed, or else inconsistent results may be returned. However, the
* position of the cursors will not be changed by this method or by the
* methods of the join cursor.
*
* @param cursors an array of cursors associated with this primary
* database.
*
* @param config The join attributes. If null, default attributes are
* used.
*
* @return a specialized cursor that returns the results of the equality
* join operation.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified,
* for example, an invalid {@code JoinConfig} parameter.
*
* @see JoinCursor
*/
public JoinCursor join(final Cursor[] cursors, final JoinConfig config) {
try {
EnvironmentImpl env = checkEnv();
checkOpen();
DatabaseUtil.checkForNullParam(cursors, "cursors");
if (cursors.length == 0) {
throw new IllegalArgumentException(
"At least one cursor is required.");
}
/*
* Check that all cursors use the same locker, if any cursor is
* transactional. And if non-transactional, that all databases are
* in the same environment.
*/
Locker locker = cursors[0].getCursorImpl().getLocker();
if (!locker.isTransactional()) {
for (int i = 1; i < cursors.length; i += 1) {
Locker locker2 = cursors[i].getCursorImpl().getLocker();
if (locker2.isTransactional()) {
throw new IllegalArgumentException(
"All cursors must use the same transaction.");
}
EnvironmentImpl env2 =
cursors[i].getDatabaseImpl().getEnv();
if (env != env2) {
throw new IllegalArgumentException(
"All cursors must use the same environment.");
}
}
} else {
for (int i = 1; i < cursors.length; i += 1) {
Locker locker2 = cursors[i].getCursorImpl().getLocker();
if (locker.getTxnLocker() != locker2.getTxnLocker()) {
throw new IllegalArgumentException(
"All cursors must use the same transaction.");
}
}
}
/* Create the join cursor. */
return new JoinCursor(this, cursors, config);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Preloads the cache. This method should only be called when there are no
* operations being performed on the database in other threads. Executing
* preload during concurrent updates may result in some or all of the tree
* being loaded into the JE cache. Executing preload during any other
* types of operations may result in JE exceeding its allocated cache
* size. preload() effectively locks the entire database and therefore will
* lock out the checkpointer, cleaner, and compressor, as well as not allow
* eviction to occur.
*
* @deprecated As of JE 2.0.83, replaced by {@link
* Database#preload(PreloadConfig)}.
*
* @param maxBytes The maximum number of bytes to load. If maxBytes is 0,
* je.evictor.maxMemory is used.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*/
public void preload(final long maxBytes) {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
PreloadConfig config = new PreloadConfig();
config.setMaxBytes(maxBytes);
dbImpl.preload(config);
}
/**
* Preloads the cache. This method should only be called when there are no
* operations being performed on the database in other threads. Executing
* preload during concurrent updates may result in some or all of the tree
* being loaded into the JE cache. Executing preload during any other
* types of operations may result in JE exceeding its allocated cache
* size. preload() effectively locks the entire database and therefore will
* lock out the checkpointer, cleaner, and compressor, as well as not allow
* eviction to occur.
*
* @deprecated As of JE 2.0.101, replaced by {@link
* Database#preload(PreloadConfig)}.
*
* @param maxBytes The maximum number of bytes to load. If maxBytes is 0,
* je.evictor.maxMemory is used.
*
* @param maxMillisecs The maximum time in milliseconds to use when
* preloading. Preloading stops once this limit has been reached. If
* maxMillisecs is 0, preloading can go on indefinitely or until maxBytes
* (if non-0) is reached.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*/
public void preload(final long maxBytes, final long maxMillisecs) {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
PreloadConfig config = new PreloadConfig();
config.setMaxBytes(maxBytes);
config.setMaxMillisecs(maxMillisecs);
dbImpl.preload(config);
}
/**
* Preloads the cache. This method should only be called when there are no
* operations being performed on the database in other threads. Executing
* preload during concurrent updates may result in some or all of the tree
* being loaded into the JE cache. Executing preload during any other
* types of operations may result in JE exceeding its allocated cache
* size. preload() effectively locks the entire database and therefore will
* lock out the checkpointer, cleaner, and compressor, as well as not allow
* eviction to occur. If the database is replicated and the environment is
* in the replica state, then the replica may become temporarily
* disconnected from the master if the replica needs to replay changes
* against the database and is locked out because the time taken by the
* preload operation exceeds {@link
* com.sleepycat.je.rep.ReplicationConfig#FEEDER_TIMEOUT}.
*
* While this method preloads a single database, {@link
* Environment#preload} lets you preload multiple databases.
*
* @param config The PreloadConfig object that specifies the parameters
* of the preload.
*
* @return A PreloadStats object with various statistics about the
* preload() operation.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if {@code PreloadConfig.getMaxBytes} is
* greater than size of the JE cache.
*/
public PreloadStats preload(final PreloadConfig config) {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
PreloadConfig useConfig =
(config == null) ? new PreloadConfig() : config;
return dbImpl.preload(useConfig);
}
/**
* Counts the key/data pairs in the database. This operation is faster than
* obtaining a count from a cursor based scan of the database, and will not
* perturb the current contents of the cache. However, the count is not
* guaranteed to be accurate if there are concurrent updates. Note that
* this method does scan a significant portion of the database and should
* be considered a fairly expensive operation.
*
* This operation uses the an internal infrastructure and algorithm that is
* similar to the one used for the {@link DiskOrderedCursor}. Specifically,
* it will disable deletion of log files by the JE log cleaner during its
* execution and will consume a certain amount of memory (but without
* affecting the memory that is available for the JE cache). To avoid
* excessive memory consumption (and a potential {@code OutOfMemoryError})
* this method places an internal limit on its memory consumption. If this
* limit is reached, the method will still work properly, but its
* performance will degrade. To specify a different memory limit than the
* one used by this method, use the
* {@link Database#count(long memoryLimit)} method.
*
* Currently, the internal memory limit is calculated as 10% of the
* difference between the max JVM memory (the value returned by
* Runtime.getRuntime().maxMemory()) and the configured JE cache size.
*
* @return The count of key/data pairs in the database.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @see Cache
* Statistics: Unexpected Sizes
*/
public long count() {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
return dbImpl.count(0);
}
/**
* Counts the key/data pairs in the database. This operation is faster than
* obtaining a count from a cursor based scan of the database, and will not
* perturb the current contents of the cache. However, the count is not
* guaranteed to be accurate if there are concurrent updates. Note that
* this method does scan a significant portion of the database and should
* be considered a fairly expensive operation.
*
* This operation uses the an internal infrastructure and algorithm that is
* similar to the one used for the {@link DiskOrderedCursor}. Specifically,
* it will disable deletion of log files by the JE log cleaner during its
* execution and will consume a certain amount of memory (but without
* affecting the memory that is available for the JE cache). To avoid
* excessive memory consumption (and a potential {@code OutOfMemoryError})
* this method takes as input an upper bound on the memory it may consume.
* If this limit is reached, the method will still work properly, but its
* performance will degrade.
*
* @param memoryLimit The maximum memory (in bytes) that may be consumed
* by this method.
*
* @return The count of key/data pairs in the database.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*
* @see Cache
* Statistics: Unexpected Sizes
*/
public long count(long memoryLimit) {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
return dbImpl.count(memoryLimit);
}
/**
* Returns Btree node counts.
*
*
The {@code DatabaseStats} return type is abstract to allow for
* different types of database structures. However, currently only the
* Btree structure is used and the return type can be unconditionally
* cast to {@link BtreeStats}.
*
* This method will access some of or all the pages in the database,
* incurring a severe performance penalty as well as possibly flushing
* the underlying cache.
*
* In the presence of multiple threads or processes accessing an active
* database, the information returned by this method may be
* out-of-date.
*
* @param config the stats config; if null, default statistics are
* returned. NOTE: If {@link com.sleepycat.je.StatsConfig#setFast fast
* stats} (StatsConfig.setFast(true)) is configured when calling this
* method, no statistics will be returned. The use of fast stats for this
* method is deprecated.
*
* @return the {@code DatabaseStats} object, which is currently always a
* {@link BtreeStats} object.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*/
public DatabaseStats getStats(StatsConfig config) {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
if (config == null) {
config = StatsConfig.DEFAULT;
}
return dbImpl.stat(config);
}
/**
* Verifies the integrity of the database.
*
* Verification is an expensive operation that should normally only be
* used for troubleshooting and debugging.
*
* @param config Configures the verify operation; if null, the default
* operation is performed.
*
* @return Database statistics.
*
* @throws OperationFailureException if one of the Read Operation
* Failures occurs.
*
* @throws EnvironmentFailureException if an environment corruption is
* detected, or if an unexpected, internal or environment-wide failure
* occurs. If a persistent corruption is detected,
* {@link EnvironmentFailureException#isCorrupted()} will return true.
*
* @throws SecondaryIntegrityException if an environment corruption is
* not detected, but this {@link SecondaryDatabase}s is corrupt.
* {@code SecondaryIntegrityException} will be thrown when attempting to
* access the database and it should be closed.
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if an invalid parameter is specified.
*/
public DatabaseStats verify(VerifyConfig config) {
try {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
if (config == null) {
config = VerifyConfig.DEFAULT;
}
return dbImpl.verify(config);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Returns the database name. Can be called under all conditions, including
* when the database or environment is invalid, and after the database has
* been closed.
*
* @return the database name.
*/
public String getDatabaseName() {
return name;
}
/**
* Returns this Database object's configuration.
*
* This may differ from the configuration used to open this object if
* the database existed previously.
*
* Unlike most Database methods, this method may be called after the
* database is closed.
*
* @return This Database object's configuration.
*
* @throws EnvironmentFailureException if an unexpected, internal or
* environment-wide failure occurs.
*
* @throws IllegalStateException if the database has been closed.
*/
public DatabaseConfig getConfig() {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
try {
return DatabaseConfig.combineConfig(dbImpl, configuration);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/**
* Equivalent to getConfig().getTransactional() but cheaper.
*/
boolean isTransactional() {
final DatabaseImpl dbImpl = checkOpen();
return dbImpl.isTransactional();
}
/**
* Returns the {@link com.sleepycat.je.Environment Environment} handle for
* the database environment underlying the {@link
* com.sleepycat.je.Database Database}.
*
* This method may be called at any time during the life of the
* application.
*
* @return The {@link com.sleepycat.je.Environment Environment} handle
* for the database environment underlying the {@link
* com.sleepycat.je.Database Database}.
*/
public Environment getEnvironment() {
return envHandle;
}
/**
* Returns a list of all {@link com.sleepycat.je.SecondaryDatabase
* SecondaryDatabase} objects associated with a primary database.
*
* If no secondaries are associated with this database, an empty list is
* returned.
*/
/*
* Replacement for above paragraph when SecondaryAssociation is published:
* If no secondaries are associated with this database, or a {@link
* SecondaryAssociation} is {@link DatabaseConfig#setSecondaryAssociation
* configured}, an empty list is returned.
*/
public List getSecondaryDatabases() {
return new ArrayList<>(simpleAssocSecondaries);
}
/**
* Compares two keys using either the default comparator if no BTree
* comparator has been set or the BTree comparator if one has been set.
*
* @return -1 if entry1 compares less than entry2,
* 0 if entry1 compares equal to entry2,
* 1 if entry1 compares greater than entry2
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if either entry is a partial
* DatabaseEntry, or is null.
*/
public int compareKeys(final DatabaseEntry entry1,
final DatabaseEntry entry2) {
return doCompareKeys(entry1, entry2, false/*duplicates*/);
}
/**
* Compares two data elements using either the default comparator if no
* duplicate comparator has been set or the duplicate comparator if one has
* been set.
*
* @return -1 if entry1 compares less than entry2,
* 0 if entry1 compares equal to entry2,
* 1 if entry1 compares greater than entry2
*
* @throws IllegalStateException if the database has been closed.
*
* @throws IllegalArgumentException if either entry is a partial
* DatabaseEntry, or is null.
*/
public int compareDuplicates(final DatabaseEntry entry1,
final DatabaseEntry entry2) {
return doCompareKeys(entry1, entry2, true/*duplicates*/);
}
private int doCompareKeys(final DatabaseEntry entry1,
final DatabaseEntry entry2,
final boolean duplicates) {
try {
checkEnv();
final DatabaseImpl dbImpl = checkOpen();
DatabaseUtil.checkForNullDbt(entry1, "entry1", true);
DatabaseUtil.checkForNullDbt(entry2, "entry2", true);
DatabaseUtil.checkForPartial(entry1, "entry1");
DatabaseUtil.checkForPartial(entry2, "entry2");
return dbImpl.compareEntries(entry1, entry2, duplicates);
} catch (Error E) {
envHandle.invalidate(E);
throw E;
}
}
/*
* Helpers, not part of the public API
*/
/**
* Returns true if the Database was opened read/write.
*
* @return true if the Database was opened read/write.
*/
boolean isWritable() {
return isWritable;
}
/**
* Returns the non-null, underlying getDbImpl.
*
* This method should always be called to access the databaseImpl, to guard
* against NPE when the database has been closed after the initial checks.
*
* However, callers should additionally call checkOpen at API entry points
* to reject the operation as soon as possible. Plus, if the database has
* been closed, this method may return non-null because the databaseImpl
* field is not volatile.
*
* @throws IllegalStateException if the database has been closed since
* checkOpen was last called.
*/
DatabaseImpl getDbImpl() {
final DatabaseImpl dbImpl = databaseImpl;
if (dbImpl != null) {
return dbImpl;
}
checkOpen();
/*
* checkOpen should have thrown an exception, but we'll throw again
* here just in case.
*/
throw new IllegalStateException("Database is closed. State=" + state);
}
/**
* Called during database open to set the handleLocker field.
* @see HandleLocker
*/
HandleLocker initHandleLocker(EnvironmentImpl envImpl,
Locker openDbLocker) {
handleLocker = HandleLocker.createHandleLocker(envImpl, openDbLocker);
return handleLocker;
}
@SuppressWarnings("unused")
void removeCursor(final ForwardCursor ignore)
throws DatabaseException {
/*
* Do not call checkOpen if the handle was preempted or corrupted, to
* allow closing a cursor after an operation failure. [#17015]
*/
if (state != DbState.PREEMPTED && state != DbState.CORRUPTED) {
checkOpen();
}
openCursors.getAndDecrement();
}
@SuppressWarnings("unused")
void addCursor(final ForwardCursor ignore) {
checkOpen();
openCursors.getAndIncrement();
}
DatabaseImpl checkOpen() {
switch (state) {
case OPEN:
return databaseImpl;
case CLOSED:
throw new IllegalStateException("Database was closed.");
case INVALID:
throw new IllegalStateException(
"The Transaction used to open the Database was aborted.");
case PREEMPTED:
throw preemptedCause.wrapSelf(preemptedCause.getMessage());
case CORRUPTED:
throw corruptedCause.wrapSelf(corruptedCause.getMessage());
default:
assert false : state;
return null;
}
}
/**
* @throws EnvironmentFailureException if the underlying environment is
* invalid.
* @throws IllegalStateException if the environment is not open.
*/
EnvironmentImpl checkEnv() {
return envHandle.checkOpen();
}
void checkLockModeWithoutTxn(final Transaction userTxn,
final LockMode lockMode) {
if (userTxn == null && LockMode.RMW.equals(lockMode)) {
throw new IllegalArgumentException(
lockMode + " is meaningless and can not be specified " +
"when a null (autocommit) transaction is used. There " +
"will never be a follow on operation which will promote " +
"the lock to WRITE.");
}
}
/**
* 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 Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
if (logger.isLoggable(level)) {
StringBuilder sb = new StringBuilder();
sb.append(methodName).append(" ");
sb.append(getOrPutType);
if (txn != null) {
sb.append(" txnId=").append(txn.getId());
}
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, 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 Transaction txn,
final DatabaseEntry key,
final DatabaseEntry data,
final LockMode lockMode) {
if (logger.isLoggable(level)) {
StringBuilder sb = new StringBuilder();
sb.append(methodName);
if (txn != null) {
sb.append(" txnId=").append(txn.getId());
}
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, 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 Transaction txn,
final Object config) {
if (logger.isLoggable(level)) {
StringBuilder sb = new StringBuilder();
sb.append(methodName);
sb.append(" name=").append(getDatabaseName());
if (txn != null) {
sb.append(" txnId=").append(txn.getId());
}
if (config != null) {
sb.append(" config=").append(config);
}
LoggerUtils.logMsg(
logger, getEnv(), level, sb.toString());
}
}
boolean hasSecondaryOrForeignKeyAssociations() {
return (!secAssoc.isEmpty() || !foreignKeySecondaries.isEmpty());
}
SecondaryAssociation getSecondaryAssociation() {
return secAssoc;
}
/**
* Creates a SecondaryIntegrityException using the information given.
*
* This method is in the Database class, rather than in SecondaryDatabase,
* to support joins with plain Cursors [#21258].
*/
SecondaryIntegrityException secondaryRefersToMissingPrimaryKey(
final Locker locker,
final Database priDb,
final DatabaseEntry secKey,
final DatabaseEntry priKey,
final long expirationTime) {
return new SecondaryIntegrityException(
this,
locker,
"Secondary refers to a missing key in the primary database",
getDatabaseName(), priDb.getDatabaseName(), secKey, priKey,
DbLsn.NULL_LSN, expirationTime, null);
}
}