Package com.sleepycat.je

Source Code of com.sleepycat.je.Database

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2011 Oracle and/or its affiliates.  All rights reserved.
*
*/

package com.sleepycat.je;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
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.PutMode;
import com.sleepycat.je.dbi.TriggerManager;
import com.sleepycat.je.dbi.CursorImpl.SearchMode;
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.LoggerUtils;

/**
* A database handle.
*
* <p>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.</p>
*
* <p>To open an existing database with default attributes:</p>
*
* <blockquote><pre>
*     Environment env = new Environment(home, null);
*     Database myDatabase = env.openDatabase(null, "mydatabase", null);
* </pre></blockquote>
*
* <p>To create a transactional database that supports duplicates:</p>
*
* <blockquote><pre>
*     DatabaseConfig dbConfig = new DatabaseConfig();
*     dbConfig.setTransactional(true);
*     dbConfig.setAllowCreate(true);
*     dbConfig.setSortedDuplicates(true);
*     Database db = env.openDatabase(txn, "mydatabase", dbConfig);
* </pre></blockquote>
*/
public class Database implements Closeable {

    /*
     * DbState embodies the Database handle state.
     */
    enum DbState {
        OPEN, CLOSED, INVALID, PREEMPTED
    }

    /* The current state of the handle. */
    private volatile DbState state;
    /* The DatabasePreemptedException cause when state == PREEMPTED. */
    private OperationFailureException preemptedCause;

    /* Handles onto the owning environment and the databaseImpl object. */
    Environment envHandle;            // used by subclasses
    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. */
    private HandleLocker handleLocker;

    /*
     * DatabaseTrigger list.  The list is null if empty, and is checked for
     * null to avoiding read locking overhead when no triggers are present.
     * Access to this list is protected by the shared trigger latch in
     * EnvironmentImpl.
     */
    private List<DatabaseTrigger> triggerList;

    protected final Logger logger;

    /**
     * Creates a database but does not open or fully initialize it.  Is
     * protected for use in compat package.
     * @param env
     */
    protected Database(final Environment env) {
        this.envHandle = env;
        handleLocker = null;
        logger = envHandle.getEnvironmentImpl().getLogger();
    }

    /**
     * Creates a database, called by Environment.
     */
    DatabaseImpl initNew(final Environment env,
                         final Locker locker,
                         final String databaseName,
                         final DatabaseConfig dbConfig)
        throws DatabaseException {

        dbConfig.validateForNewDb();

        init(env, dbConfig);

        /* Make the databaseImpl. */
        EnvironmentImpl environmentImpl =
            DbInternal.getEnvironmentImpl(envHandle);
        databaseImpl = environmentImpl.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 DatabaseConfig dbConfig)
        throws DatabaseException {

        /*
         * Make sure the configuration used for the open is compatible with the
         * existing databaseImpl.
         */
        validateConfigAgainstExistingDb(locker, dbConfig, dbImpl);

        init(env, dbConfig);
        this.databaseImpl = dbImpl;
        dbImpl.addReferringHandle(this);
    }

    private void init(final Environment env, final DatabaseConfig config) {
        assert handleLocker != null;
        envHandle = env;
        configuration = config.cloneConfig();
        isWritable = !configuration.getReadOnly();
        state = DbState.OPEN;
    }

    /**
     * Sees if this new handle's configuration is compatible with the
     * pre-existing database.
     */
    private void validateConfigAgainstExistingDb(Locker locker,
                                                 final DatabaseConfig config,
                                                 final DatabaseImpl dbImpl)
        throws DatabaseException {

        /*
         * 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 (envHandle.getEnvironmentImpl().isReplicated()) {
                validatePropertyMatches
                    ("replicated", dbImpl.isReplicated(),
                     DbInternal.getReplicated(config));
            }
        }

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

        if (config.getOverrideTriggers()) {
            dbImplModified |= dbImpl.setTriggers(locker,
                                                 getDatabaseName(),
                                                 config.getTriggers());
        }

        /* 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 = envHandle.getEnvironmentImpl();
        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 and Strategy are changed for all handles, but are not
         * persistent.
         */
        dbImpl.setCacheMode(config.getCacheMode());
        dbImpl.setCacheModeStrategy(config.getCacheModeStrategy());
    }

    /**
     * @throws IllegalArgumentException via Environment.openDatabase and
     * openSecondaryDatabase.
     */
    private void validatePropertyMatches(final String propName,
                                         final boolean existingValue,
                                         final boolean newValue)
        throws IllegalArgumentException {

        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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * When multiple threads are using the {@link com.sleepycat.je.Database
     * Database} handle concurrently, only a single thread may call this
     * method.
     * <p>
     * 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.
     * <p>
     * The database handle may not be accessed again after this method is
     * called, regardless of the method's success or failure.
     *
     * <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>
     *
     * @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()
        throws DatabaseException {

        try {
            closeInternal(true /*doSyncDw*/, true /*deleteTempDb*/,
                          DbState.CLOSED, null /*preemptedException*/);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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()
        throws DatabaseException {

        try {
            closeInternal(false /*doSyncDw*/, true /*deleteTempDb*/,
                          DbState.CLOSED, null /*preemptedException*/);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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 DatabasePreemtpedException 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) {
        OperationFailureException preemptedException =
            databaseImpl.getDbEnvironment().createDatabasePreemptedException
                (msg, dbName, this);
        closeInternal(false /*doSyncDw*/, false /*deleteTempDb*/,
                      DbState.PREEMPTED, preemptedException);
    }

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

    private void
        closeInternal(final boolean doSyncDw,
                      final boolean deleteTempDb,
                      final DbState newState,
                      final OperationFailureException preemptedException)
        throws DatabaseException {

        StringBuilder errors = null;
        DatabaseImpl dbClosed = null;

        synchronized (this) {
            checkEnv();

            /* Do nothing if handle was previously closed. */
            if (state != DbState.OPEN) {
                return;
            }

            /*
             * The state should be changed ASAP during close, so that
             * addCursor and removeCursor will see the updated state ASAP.
             */
            state = newState;
            preemptedCause = preemptedException;

            /*
             * Throw a DatabaseException if there are open cursors while
             * closing a Database.
             */
            if (newState == DbState.CLOSED &&
                openCursors.get() != 0) {
                errors = new StringBuilder();
                errors.append("Database still has " + openCursors.get() +
                              " open cursors while trying to close.");
            }

            trace(Level.FINEST, "Database.close: ", null, null);

            /* Disassociate triggers before closing. */
            removeAllTriggers();

            envHandle.removeReferringHandle(this);

            if (databaseImpl != null) {
                dbClosed = databaseImpl;
                databaseImpl.removeReferringHandle(this);
                envHandle.getEnvironmentImpl().
                    getDbTree().releaseDb(databaseImpl);

                /*
                 * Database.close may be called after an abort.  By setting the
                 * databaseImpl field to null we ensure that close won't call
                 * releaseDb or endOperation. [#13415]
                 */
                databaseImpl = null;

                if (handleLocker != null) {

                    /*
                     * 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() &&
                            (dbClosed.noteWriteHandleClose() == 0)) {
                            TriggerManager.runCloseTriggers(handleLocker,
                                                            dbClosed);
                        }
                        handleLocker.operationEnd(true);
                    } else {
                        handleLocker.operationEnd(false);
                    }

                    /* Null-out indirect reference to environment. */
                    handleLocker = null;
                }
            }
        }

        /*
         * 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.
         */
        if (dbClosed != null) {
            dbClosed.handleClosed(doSyncDw, deleteTempDb);
        }

        if (errors != null) {
            throw new IllegalStateException(errors.toString());
        }
    }

    /**
     * Flushes any cached information for this database to disk; only
     * applicable for deferred-write databases.
     * <p> 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()
        throws DatabaseException, UnsupportedOperationException {

        checkEnv();
        checkOpen("Can't call Database.sync:");
        checkWritable("sync");
        trace(Level.FINEST, "Database.sync", null, null, null, null);

        databaseImpl.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 <a
     * href="../je/OperationFailureException.html#readFailures">Read Operation
     * Failures</a> occurs. If the sequence does not exist and the {@link
     * SequenceConfig#setAllowCreate AllowCreate} parameter is true, then one
     * of the <a
     * href="../je/OperationFailureException.html#writeFailures">Write
     * Operation Failures</a> 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)
        throws SequenceNotFoundException, SequenceExistsException {

        try {
            checkEnv();
            DatabaseUtil.checkForNullDbt(key, "key", true);
            checkOpen("Can't call Database.openSequence:");
            checkWritable("openSequence");
            trace(Level.FINEST, "Database.openSequence", txn, key, null, null);

            return new Sequence(this, txn, key, config);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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 <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 this database is read-only.
     */
    public void removeSequence(final Transaction txn, final DatabaseEntry key)
        throws DatabaseException {

        try {
            delete(txn, key);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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,
                             final CursorConfig cursorConfig)
        throws DatabaseException, IllegalArgumentException {

        try {
            checkEnv();
            checkOpen("Can't open a cursor");
            CursorConfig useConfig =
                (cursorConfig == null) ? CursorConfig.DEFAULT : cursorConfig;

            if (useConfig.getReadUncommitted() &&
                useConfig.getReadCommitted()) {
                throw new IllegalArgumentException
                    ("Only one may be specified: " +
                     "ReadCommitted or ReadUncommitted");
            }

            trace(Level.FINEST, "Database.openCursor", txn, cursorConfig);
            Cursor ret = newDbcInstance(txn, useConfig);

            return ret;
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Create a ForwardCursor to iterate over the records in 'this'.  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.
     *
     * The records returned by the scan correspond to the state of the data at
     * the beginning of the scan plus some, but not all, changes made by the
     * application after the start of the scan.  The user should not rely on
     * the scan returning any changes made after the start of the scan.
     * Specifically, if the record referred to by the ForwardCursor in a
     * DiskOrderedScan is deleted after the ForwardCursor is positioned at that
     * record, getCurrent() will still return the key and value of that record
     * and OperationStatus.SUCCESS.
     *
     * As with a READ_UNCOMMITTED scan, changes made by all transactions,
     * including uncommitted transactions, may be returned by the scan.  Also,
     * a record returned by the scan is not locked, and may be modified or
     * deleted by the application after it is returned, including modification
     * or deletion of the record at the cursor position.
     *
     * If a transactionally correct data set is required, the application must
     * ensure that all transactions that write to the key range are committed
     * before the beginning of the scan, and that during the scan no records in
     * the key range of the scan are inserted, deleted, or modified.
     *
     * <p><em>WARNING:</em> After calling this method, deletion of log files by
     * the JE log cleaner will be disabled until {@link ForwardCursor#close()}
     * is called.  To prevent unbounded growth of disk usage, be sure to call
     * {@link ForwardCursor#close()} to re-enable log file deletion.</p>
     *
     * <p><em>WARNING:</em> During initialization of a Disk Ordered Scan,
     * the internal nodes of the btree are latched for read and this may cause
     * reduced performance for concurrent update operations.  This pause
     * may be limited using
     * {@link DiskOrderedCursorConfig#setMaxSeedMillisecs} or
     * {@link DiskOrderedCursorConfig#setMaxSeedNodes}.
     */
    public DiskOrderedCursor
        openCursor(final DiskOrderedCursorConfig cursorConfig)
        throws DatabaseException, IllegalArgumentException {

        try {
            checkEnv();
            checkOpen("Can't open a cursor");
            DiskOrderedCursorConfig useConfig =
                (cursorConfig == null) ?
                DiskOrderedCursorConfig.DEFAULT :
                cursorConfig;

            trace(Level.FINEST, "Database.openForwardCursor",
                  null, cursorConfig);
            DiskOrderedCursor ret = new DiskOrderedCursor(this, useConfig);

            return ret;
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Is overridden by SecondaryDatabase.
     */
    Cursor newDbcInstance(final Transaction txn,
                          final CursorConfig cursorConfig)
        throws DatabaseException {

        return new Cursor(this, txn, cursorConfig);
    }

    /**
     * Removes key/data pairs from the database.
     *
     * <p>The key/data pair associated with the specified key is discarded
     * from the database.  In the presence of duplicate key values, all
     * records associated with the designated key will be discarded.</p>
     *
     * <p>The key/data pair is also deleted from any associated secondary
     * databases.</p>
     *
     * @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}
     * operated on.
     *
     * @return The method will return {@link
     * com.sleepycat.je.OperationStatus#NOTFOUND OperationStatus.NOTFOUND} if
     * the specified key is not found in the database; otherwise the method
     * will 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 this database is read-only.
     *
     * @throws IllegalStateException if the database has been closed.
     *
     * @throws IllegalArgumentException if an invalid parameter is specified.
     */
    public OperationStatus delete(final Transaction txn,
                                  final DatabaseEntry key)
        throws DeleteConstraintException,
               LockConflictException,
               DatabaseException,
               UnsupportedOperationException,
               IllegalArgumentException {

        try {
            checkEnv();
            DatabaseUtil.checkForNullDbt(key, "key", true);
            checkOpen("Can't call Database.delete:");
            checkWritable("delete");
            trace(Level.FINEST, "Database.delete", txn, key, null, null);

            OperationStatus commitStatus = OperationStatus.NOTFOUND;
            Locker locker = null;
            try {
                locker = LockerFactory.getWritableLocker
                    (envHandle, txn,
                     databaseImpl.isInternalDb(),
                     isTransactional(),
                     databaseImpl.isReplicated()); // autoTxnIsReplicated
                commitStatus = deleteInternal(locker, key, null);
                return commitStatus;
            } finally {
                if (locker != null) {
                    locker.operationEnd(commitStatus);
                }
            }
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /*
     * This is commented out until we agree on whether this should even be in
     * the API.  See [14264].
    private OperationStatus delete(Transaction txn,
                                   DatabaseEntry key,
                                   DatabaseEntry data)
        throws DatabaseException {

        try {
            checkEnv();
            DatabaseUtil.checkForNullDbt(key, "key", true);
            DatabaseUtil.checkForNullDbt(data, "data", true);
            // FUTURE: Ensure data is not partial
            checkOpen("Can't call Database.delete:");
            checkWritable("delete");
            trace(Level.FINEST, "Database.delete", txn, key, data, null);

            OperationStatus commitStatus = OperationStatus.NOTFOUND;
            Locker locker = null;
            try {
                locker = LockerFactory.getWritableLocker
                    (envHandle, txn, isTransactional());
                commitStatus = deleteInternal(locker, key, data);
                return commitStatus;
            } finally {
                if (locker != null) {
                    locker.operationEnd(commitStatus);
                }
            }
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }
    */

    /**
     * Internal version of delete() that does no parameter checking.  Notify
     * triggers.  Deletes all duplicates.
     */
    OperationStatus deleteInternal(final Locker locker,
                                   final DatabaseEntry key,
                                   final DatabaseEntry data)
        throws DatabaseException {

        Cursor cursor = null;
        try {
            cursor = new Cursor(this, locker, null);
            cursor.setNonCloning(true);
            OperationStatus commitStatus = OperationStatus.NOTFOUND;

            final boolean doNotifyTriggers = hasTriggers();
            final boolean hasUserTriggers = databaseImpl.getTriggers() != null;

            /* Position a cursor at the specified data record. */
            DatabaseEntry oldData;
            OperationStatus searchStatus;
            if (data == null) {
                oldData = new DatabaseEntry();
                /* Fetch old data with RMW to avoid lock upgrades. */
                LockMode lockMode = LockMode.RMW;
                /* Do not fetch old data unless needed for triggers. */
                if (!doNotifyTriggers && !hasUserTriggers) {
                    oldData.setPartial(0, 0, true);

                    /*
                     * If we don't need the data, use dirty read to enable the
                     * uncontended lock optimization.
                     */
                    lockMode = LockMode.READ_UNCOMMITTED;
                }
                searchStatus =
                    cursor.search(key, oldData, lockMode, SearchMode.SET);
            } else {
                oldData = data;
                searchStatus =
                    cursor.search(key, oldData, LockMode.RMW, SearchMode.BOTH);
            }

            /* Delete all records with that key. */
            if (searchStatus == OperationStatus.SUCCESS) {
                do {

                    /*
                     * Notify triggers before the actual deletion so that a
                     * primary record never exists while secondary keys refer
                     * to it.  This is relied on by secondary read-uncommitted.
                     */
                    if (doNotifyTriggers) {
                        notifyTriggers(locker, key, oldData, null);
                    }

                    /* The actual deletion. */
                    commitStatus =
                        cursor.deleteNoNotify(databaseImpl.getRepContext());
                    if (commitStatus != OperationStatus.SUCCESS) {
                        return commitStatus;
                    }

                    if (hasUserTriggers) {
                        // TODO: Mark, do we need "pre" triggers for our
                        // indexes as above?
                        TriggerManager.runDeleteTriggers(locker, databaseImpl,
                                                         key, oldData);
                    }

                    if (data != null) {
                        /* delete(key, data) called so only delete one item. */
                        break;
                    }

                    /* Get another duplicate. */
                    if (databaseImpl.getSortedDuplicates()) {
                        searchStatus =
                            cursor.retrieveNext(key, oldData,
                                                LockMode.RMW,
                                                GetMode.NEXT_DUP);
                    } else {
                        searchStatus = OperationStatus.NOTFOUND;
                    }
                } while (searchStatus == OperationStatus.SUCCESS);
                commitStatus = OperationStatus.SUCCESS;
            }
            return commitStatus;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    /**
     * 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.
     *
     * @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.  It must be initialized with a
     * non-null byte array by the caller.
     *
     * @param data the data returned as output.  Its byte array does not need
     * to be initialized by the caller.
     * A <a href="Cursor.html#partialEntry">partial data item</a> may be
     * specified to optimize for key only or partial data retrieval.
     *
     * @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 <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 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)
        throws LockConflictException,
               DatabaseException,
               IllegalArgumentException {

        try {
            checkEnv();
            DatabaseUtil.checkForNullDbt(key, "key", true);
            DatabaseUtil.checkForNullDbt(data, "data", false);
            checkOpen("Can't call Database.get:");
            trace(Level.FINEST, "Database.get", txn, key, null, lockMode);

            CursorConfig cursorConfig = CursorConfig.DEFAULT;
            if (lockMode == LockMode.READ_COMMITTED) {
                cursorConfig = CursorConfig.READ_COMMITTED;
                lockMode = null;
            }
            checkLockModeWithoutTxn(txn, lockMode);

            Locker locker = null;
            Cursor cursor = null;
            OperationStatus commitStatus = null;
            try {
                locker = LockerFactory.getReadableLocker
                    (envHandle, txn, isTransactional(),
                     cursorConfig.getReadCommitted());

                cursor = new Cursor(this, locker, cursorConfig);
                cursor.setNonCloning(true);
                commitStatus =
                    cursor.search(key, data, lockMode, SearchMode.SET);
                return commitStatus;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }

                if (locker != null) {
                    locker.operationEnd(commitStatus);
                }
            }
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Retrieves the key/data pair with the given key and data value, that is,
     * both the key and data items must match.
     *
     * @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. It must be initialized with a non-null
     * byte array by the caller.
     *
     * @param data the data used as input. It must be initialized with a
     * non-null byte array by the caller.
     *
     * @param lockMode the locking attributes; if null, default attributes are
     * used.
     *
     * @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 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)
        throws LockConflictException,
               DatabaseException,
               IllegalArgumentException {

        try {
            checkEnv();
            DatabaseUtil.checkForNullDbt(key, "key", true);
            DatabaseUtil.checkForNullDbt(data, "data", true);
            checkOpen("Can't call Database.getSearchBoth:");
            trace(Level.FINEST, "Database.getSearchBoth", txn, key, data,
                  lockMode);

            CursorConfig cursorConfig = CursorConfig.DEFAULT;
            if (lockMode == LockMode.READ_COMMITTED) {
                cursorConfig = CursorConfig.READ_COMMITTED;
                lockMode = null;
            }
            checkLockModeWithoutTxn(txn, lockMode);

            Locker locker = null;
            Cursor cursor = null;
            OperationStatus commitStatus = null;
            try {
                locker = LockerFactory.getReadableLocker
                    (envHandle, txn, isTransactional(),
                     cursorConfig.getReadCommitted());

                cursor = new Cursor(this, locker, cursorConfig);
                cursor.setNonCloning(true);
                commitStatus =
                    cursor.search(key, data, lockMode, SearchMode.BOTH);
                return commitStatus;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }

                if (locker != null) {
                    locker.operationEnd(commitStatus);
                }
            }
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Stores the key/data pair into the database.
     *
     * <p>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.</p>
     *
     * @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}
     * operated on.
     *
     * @param data the data {@link com.sleepycat.je.DatabaseEntry
     * DatabaseEntry} stored.
     *
     * @return {@link com.sleepycat.je.OperationStatus#SUCCESS
     * OperationStatus.SUCCESS} if the operation succeeds.
     *
     * @throws OperationFailureException if one of the <a
     * href="../je/OperationFailureException.html#writeFailures">Write
     * Operation Failures</a> occurs.
     *
     * @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 database is read-only.
     *
     * @throws IllegalStateException if the database has been closed.
     */
    public OperationStatus put(final Transaction txn,
                               final DatabaseEntry key,
                               final DatabaseEntry data)
        throws DatabaseException {

        checkEnv();
        DatabaseUtil.checkForNullDbt(key, "key", true);
        DatabaseUtil.checkForNullDbt(data, "data", true);
        DatabaseUtil.checkForPartialKey(key);
        checkOpen("Can't call Database.put");
        checkWritable("put");
        trace(Level.FINEST, "Database.put", txn, key, data, null);

        return putInternal(txn, key, data, PutMode.OVERWRITE);
    }

    /**
     * Stores the key/data pair into the database if the key does not already
     * appear in the database.
     *
     * <p>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.</p>
     *
     * @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}
     * operated on.
     *
     * @param data the data {@link com.sleepycat.je.DatabaseEntry
     * DatabaseEntry} stored.
     *
     * @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 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)
        throws DatabaseException {

        checkEnv();
        DatabaseUtil.checkForNullDbt(key, "key", true);
        DatabaseUtil.checkForNullDbt(data, "data", true);
        DatabaseUtil.checkForPartialKey(key);
        checkOpen("Can't call Database.putNoOverWrite");
        checkWritable("putNoOverwrite");
        trace(Level.FINEST, "Database.putNoOverwrite", txn, key, data, null);

        return putInternal(txn, key, data, PutMode.NO_OVERWRITE);
    }

    /**
     * Stores the key/data pair into the database if it does not already appear
     * in the database.
     *
     * <p>This method may only be called if the underlying database has been
     * configured to support sorted duplicates.</p>
     *
     * @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}
     * operated on.
     *
     * @param data the data {@link com.sleepycat.je.DatabaseEntry
     * DatabaseEntry} stored.
     *
     * @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 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)
        throws DatabaseException {

        checkEnv();
        DatabaseUtil.checkForNullDbt(key, "key", true);
        DatabaseUtil.checkForNullDbt(data, "data", true);
        DatabaseUtil.checkForPartialKey(key);
        checkOpen("Can't call Database.putNoDupData");
        if (!databaseImpl.getSortedDuplicates()) {
            throw new UnsupportedOperationException
                ("Database is not configured for duplicate data.");
        }
        checkWritable("putNoDupData");
        trace(Level.FINEST, "Database.putNoDupData", txn, key, data, null);

        return putInternal(txn, key, data, PutMode.NO_DUP_DATA);
    }

    /**
     * Internal version of put() that does no parameter checking.
     */
    OperationStatus putInternal(final Transaction txn,
                                final DatabaseEntry key,
                                final DatabaseEntry data,
                                final PutMode putMode)
        throws DatabaseException {

        try {
            Locker locker = null;
            Cursor cursor = null;
            OperationStatus commitStatus = OperationStatus.KEYEXIST;
            try {
                locker = LockerFactory.getWritableLocker
                    (envHandle, txn,
                     databaseImpl.isInternalDb(),
                     isTransactional(),
                     databaseImpl.isReplicated()); // autoTxnIsReplicated

                cursor = new Cursor(this, locker, null);
                cursor.setNonCloning(true);
                commitStatus = cursor.putInternal(key, data, putMode);
                return commitStatus;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
                if (locker != null) {
                    locker.operationEnd(commitStatus);
                }
            }
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Creates a specialized join cursor for use in performing equality or
     * natural joins on secondary indices.
     *
     * <p>Each cursor in the <code>cursors</code> 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}.</p>
     *
     * <p>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.</p>
     *
     * @param cursors an array of cursors associated with this primary
     * database.  In a replicated environment, an explicit transaction must be
     * specified when opening each cursor, unless read-uncommitted isolation is
     * isolation is specified via the {@link CursorConfig} or {@link LockMode}
     * parameter.
     *
     * @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 <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 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)
        throws DatabaseException, IllegalArgumentException {

        try {
            checkEnv();
            checkOpen("Can't call Database.join");
            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()) {
                EnvironmentImpl env = envHandle.getEnvironmentImpl();
                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()
                        .getDbEnvironment();
                    if (env != env2) {
                        throw new IllegalArgumentException
                            ("All cursors must use the same environment.");
                    }
                }
                locker = null; /* Don't reuse a non-transactional locker. */
            } 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(locker, this, cursors, config);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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)}.</p>
     *
     * @param maxBytes The maximum number of bytes to load.  If maxBytes is 0,
     * je.evictor.maxMemory is used.
     *
     * @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 database has been closed.
     */
    public void preload(final long maxBytes)
        throws DatabaseException {

        checkEnv();
        checkOpen("Can't call Database.preload");

        PreloadConfig config = new PreloadConfig();
        config.setMaxBytes(maxBytes);
        databaseImpl.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)}.</p>
     *
     * @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 <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 database has been closed.
     */
    public void preload(final long maxBytes, final long maxMillisecs)
        throws DatabaseException {

        checkEnv();
        checkOpen("Can't call Database.preload");

        PreloadConfig config = new PreloadConfig();
        config.setMaxBytes(maxBytes);
        config.setMaxMillisecs(maxMillisecs);
        databaseImpl.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.
     * <p>
     * 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 <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 database has been closed.
     *
     * @throws IllegalArgumentException if {@code PreloadConfig.getMaxBytes} is
     * greater than size of the JE cache.
     */
    public PreloadStats preload(final PreloadConfig config)
        throws DatabaseException {

        checkEnv();
        checkOpen("Can't call Database.preload");

        PreloadConfig useConfig =
            (config == null) ? new PreloadConfig() : config;
        return databaseImpl.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.
     *
     * <p>A count of the key/data pairs in the database is returned without
     * adding to the cache.  The count may not be accurate in the face of
     * concurrent update operations in the database.</p>
     *
     * @return The count of key/data pairs in the database.
     *
     * @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 database has been closed.
     */
    public long count()
        throws DatabaseException {

        checkEnv();
        checkOpen("Can't call Database.count");

        return databaseImpl.count();
    }

    /**
     * Returns database statistics.
     *
     * <p>If this method has not been configured to avoid expensive operations
     * (using the {@link com.sleepycat.je.StatsConfig#setFast
     * StatsConfig.setFast} method), it will access some of or all the pages in
     * the database, incurring a severe performance penalty as well as possibly
     * flushing the underlying cache.</p>
     *
     * <p>In the presence of multiple threads or processes accessing an active
     * database, the information returned by this method may be
     * out-of-date.</p>
     *
     * @param config The statistics returned; if null, default statistics are
     * returned.
     *
     * @return Database statistics.
     *
     * @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 database has been closed.
     */
    public DatabaseStats getStats(final StatsConfig config)
        throws DatabaseException {

        checkEnv();
        checkOpen("Can't call Database.stat");
        StatsConfig useConfig =
            (config == null) ? StatsConfig.DEFAULT : config;

        if (databaseImpl != null) {
            return databaseImpl.stat(useConfig);
        }
        return null;
    }

    /**
     * Verifies the integrity of the database.
     *
     * <p>Verification is an expensive operation that should normally only be
     * used for troubleshooting and debugging.</p>
     *
     * @param config Configures the verify operation; if null, the default
     * operation is performed.
     *
     * @return Database statistics.
     *
     * @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 database has been closed.
     *
     * @throws IllegalArgumentException if an invalid parameter is specified.
     */
    public DatabaseStats verify(final VerifyConfig config)
        throws DatabaseException {

        try {
            checkEnv();
            checkOpen("Can't call Database.verify");
            VerifyConfig useConfig =
                (config == null) ? VerifyConfig.DEFAULT : config;

            DatabaseStats stats = databaseImpl.getEmptyStats();
            databaseImpl.verify(useConfig, stats);
            return stats;
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Returns the database name.
     *
     * <p>This method may be called at any time during the life of the
     * application.</p>
     *
     * @return The database name.
     */
    public String getDatabaseName()
        throws DatabaseException {

        try {
            checkEnv();
            if (databaseImpl != null) {
                return databaseImpl.getName();
            }
            return null;
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /*
     * Non-transactional database name, safe to access when creating error
     * messages.
     */
    String getDebugName() {
        if (databaseImpl != null) {
            return databaseImpl.getDebugName();
        }
        return null;
    }

    /**
     * Returns this Database object's configuration.
     *
     * <p>This may differ from the configuration used to open this object if
     * the database existed previously.</p>
     *
     * @return This Database object's configuration.
     *
     * @throws EnvironmentFailureException if an unexpected, internal or
     * environment-wide failure occurs.
     */
    public DatabaseConfig getConfig() {

        try {
            return DatabaseConfig.combineConfig(databaseImpl, configuration);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * Equivalent to getConfig().getTransactional() but cheaper.
     */
    boolean isTransactional() {
        return databaseImpl.isTransactional();
    }

    /**
     * Returns the {@link com.sleepycat.je.Environment Environment} handle for
     * the database environment underlying the {@link
     * com.sleepycat.je.Database Database}.
     *
     * <p>This method may be called at any time during the life of the
     * application.</p>
     *
     * @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.
     *
     * <p>If no secondaries are associated or this is itself a secondary
     * database, an empty list is returned.</p>
     *
     * @return A list of all {@link com.sleepycat.je.SecondaryDatabase
     * SecondaryDatabase} objects associated with a primary database.
     */
    public List<SecondaryDatabase> getSecondaryDatabases()
        throws DatabaseException {

        try {
            List<SecondaryDatabase> list = new ArrayList<SecondaryDatabase>();
            if (hasTriggers()) {
                acquireTriggerListReadLock();
                try {
                    for (int i = 0; i < triggerList.size(); i += 1) {
                        DatabaseTrigger t = triggerList.get(i);
                        if (t instanceof SecondaryTrigger) {
                            list.add(((SecondaryTrigger) t).getDb());
                        }
                    }
                } finally {
                    releaseTriggerListReadLock();
                }
            }
            return list;
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
            throw E;
        }
    }

    /**
     * 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 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 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();
            DatabaseUtil.checkForNullDbt(entry1, "entry1", true);
            DatabaseUtil.checkForNullDbt(entry2, "entry2", true);
            DatabaseUtil.checkForPartialKey(entry1);
            DatabaseUtil.checkForPartialKey(entry2);
            return databaseImpl.compareEntries(entry1, entry2, duplicates);
        } catch (Error E) {
            DbInternal.getEnvironmentImpl(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 databaseImpl object instance.
     */
    DatabaseImpl getDatabaseImpl() {
        return databaseImpl;
    }

    /**
     * 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, to allow closing
         * a cursor after an operation failure.  [#17015]
         */
        if (state != DbState.PREEMPTED) {
            checkOpen("Database was closed while still in use:");
        }
        openCursors.getAndDecrement();
    }

    @SuppressWarnings("unused")
    void addCursor(final ForwardCursor ignore)
        throws DatabaseException {

        checkOpen("Database was closed while still in use:");
        openCursors.getAndIncrement();
    }

    void checkOpen(final String msg) {
        switch (state) {
        case OPEN:
            break;
        case CLOSED:
            throw new IllegalStateException(msg + " Database was closed.");
        case INVALID:
            throw new IllegalStateException
                (msg +
                 " The Transaction used to open the Database was aborted.");
        case PREEMPTED:
            throw preemptedCause.wrapSelf(msg);
        default:
            assert false : state;
        }
    }

    /**
     * @throws EnvironmentFailureException if the underlying environment is
     * invalid
     */
    void checkEnv()
        throws EnvironmentFailureException {

        EnvironmentImpl env = envHandle.getEnvironmentImpl();
        if (env != null) {
            env.checkIfInvalid();
        }
    }

    /**
     * Checks that write operations aren't used on a readonly Database.
     */
    private void checkWritable(final String operation) {
        if (!isWritable) {
            throw new UnsupportedOperationException
                ("Database is Read Only: " + operation);
        }
    }

    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 Transaction txn,
               final DatabaseEntry key,
               final DatabaseEntry data,
               final LockMode lockMode)
        throws DatabaseException {

        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, envHandle.getEnvironmentImpl(), 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)
        throws DatabaseException {

        if (logger.isLoggable(level)) {
            StringBuilder sb = new StringBuilder();
            sb.append(methodName);
            sb.append(" name=" + getDebugName());
            if (txn != null) {
                sb.append(" txnId=").append(txn.getId());
            }
            if (config != null) {
                sb.append(" config=").append(config);
            }
            LoggerUtils.logMsg
                (logger, envHandle.getEnvironmentImpl(), level, sb.toString());
        }
    }

    /*
     * Manage triggers.
     */

    /**
     * Returns whether any triggers are currently associated with this primary.
     * Note that an update of the trigger list may be in progress and this
     * method does not wait for that update to be completed.
     */
    boolean hasTriggers() {

        return triggerList != null;
    }

    /**
     * Gets a read-lock on the list of triggers.  releaseTriggerListReadLock()
     * must be called to release the lock.  Called by all primary put and
     * delete operations.
     */
    private void acquireTriggerListReadLock()
        throws DatabaseException {

        EnvironmentImpl env = envHandle.getEnvironmentImpl();
        env.getTriggerLatch().acquireShared();
        if (triggerList == null) {
            triggerList = new ArrayList<DatabaseTrigger>();
        }
    }

    /**
     * Releases a lock acquired by calling acquireTriggerListReadLock().
     */
    private void releaseTriggerListReadLock()
        throws DatabaseException {

        EnvironmentImpl env = envHandle.getEnvironmentImpl();
        env.getTriggerLatch().release();
    }

    /**
     * Gets a write lock on the list of triggers.  An empty list is created if
     * necessary, so null is never returned.  releaseTriggerListWriteLock()
     * must always be called to release the lock.
     */
    private void acquireTriggerListWriteLock()
        throws DatabaseException {

        EnvironmentImpl env = envHandle.getEnvironmentImpl();
        env.getTriggerLatch().acquireExclusive();
        if (triggerList == null) {
            triggerList = new ArrayList<DatabaseTrigger>();
        }
    }

    /**
     * Releases a lock acquired by calling acquireTriggerListWriteLock().  If
     * the list is now empty then it is set to null, that is, hasTriggers()
     * will subsequently return false.
     */
    private void releaseTriggerListWriteLock()
        throws DatabaseException {

        if (triggerList.size() == 0) {
            triggerList = null;
        }
        EnvironmentImpl env = envHandle.getEnvironmentImpl();
        env.getTriggerLatch().release();
    }

    /**
     * Adds a given trigger to the list of triggers.  Called while opening
     * a SecondaryDatabase.
     *
     * @param insertAtFront true to insert at the front, or false to append.
     */
    void addTrigger(final DatabaseTrigger trigger, final boolean insertAtFront)
        throws DatabaseException {

        acquireTriggerListWriteLock();
        try {
            if (insertAtFront) {
                triggerList.add(0, trigger);
            } else {
                triggerList.add(trigger);
            }
            trigger.triggerAdded(this);
        } finally {
            releaseTriggerListWriteLock();
        }
    }

    /**
     * Removes a given trigger from the list of triggers.  Called by
     * SecondaryDatabase.close().
     */
    void removeTrigger(final DatabaseTrigger trigger)
        throws DatabaseException {

        acquireTriggerListWriteLock();
        try {
            triggerList.remove(trigger);
            trigger.triggerRemoved(this);
        } finally {
            releaseTriggerListWriteLock();
        }
    }

    /**
     * Clears the list of triggers.  Called by close(), this allows closing the
     * primary before its secondaries, although we document that secondaries
     * should be closed first.
     */
    private void removeAllTriggers()
        throws DatabaseException {

        acquireTriggerListWriteLock();
        try {
            for (int i = 0; i < triggerList.size(); i += 1) {
                DatabaseTrigger trigger = triggerList.get(i);
                trigger.triggerRemoved(this);
            }
            triggerList.clear();
        } finally {
            releaseTriggerListWriteLock();
        }
    }

    /**
     * Notifies associated triggers when a put() or delete() is performed on
     * the primary.  This method is normally called only if hasTriggers() has
     * returned true earlier.  This avoids acquiring a shared latch for
     * primaries with no triggers.  If a trigger is added during the update
     * process, there is no requirement to immediately start updating it.
     *
     * @param locker the internal locker.
     *
     * @param priKey the primary key.
     *
     * @param oldData the primary data before the change, or null if the record
     * did not previously exist.
     *
     * @param newData the primary data after the change, or null if the record
     * has been deleted.
     */
    void notifyTriggers(final Locker locker,
                        final DatabaseEntry priKey,
                        final DatabaseEntry oldData,
                        final DatabaseEntry newData)
        throws DatabaseException {

        acquireTriggerListReadLock();
        try {
            for (int i = 0; i < triggerList.size(); i += 1) {
                DatabaseTrigger trigger = triggerList.get(i);

                /* Notify trigger. */
                trigger.databaseUpdated
                    (this, locker, priKey, oldData, newData);
            }
        } finally {
            releaseTriggerListReadLock();
        }
    }
}
TOP

Related Classes of com.sleepycat.je.Database

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