1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je;
9 
10 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_DELETE;
11 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_GET;
12 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_GETSEARCHBOTH;
13 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_PUT;
14 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_PUTNODUPDATA;
15 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_PUTNOOVERWRITE;
16 import static com.sleepycat.je.dbi.DbiStatDefinition.THROUGHPUT_DB_REMOVESEQUENCE;
17 
18 import java.io.Closeable;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.concurrent.CopyOnWriteArraySet;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27 
28 import com.sleepycat.je.dbi.CursorImpl.SearchMode;
29 import com.sleepycat.je.dbi.DatabaseImpl;
30 import com.sleepycat.je.dbi.EnvironmentImpl;
31 import com.sleepycat.je.dbi.GetMode;
32 import com.sleepycat.je.dbi.PutMode;
33 import com.sleepycat.je.dbi.TriggerManager;
34 import com.sleepycat.je.txn.HandleLocker;
35 import com.sleepycat.je.txn.Locker;
36 import com.sleepycat.je.txn.LockerFactory;
37 import com.sleepycat.je.utilint.AtomicLongStat;
38 import com.sleepycat.je.utilint.DatabaseUtil;
39 import com.sleepycat.je.utilint.LoggerUtils;
40 
41 /**
42  * A database handle.
43  *
44  * <p>Database attributes are specified in the {@link
45  * com.sleepycat.je.DatabaseConfig DatabaseConfig} class. Database handles are
46  * free-threaded and may be used concurrently by multiple threads.</p>
47  *
48  * <p>To open an existing database with default attributes:</p>
49  *
50  * <blockquote><pre>
51  *     Environment env = new Environment(home, null);
52  *     Database myDatabase = env.openDatabase(null, "mydatabase", null);
53  * </pre></blockquote>
54  *
55  * <p>To create a transactional database that supports duplicates:</p>
56  *
57  * <blockquote><pre>
58  *     DatabaseConfig dbConfig = new DatabaseConfig();
59  *     dbConfig.setTransactional(true);
60  *     dbConfig.setAllowCreate(true);
61  *     dbConfig.setSortedDuplicates(true);
62  *     Database db = env.openDatabase(txn, "mydatabase", dbConfig);
63  * </pre></blockquote>
64  */
65 public class Database implements Closeable {
66 
67     /*
68      * DbState embodies the Database handle state.
69      */
70     enum DbState {
71         OPEN, CLOSED, INVALID, PREEMPTED
72     }
73 
74     /* The current state of the handle. */
75     private volatile DbState state;
76     /* The DatabasePreemptedException cause when state == PREEMPTED. */
77     private OperationFailureException preemptedCause;
78 
79     /* Handles onto the owning environment and the databaseImpl object. */
80     Environment envHandle;            // used by subclasses
81     private DatabaseImpl databaseImpl;
82 
83     /*
84      * Used to store per-Database handle properties: allow create,
85      * exclusive create, read only and use existing config. Other Database-wide
86      * properties are stored in DatabaseImpl.
87      */
88     DatabaseConfig configuration;
89 
90     /* True if this handle permits write operations; */
91     private boolean isWritable;
92 
93     /* Record how many open cursors on this database. */
94     private final AtomicInteger openCursors = new AtomicInteger();
95 
96     /* Locker that owns the NameLN lock held while the Database is open. */
97     private HandleLocker handleLocker;
98 
99     /*
100      * If a user-supplied SecondaryAssociation is configured, this field
101      * contains it.  Otherwise, it contains an internal SecondaryAssociation
102      * that uses the simpleAssocSecondaries to store associations between a
103      * single primary and its secondaries.
104      */
105     SecondaryAssociation secAssoc;
106     Collection<SecondaryDatabase> simpleAssocSecondaries;
107 
108     /*
109      * Secondaries whose keys have values contrained to the primary keys in
110      * this database.
111      */
112     Collection<SecondaryDatabase> foreignKeySecondaries;
113 
114     private AtomicLongStat deleteStat;
115     private AtomicLongStat getStat;
116     private AtomicLongStat getSearchBothStat;
117     private AtomicLongStat putStat;
118     private AtomicLongStat putNoDupDataStat;
119     private AtomicLongStat putNoOverwriteStat;
120     private AtomicLongStat removeSequenceStat;
121 
122     final Logger logger;
123 
124     /**
125      * Creates a database but does not open or fully initialize it.  Is
126      * protected for use in compat package.
127      * @param env
128      */
Database(final Environment env)129     Database(final Environment env) {
130         this.envHandle = env;
131         handleLocker = null;
132         logger = envHandle.getEnvironmentImpl().getLogger();
133     }
134 
135     /**
136      * Creates a database, called by Environment.
137      */
initNew(final Environment env, final Locker locker, final String databaseName, final DatabaseConfig dbConfig)138     DatabaseImpl initNew(final Environment env,
139                          final Locker locker,
140                          final String databaseName,
141                          final DatabaseConfig dbConfig)
142         throws DatabaseException {
143 
144         dbConfig.validateForNewDb();
145 
146         init(env, dbConfig);
147 
148         /* Make the databaseImpl. */
149         EnvironmentImpl environmentImpl =
150             DbInternal.getEnvironmentImpl(envHandle);
151         databaseImpl = environmentImpl.getDbTree().createDb(
152             locker, databaseName, dbConfig, handleLocker);
153         databaseImpl.addReferringHandle(this);
154         return databaseImpl;
155     }
156 
157     /**
158      * Opens a database, called by Environment.
159      */
initExisting(final Environment env, final Locker locker, final DatabaseImpl dbImpl, final String databaseName, final DatabaseConfig dbConfig)160     void initExisting(final Environment env,
161                       final Locker locker,
162                       final DatabaseImpl dbImpl,
163                       final String databaseName,
164                       final DatabaseConfig dbConfig)
165         throws DatabaseException {
166 
167         /*
168          * Make sure the configuration used for the open is compatible with the
169          * existing databaseImpl.
170          */
171         validateConfigAgainstExistingDb(locker, databaseName, dbConfig,
172                                         dbImpl);
173 
174         init(env, dbConfig);
175         this.databaseImpl = dbImpl;
176         dbImpl.addReferringHandle(this);
177     }
178 
init(final Environment env, final DatabaseConfig config)179     private void init(final Environment env, final DatabaseConfig config) {
180         assert handleLocker != null;
181         envHandle = env;
182         configuration = config.cloneConfig();
183         isWritable = !configuration.getReadOnly();
184         setupThroughputStats(env.getEnvironmentImpl());
185         secAssoc = makeSecondaryAssociation();
186         state = DbState.OPEN;
187     }
188 
setupThroughputStats(EnvironmentImpl envImpl)189     private void setupThroughputStats(EnvironmentImpl envImpl) {
190         deleteStat = envImpl.getThroughputStat(THROUGHPUT_DB_DELETE);
191         getStat = envImpl.getThroughputStat(THROUGHPUT_DB_GET);
192         getSearchBothStat =
193                 envImpl.getThroughputStat(THROUGHPUT_DB_GETSEARCHBOTH);
194         putStat = envImpl.getThroughputStat(THROUGHPUT_DB_PUT);
195         putNoDupDataStat =
196                 envImpl.getThroughputStat(THROUGHPUT_DB_PUTNODUPDATA);
197         putNoOverwriteStat =
198                 envImpl.getThroughputStat(THROUGHPUT_DB_PUTNOOVERWRITE);
199         removeSequenceStat =
200                 envImpl.getThroughputStat(THROUGHPUT_DB_REMOVESEQUENCE);
201     }
202 
makeSecondaryAssociation()203     SecondaryAssociation makeSecondaryAssociation() {
204         foreignKeySecondaries = new CopyOnWriteArraySet<SecondaryDatabase>();
205 
206         if (configuration.getSecondaryAssociation() != null) {
207             if (configuration.getSortedDuplicates()) {
208                 throw new IllegalArgumentException(
209                     "Duplicates not allowed for a primary database");
210             }
211             simpleAssocSecondaries = Collections.emptySet();
212             return configuration.getSecondaryAssociation();
213         }
214 
215         simpleAssocSecondaries = new CopyOnWriteArraySet<SecondaryDatabase>();
216 
217         return new SecondaryAssociation() {
218 
219             public boolean isEmpty() {
220                 return simpleAssocSecondaries.isEmpty();
221             }
222 
223             public Database getPrimary(@SuppressWarnings("unused")
224                                        DatabaseEntry primaryKey) {
225                 return Database.this;
226             }
227 
228             public Collection<SecondaryDatabase>
229                 getSecondaries(@SuppressWarnings("unused")
230                                 DatabaseEntry primaryKey) {
231                 return simpleAssocSecondaries;
232             }
233         };
234     }
235 
236     /**
237      * Used to remove references to this database from other objects, when this
238      * database is closed.  We don't remove references from cursors or
239      * secondaries here, because it's an error to close a database before its
240      * cursors and to close a primary before its secondaries.
241      */
242     void removeReferringAssociations() {
243         envHandle.removeReferringHandle(this);
244     }
245 
246     /**
247      * Sees if this new handle's configuration is compatible with the
248      * pre-existing database.
249      */
250     private void validateConfigAgainstExistingDb(Locker locker,
251                                                  final String databaseName,
252                                                  final DatabaseConfig config,
253                                                  final DatabaseImpl dbImpl)
254         throws DatabaseException {
255 
256         /*
257          * The sortedDuplicates, temporary, and replicated properties are
258          * persistent and immutable.  But they do not need to be specified if
259          * the useExistingConfig property is set.
260          */
261         if (!config.getUseExistingConfig()) {
262             validatePropertyMatches(
263                 "sortedDuplicates", dbImpl.getSortedDuplicates(),
264                 config.getSortedDuplicates());
265             validatePropertyMatches(
266                 "temporary", dbImpl.isTemporary(),
267                 config.getTemporary());
268             /* Only check replicated if the environment is replicated. */
269             if (envHandle.getEnvironmentImpl().isReplicated()) {
270                 validatePropertyMatches(
271                     "replicated", dbImpl.isReplicated(),
272                     config.getReplicated());
273             }
274         }
275 
276         /*
277          * The transactional and deferredWrite properties are kept constant
278          * while any handles are open, and set when the first handle is opened.
279          * But if an existing handle is open and the useExistingConfig property
280          * is set, then they do not need to be specified.
281          */
282         if (dbImpl.hasOpenHandles()) {
283             if (!config.getUseExistingConfig()) {
284                 validatePropertyMatches(
285                     "transactional", dbImpl.isTransactional(),
286                     config.getTransactional());
287                 validatePropertyMatches(
288                     "deferredWrite", dbImpl.isDurableDeferredWrite(),
289                     config.getDeferredWrite());
290             }
291         } else {
292             dbImpl.setTransactional(config.getTransactional());
293             dbImpl.setDeferredWrite(config.getDeferredWrite());
294         }
295 
296         /*
297          * If this database handle uses the existing config, we shouldn't
298          * search for and write any changed attributes to the log.
299          */
300         if (config.getUseExistingConfig()) {
301             return;
302         }
303 
304         /* Write any changed, persistent attributes to the log. */
305         boolean dbImplModified = false;
306 
307         /* Only re-set the comparators if the override is allowed. */
308         if (config.getOverrideBtreeComparator()) {
309             dbImplModified |= dbImpl.setBtreeComparator(
310                 config.getBtreeComparator(),
311                 config.getBtreeComparatorByClassName());
312         }
313 
314         if (config.getOverrideDuplicateComparator()) {
315             dbImplModified |= dbImpl.setDuplicateComparator(
316                 config.getDuplicateComparator(),
317                 config.getDuplicateComparatorByClassName());
318         }
319 
320         dbImplModified |= dbImpl.setTriggers(locker,
321                                              databaseName,
322                                              config.getTriggers(),
323                                              config.getOverrideTriggers());
324 
325         /* Check if KeyPrefixing property is updated. */
326         boolean newKeyPrefixing = config.getKeyPrefixing();
327         if (newKeyPrefixing != dbImpl.getKeyPrefixing()) {
328             dbImplModified = true;
329             if (newKeyPrefixing) {
330                 dbImpl.setKeyPrefixing();
331             } else {
332                 dbImpl.clearKeyPrefixing();
333             }
334         }
335 
336         /*
337          * Check if NodeMaxEntries properties are updated.
338          */
339         int newNodeMaxEntries = config.getNodeMaxEntries();
340         if (newNodeMaxEntries != 0 &&
341             newNodeMaxEntries != dbImpl.getNodeMaxTreeEntries()) {
342             dbImplModified = true;
343             dbImpl.setNodeMaxTreeEntries(newNodeMaxEntries);
344         }
345 
346         /* Do not write LNs in a read-only environment.  Also see [#15743]. */
347         EnvironmentImpl envImpl = envHandle.getEnvironmentImpl();
348         if (dbImplModified && !envImpl.isReadOnly()) {
349 
350             /* Write a new NameLN to the log. */
351             try {
352                 envImpl.getDbTree().updateNameLN(locker, dbImpl.getName(),
353                                                  null);
354             } catch (LockConflictException e) {
355                 throw new IllegalStateException(
356                     "DatabaseConfig properties may not be updated when the " +
357                     "database is already open; first close other open " +
358                     "handles for this database.", e);
359             }
360 
361             /* Dirty the root. */
362             envImpl.getDbTree().modifyDbRoot(dbImpl);
363         }
364 
365         /*
366          * CacheMode and Strategy are changed for all handles, but are not
367          * persistent.
368          */
369         dbImpl.setCacheMode(config.getCacheMode());
370         dbImpl.setCacheModeStrategy(config.getCacheModeStrategy());
371     }
372 
373     /**
374      * @throws IllegalArgumentException via Environment.openDatabase and
375      * openSecondaryDatabase.
376      */
377     private void validatePropertyMatches(final String propName,
378                                          final boolean existingValue,
379                                          final boolean newValue)
380         throws IllegalArgumentException {
381 
382         if (newValue != existingValue) {
383             throw new IllegalArgumentException(
384                 "You can't open a Database with a " + propName +
385                 " configuration of " + newValue +
386                 " if the underlying database was created with a " +
387                 propName + " setting of " + existingValue + '.');
388         }
389     }
390 
391     /**
392      * Discards the database handle.
393      * <p>
394      * When closing the last open handle for a deferred-write database, any
395      * cached database information is flushed to disk as if {@link #sync} were
396      * called.
397      * <p>
398      * The database handle should not be closed while any other handle that
399      * refers to it is not yet closed; for example, database handles should not
400      * be closed while cursor handles into the database remain open, or
401      * transactions that include operations on the database have not yet been
402      * committed or aborted.  Specifically, this includes {@link
403      * com.sleepycat.je.Cursor Cursor} and {@link com.sleepycat.je.Transaction
404      * Transaction} handles.
405      * <p>
406      * When multiple threads are using the {@link com.sleepycat.je.Database
407      * Database} handle concurrently, only a single thread may call this
408      * method.
409      * <p>
410      * When called on a database that is the primary database for a secondary
411      * index, the primary database should be closed only after all secondary
412      * indices which reference it have been closed.
413      * <p>
414      * The database handle may not be accessed again after this method is
415      * called, regardless of the method's success or failure, with one
416      * exception:  the {@code close} method itself may be called any number of
417      * times.</p>
418      *
419      * <p>WARNING: To guard against memory leaks, the application should
420      * discard all references to the closed handle.  While BDB makes an effort
421      * to discard references from closed objects to the allocated memory for an
422      * environment, this behavior is not guaranteed.  The safe course of action
423      * for an application is to discard all references to closed BDB
424      * objects.</p>
425      *
426      * @see DatabaseConfig#setDeferredWrite DatabaseConfig.setDeferredWrite
427      *
428      * @throws EnvironmentFailureException if an unexpected, internal or
429      * environment-wide failure occurs.
430      *
431      * @throws IllegalStateException if cursors associated with this database
432      * are still open.
433      */
434     public void close()
435         throws DatabaseException {
436 
437         try {
438             closeInternal(true /*doSyncDw*/, true /*deleteTempDb*/,
439                           DbState.CLOSED, null /*preemptedException*/);
440         } catch (Error E) {
441             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
442             throw E;
443         }
444     }
445 
446     /*
447      * This method is private for now because it is incomplete.  To fully
448      * implement it we must clear all dirty nodes for the database that is
449      * closed, since otherwise they will be flushed during the next checkpoint.
450      */
451     @SuppressWarnings("unused")
452     private void closeNoSync()
453         throws DatabaseException {
454 
455         try {
456             closeInternal(false /*doSyncDw*/, true /*deleteTempDb*/,
457                           DbState.CLOSED, null /*preemptedException*/);
458         } catch (Error E) {
459             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
460             throw E;
461         }
462     }
463 
464     /**
465      * Marks the handle as preempted when the handle lock is stolen by the HA
466      * replayer, during replay of a naming operation (remove, truncate or
467      * rename).  This causes DatabasePreemtpedException to be thrown on all
468      * subsequent use of the handle or cursors opened on this handle.  [#17015]
469      */
470     synchronized void setPreempted(final String dbName, final String msg) {
471         OperationFailureException preemptedException = null;
472 
473         /* Bandaid: Avoid an NPE if DB is already closed. [#22827] */
474         if (databaseImpl != null) {
475             preemptedException = databaseImpl.getEnv().
476                 createDatabasePreemptedException(msg, dbName, this);
477         }
478 
479         closeInternal(false /*doSyncDw*/, false /*deleteTempDb*/,
480                       DbState.PREEMPTED, preemptedException);
481     }
482 
483     /**
484      * Invalidates the handle when the transaction used to open the database
485      * is aborted.
486      *
487      * Note that this method (unlike close) does not perform sync and removal
488      * of DW DBs.  A DW DB cannot be transactional.
489      */
490     synchronized void invalidate() {
491         closeInternal(false /*doSyncDw*/, false /*deleteTempDb*/,
492                       DbState.INVALID, null /*preemptedException*/);
493     }
494 
495     private void
496         closeInternal(final boolean doSyncDw,
497                       final boolean deleteTempDb,
498                       final DbState newState,
499                       final OperationFailureException preemptedException)
500         throws DatabaseException {
501 
502         /*
503          * We acquire the SecondaryAssociationLatch exclusively because
504          * associations are changed by removeReferringAssociations, and
505          * operations using the associations should not run concurrently with
506          * close.
507          */
508         final EnvironmentImpl envImpl = envHandle.getEnvironmentImpl();
509         if (envImpl != null) {
510             try {
511                 envImpl.getSecondaryAssociationLock().
512                     writeLock().lockInterruptibly();
513             } catch (InterruptedException e) {
514                 throw new ThreadInterruptedException(envImpl, e);
515             }
516         }
517         try {
518             closeInternalWork(doSyncDw, deleteTempDb, newState,
519                               preemptedException);
520         } finally {
521             if (envImpl != null) {
522                 envImpl.getSecondaryAssociationLock().writeLock().unlock();
523             }
524         }
525     }
526 
527     private void
528         closeInternalWork(final boolean doSyncDw,
529                           final boolean deleteTempDb,
530                           final DbState newState,
531                           final OperationFailureException preemptedException)
532         throws DatabaseException {
533 
534         final StringBuilder handleRefErrors = new StringBuilder();
535         RuntimeException triggerException = null;
536         DatabaseImpl dbClosed = null;
537 
538         synchronized (this) {
539 
540             /* Do nothing if handle was previously closed. */
541             if (state != DbState.OPEN) {
542                 return;
543             }
544 
545             /*
546              * Check env only after checking for closed db, to mimic close()
547              * behavior for Cursors, etc, and avoid unnecessary exception
548              * handling.  [#21264]
549              */
550             checkEnv();
551 
552             /*
553              * The state should be changed ASAP during close, so that
554              * addCursor and removeCursor will see the updated state ASAP.
555              */
556             state = newState;
557             preemptedCause = preemptedException;
558 
559             /*
560              * Throw an IllegalStateException if there are open cursors or
561              * associated secondaries.
562              */
563             if (newState == DbState.CLOSED) {
564                 if (openCursors.get() != 0) {
565                     handleRefErrors.append(" ").
566                            append(openCursors.get()).
567                            append(" open cursors.");
568                 }
569                 if (simpleAssocSecondaries != null &&
570                     simpleAssocSecondaries.size() > 0) {
571                     handleRefErrors.append(" ").
572                            append(simpleAssocSecondaries.size()).
573                            append(" associated SecondaryDatabases.");
574                 }
575                 if (foreignKeySecondaries != null &&
576                     foreignKeySecondaries.size() > 0) {
577                     handleRefErrors.append(" ").
578                            append(foreignKeySecondaries.size()).
579                            append(
580                            " associated foreign key SecondaryDatabases.");
581                 }
582             }
583 
584             trace(Level.FINEST, "Database.close: ", null, null);
585 
586             removeReferringAssociations();
587 
588             if (databaseImpl != null) {
589                 dbClosed = databaseImpl;
590                 databaseImpl.removeReferringHandle(this);
591                 envHandle.getEnvironmentImpl().
592                     getDbTree().releaseDb(databaseImpl);
593 
594                 /*
595                  * Database.close may be called after an abort.  By setting the
596                  * databaseImpl field to null we ensure that close won't call
597                  * releaseDb or endOperation. [#13415]
598                  */
599                 databaseImpl = null;
600 
601                 if (handleLocker != null) {
602 
603                     /*
604                      * If the handle was preempted, we mark the locker as
605                      * only-abortable with the DatabasePreemptedException.  If
606                      * the handle locker is a user txn, this causes the
607                      * DatabasePreemptedException to be thrown if the user
608                      * attempts to commit, or continue to use, the txn, rather
609                      * than throwing a LockConflictException.  [#17015]
610                      */
611                     if (newState == DbState.PREEMPTED) {
612                         handleLocker.setOnlyAbortable(preemptedException);
613                     }
614 
615                     /*
616                      * Tell our protecting txn that we're closing. If this type
617                      * of transaction doesn't live beyond the life of the
618                      * handle, it will release the db handle lock.
619                      */
620                     if (newState == DbState.CLOSED) {
621                         if (isWritable() &&
622                             (dbClosed.noteWriteHandleClose() == 0)) {
623                             try {
624                                 TriggerManager.runCloseTriggers(handleLocker,
625                                                                 dbClosed);
626                             } catch (RuntimeException e) {
627                                 triggerException = e;
628                             }
629                         }
630                         handleLocker.operationEnd(true);
631                     } else {
632                         handleLocker.operationEnd(false);
633                     }
634 
635                     /* Null-out indirect reference to environment. */
636                     handleLocker = null;
637                 }
638             }
639         }
640 
641         /*
642          * Notify the database when a handle is closed.  This should not be
643          * done while synchronized since it may perform database removal or
644          * sync.  Statements above are synchronized but this section must not
645          * be.
646          *
647          * Note that handleClosed may throw an exception, so any following code
648          * may not be executed.
649          */
650         if (dbClosed != null) {
651             dbClosed.handleClosed(doSyncDw, deleteTempDb);
652         }
653 
654         /* Throw exceptions for previously encountered problems. */
655         if (handleRefErrors.length() > 0) {
656             throw new IllegalStateException(
657                 "Database closed while still referenced by other handles." +
658                 handleRefErrors.toString());
659         }
660         if (triggerException != null) {
661             throw triggerException;
662         }
663     }
664 
665     /**
666      * Flushes any cached information for this database to disk; only
667      * applicable for deferred-write databases.
668      * <p> Note that deferred-write databases are automatically flushed to disk
669      * when the {@link #close} method is called.
670      *
671      * @see DatabaseConfig#setDeferredWrite DatabaseConfig.setDeferredWrite
672      *
673      * @throws com.sleepycat.je.rep.DatabasePreemptedException in a replicated
674      * environment if the master has truncated, removed or renamed the
675      * database.
676      *
677      * @throws OperationFailureException if this exception occurred earlier and
678      * caused the transaction to be invalidated.
679      *
680      * @throws EnvironmentFailureException if an unexpected, internal or
681      * environment-wide failure occurs.
682      *
683      * @throws UnsupportedOperationException if this is not a deferred-write
684      * database, or this database is read-only.
685      *
686      * @throws IllegalStateException if the database has been closed.
687      */
688     public void sync()
689         throws DatabaseException, UnsupportedOperationException {
690 
691         checkEnv();
692         checkOpen("Can't call Database.sync:");
693         trace(Level.FINEST, "Database.sync", null, null, null, null);
694 
695         databaseImpl.sync(true);
696     }
697 
698     /**
699      * Opens a sequence in the database.
700      *
701      * @param txn For a transactional database, an explicit transaction may
702      * be specified, or null may be specified to use auto-commit.  For a
703      * non-transactional database, null must be specified.
704      *
705      * @param key The key {@link DatabaseEntry} of the sequence.
706      *
707      * @param config The sequence attributes.  If null, default attributes are
708      * used.
709      *
710      * @return a new Sequence handle.
711      *
712      * @throws SequenceExistsException if the sequence record already exists
713      * and the {@code SequenceConfig ExclusiveCreate} parameter is true.
714      *
715      * @throws SequenceNotFoundException if the sequence record does not exist
716      * and the {@code SequenceConfig AllowCreate} parameter is false.
717      *
718      * @throws OperationFailureException if one of the <a
719      * href="../je/OperationFailureException.html#readFailures">Read Operation
720      * Failures</a> occurs. If the sequence does not exist and the {@link
721      * SequenceConfig#setAllowCreate AllowCreate} parameter is true, then one
722      * of the <a
723      * href="../je/OperationFailureException.html#writeFailures">Write
724      * Operation Failures</a> may also occur.
725      *
726      * @throws EnvironmentFailureException if an unexpected, internal or
727      * environment-wide failure occurs.
728      *
729      * @throws UnsupportedOperationException if this database is read-only, or
730      * this database is configured for duplicates.
731      *
732      * @throws IllegalStateException if the Sequence record is deleted by
733      * another thread during this method invocation, or the database has been
734      * closed.
735      *
736      * @throws IllegalArgumentException if an invalid parameter is specified,
737      * for example, an invalid {@code SequenceConfig} parameter.
738      */
739     public Sequence openSequence(final Transaction txn,
740                                  final DatabaseEntry key,
741                                  final SequenceConfig config)
742         throws SequenceNotFoundException, SequenceExistsException {
743 
744         try {
745             checkEnv();
746             DatabaseUtil.checkForNullDbt(key, "key", true);
747             checkOpen("Can't call Database.openSequence:");
748             trace(Level.FINEST, "Database.openSequence", txn, key, null, null);
749 
750             return new Sequence(this, txn, key, config);
751         } catch (Error E) {
752             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
753             throw E;
754         }
755     }
756 
757     /**
758      * Removes the sequence from the database.  This method should not be
759      * called if there are open handles on this sequence.
760      *
761      * @param txn For a transactional database, an explicit transaction may be
762      * specified, or null may be specified to use auto-commit.  For a
763      * non-transactional database, null must be specified.
764      *
765      * @param key The key {@link com.sleepycat.je.DatabaseEntry
766      * DatabaseEntry} of the sequence.
767      *
768      * @throws OperationFailureException if one of the <a
769      * href="../je/OperationFailureException.html#writeFailures">Write
770      * Operation Failures</a> occurs.
771      *
772      * @throws EnvironmentFailureException if an unexpected, internal or
773      * environment-wide failure occurs.
774      *
775      * @throws UnsupportedOperationException if this database is read-only.
776      */
777     public void removeSequence(final Transaction txn, final DatabaseEntry key)
778         throws DatabaseException {
779 
780         removeSequenceStat.increment();
781         try {
782             delete(txn, key);
783         } catch (Error E) {
784             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
785             throw E;
786         }
787     }
788 
789     /**
790      * Returns a cursor into the database.
791      *
792      * @param txn the transaction used to protect all operations performed with
793      * the cursor, or null if the operations should not be transaction
794      * protected.  If the database is non-transactional, null must be
795      * specified.  For a transactional database, the transaction is optional
796      * for read-only access and required for read-write access.
797      *
798      * @param cursorConfig The cursor attributes.  If null, default attributes
799      * are used.
800      *
801      * @return A database cursor.
802      *
803      * @throws com.sleepycat.je.rep.DatabasePreemptedException in a replicated
804      * environment if the master has truncated, removed or renamed the
805      * database.
806      *
807      * @throws OperationFailureException if this exception occurred earlier and
808      * caused the transaction to be invalidated.
809      *
810      * @throws EnvironmentFailureException if an unexpected, internal or
811      * environment-wide failure occurs.
812      *
813      * @throws IllegalStateException if the database has been closed.
814      *
815      * @throws IllegalArgumentException if an invalid parameter is specified,
816      * for example, an invalid {@code CursorConfig} parameter.
817      */
818     public Cursor openCursor(final Transaction txn,
819                              final CursorConfig cursorConfig)
820         throws DatabaseException, IllegalArgumentException {
821 
822         try {
823             checkEnv();
824             checkOpen("Can't open a cursor");
825             CursorConfig useConfig =
826                 (cursorConfig == null) ? CursorConfig.DEFAULT : cursorConfig;
827 
828             if (useConfig.getReadUncommitted() &&
829                 useConfig.getReadCommitted()) {
830                 throw new IllegalArgumentException(
831                     "Only one may be specified: " +
832                     "ReadCommitted or ReadUncommitted");
833             }
834 
835             trace(Level.FINEST, "Database.openCursor", txn, cursorConfig);
836             Cursor ret = newDbcInstance(txn, useConfig);
837 
838             return ret;
839         } catch (Error E) {
840             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
841             throw E;
842         }
843     }
844 
845     /**
846      * Create a DiskOrderedCursor to iterate over the records in 'this'
847      * Database.  Because the retrieval is based on Log Sequence Number (LSN)
848      * order rather than key order, records are returned in unsorted order in
849      * exchange for generally faster retrieval.  LSN order approximates disk
850      * sector order.
851      * <p>
852      * See {@link DiskOrderedCursor} for more details and a description of the
853      * consistency guarantees provided by the scan.
854      * <p>
855      * <em>WARNING:</em> After calling this method, deletion of log files by
856      * the JE log cleaner will be disabled until {@link
857      * DiskOrderedCursor#close()} is called.  To prevent unbounded growth of
858      * disk usage, be sure to call {@link DiskOrderedCursor#close()} to
859      * re-enable log file deletion.
860      */
861     public DiskOrderedCursor
862         openCursor(final DiskOrderedCursorConfig cursorConfig)
863         throws DatabaseException, IllegalArgumentException {
864 
865         try {
866             checkEnv();
867             checkOpen("Can't open a cursor");
868             DiskOrderedCursorConfig useConfig =
869                 (cursorConfig == null) ?
870                 DiskOrderedCursorConfig.DEFAULT :
871                 cursorConfig;
872 
873             trace(Level.FINEST, "Database.openForwardCursor",
874                   null, cursorConfig);
875             DiskOrderedCursor ret = new DiskOrderedCursor(this, useConfig);
876 
877             return ret;
878         } catch (Error E) {
879             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
880             throw E;
881         }
882     }
883 
884     /**
885      * Is overridden by SecondaryDatabase.
886      */
887     Cursor newDbcInstance(final Transaction txn,
888                           final CursorConfig cursorConfig)
889         throws DatabaseException {
890 
891         return new Cursor(this, txn, cursorConfig);
892     }
893 
894     /**
895      * @hidden
896      * For internal use only.
897      *
898      * Given the {@code key} and {@code data} for a locked primary DB record,
899      * update the corresponding secondary database (index) records, for
900      * secondaries enabled for incremental population.
901      * <p>
902      * The secondaries associated the primary record are determined by calling
903      * {@link SecondaryAssociation#getSecondaries}.  For each of these
904      * secondaries, {@link SecondaryDatabase#isIncrementalPopulationEnabled} is
905      * called to determine whether incremental population is enabled.  If so,
906      * appropriate secondary records are inserted and deleted so that the
907      * index accurately reflects the current state of the primary record.
908      * <p>
909      * Note that for a given primary record, this method will not modify the
910      * secondary database if the secondary has already been updated for the
911      * primary record, due to concurrent primary write operations.  Due to this
912      * behavior, certain integrity checks are not performed as documented in
913      * {@link SecondaryDatabase#startIncrementalPopulation}.
914      * <p>
915      * The primary record must be locked (read or write locked) when this
916      * method is called. Therefore, the caller should not use dirty-read to
917      * read the primary record. The simplest way to ensure that the primary
918      * record is locked is to use a cursor to read primary records, and call
919      * this method while the cursor is still positioned on the primary record.
920      * <p>
921      * It is the caller's responsibility to pass all primary records to this
922      * method that contain index keys for a secondary DB being incrementally
923      * populated, before calling {@link
924      * SecondaryDatabase#endIncrementalPopulation} on that secondary DB. It
925      * will be simpler for some applications to read and process all records in
926      * this DB in batches by calling
927      * {@link #populateSecondaries(DatabaseEntry, int)} instead.
928      *
929      * @param txn is the transaction to be used to write secondary records. If
930      * null and the secondary DBs are transactional, auto-commit will be used.
931 
932      * @param key is the key of the locked primary record.
933      *
934      * @param data is the data of the locked primary record.
935      */
936     public void populateSecondaries(final Transaction txn,
937                                     final DatabaseEntry key,
938                                     final DatabaseEntry data) {
939         try {
940             checkEnv();
941             DatabaseUtil.checkForNullDbt(key, "key", true);
942             DatabaseUtil.checkForNullDbt(data, "true", true);
943             checkOpen("Can't call populateSecondaries:");
944             trace(Level.FINEST, "populateSecondaries", null, key, data, null);
945 
946             final Collection<SecondaryDatabase> secondaries =
947                 secAssoc.getSecondaries(key);
948 
949             final Locker locker = LockerFactory.getWritableLocker(
950                 envHandle, txn, databaseImpl.isInternalDb(), isTransactional(),
951                 databaseImpl.isReplicated()); // autoTxnIsReplicated
952 
953             boolean success = false;
954 
955             try {
956                 for (final SecondaryDatabase secDb : secondaries) {
957                     if (secDb.isIncrementalPopulationEnabled()) {
958                         secDb.updateSecondary(
959                             locker, null, key, null, data);
960                     }
961                 }
962                 success = true;
963             } finally {
964                 locker.operationEnd(success);
965             }
966         } catch (Error E) {
967             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
968             throw E;
969         }
970     }
971 
972     /**
973      * @hidden
974      * For internal use only.
975      *
976      * Reads {@code batchSize} records starting at the given {@code key}, and
977      * updates the corresponding secondary database (index) records, for
978      * secondaries enabled for incremental population.  The next key to be
979      * processed is returned in the {@code key} parameter so it can be passed
980      * in to process the next batch.
981      * <p>
982      * For each primary record, its associated secondaries are determined by
983      * calling {@link SecondaryAssociation#getSecondaries}.  For each of these
984      * secondaries, {@link SecondaryDatabase#isIncrementalPopulationEnabled} is
985      * called to determine whether incremental population is enabled.  If so,
986      * appropriate secondary records are inserted and deleted so that the
987      * index accurately reflects the current state of the primary record.
988      * <p>
989      * Note that for a given primary record, this method will not modify the
990      * secondary database if the secondary has already been updated for the
991      * primary record, due to concurrent primary write operations.  Due to this
992      * behavior, certain integrity checks are not performed as documented in
993      * {@link SecondaryDatabase#startIncrementalPopulation}.
994      * <p>
995      * It is the application's responsibility to save the key returned by this
996      * method, and then pass the saved key when the method is called again to
997      * process the next batch of records.  The application may wish to save the
998      * key persistently in order to avoid restarting the processing from the
999      * beginning of the database after a crash.
1000      *
1001      * @param key contains the starting key for the batch of records to be
1002      * processed when this method is called, and contains the next key to be
1003      * processed when this method returns.  If {@code key.getData() == null}
1004      * when this method is called, the batch will begin with the first record
1005      * in the database.
1006      *
1007      * @param batchSize is the maximum number of records to be read. The
1008      * maximum number of secondary inserts and deletions that will be included
1009      * in a single transaction is the batchSize times the number of secondary
1010      * databases (associated with this primary database) that are enabled for
1011      * incremental population.
1012      *
1013      * @return true if more records may need to be processed, or false if
1014      * processing is complete.
1015      */
1016     public boolean populateSecondaries(final DatabaseEntry key,
1017                                        final int batchSize) {
1018         try {
1019             checkEnv();
1020             DatabaseUtil.checkForNullDbt(key, "key", false);
1021             if (batchSize <= 0) {
1022                 throw new IllegalArgumentException(
1023                     "batchSize must be positive");
1024             }
1025             checkOpen("Can't call populateSecondaries:");
1026             trace(Level.FINEST, "populateSecondaries", null, key, null, null);
1027 
1028             final Locker locker = LockerFactory.getWritableLocker(
1029                 envHandle, null, databaseImpl.isInternalDb(),
1030                 isTransactional(),
1031                 databaseImpl.isReplicated() /*autoTxnIsReplicated*/);
1032             try {
1033                 final Cursor cursor = new Cursor(this, locker, null);
1034                 try {
1035                     return populateSecondariesInternal(cursor, locker, key,
1036                                                        batchSize);
1037                 } finally {
1038                     cursor.close();
1039                 }
1040             } finally {
1041                 locker.operationEnd(true);
1042             }
1043         } catch (Error E) {
1044             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1045             throw E;
1046         }
1047     }
1048 
1049     /**
1050      * Use a key-only scan to walk through primary keys.  Only if the primary
1051      * key has a secondary, with incremental population enabled, do we then
1052      * read the primary data.  Then update secondaries as if this were a
1053      * primary database insertion.
1054      */
1055     private boolean populateSecondariesInternal(final Cursor cursor,
1056                                                 final Locker locker,
1057                                                 final DatabaseEntry key,
1058                                                 final int batchSize) {
1059         /*
1060          * Use dirty read so no lock is taken when the primary record has no
1061          * associated secondaries with incremental population enabled. Use
1062          * READ_UNCOMMITTED_ALL so we don't skip records to due txn aborts.
1063          */
1064         final LockMode scanMode = LockMode.READ_UNCOMMITTED_ALL;
1065 
1066         OperationStatus searchStatus;
1067         if (key.getData() == null) {
1068             /* Start at first key. */
1069             searchStatus = cursor.position(
1070                 key, Cursor.NO_RETURN_DATA, scanMode, true);
1071         } else {
1072             /* Resume at key last processed. */
1073             searchStatus = cursor.search(
1074                 key, Cursor.NO_RETURN_DATA, scanMode, SearchMode.SET_RANGE);
1075         }
1076         final DatabaseEntry data = new DatabaseEntry();
1077         int nProcessed = 0;
1078         while (searchStatus == OperationStatus.SUCCESS) {
1079             if (nProcessed >= batchSize) {
1080                 return true;
1081             }
1082             nProcessed += 1;
1083             final Collection<SecondaryDatabase> secondaries =
1084                 secAssoc.getSecondaries(key);
1085             boolean anySecondaries = false;
1086             for (final SecondaryDatabase secDb : secondaries) {
1087                 if (secDb.isIncrementalPopulationEnabled()) {
1088                     anySecondaries = true;
1089                     break;
1090                 }
1091             }
1092             if (anySecondaries) {
1093 
1094                 /*
1095                  * Note that we do not use RMW here for reading the primary.
1096                  * This would be necessary if the secondaries being written
1097                  * were accessible via reads, but the incremental population
1098                  * mode makes them inaccessible. (During a secondary read, we
1099                  * use the read lock on the primary to protect against
1100                  * modifications to the secondary, which is not locked. If we
1101                  * allowed accessed to the secondary during population, we
1102                  * would need to use RMW here.)
1103                  */
1104                 if (cursor.getCurrent(key, data, LockMode.DEFAULT) ==
1105                     OperationStatus.SUCCESS) {
1106                     for (final SecondaryDatabase secDb : secondaries) {
1107                         if (secDb.isIncrementalPopulationEnabled()) {
1108                             secDb.updateSecondary(
1109                                 locker, null, key, null, data);
1110                         }
1111                     }
1112                 }
1113             }
1114             searchStatus = cursor.retrieveNext(
1115                 key, Cursor.NO_RETURN_DATA, scanMode, GetMode.NEXT);
1116         }
1117         return false;
1118     }
1119 
1120     /**
1121      * Removes key/data pairs from the database.
1122      *
1123      * <p>The key/data pair associated with the specified key is discarded
1124      * from the database.  In the presence of duplicate key values, all
1125      * records associated with the designated key will be discarded.</p>
1126      *
1127      * <p>The key/data pair is also deleted from any associated secondary
1128      * databases.</p>
1129      *
1130      * @param txn For a transactional database, an explicit transaction may
1131      * be specified, or null may be specified to use auto-commit.  For a
1132      * non-transactional database, null must be specified.
1133      *
1134      * @param key the key {@link com.sleepycat.je.DatabaseEntry DatabaseEntry}
1135      * operated on.
1136      *
1137      * @return The method will return {@link
1138      * com.sleepycat.je.OperationStatus#NOTFOUND OperationStatus.NOTFOUND} if
1139      * the specified key is not found in the database; otherwise the method
1140      * will return {@link com.sleepycat.je.OperationStatus#SUCCESS
1141      * OperationStatus.SUCCESS}.
1142      *
1143      * @throws OperationFailureException if one of the <a
1144      * href="../je/OperationFailureException.html#writeFailures">Write
1145      * Operation Failures</a> occurs.
1146      *
1147      * @throws EnvironmentFailureException if an unexpected, internal or
1148      * environment-wide failure occurs.
1149      *
1150      * @throws UnsupportedOperationException if this database is read-only.
1151      *
1152      * @throws IllegalStateException if the database has been closed.
1153      *
1154      * @throws IllegalArgumentException if an invalid parameter is specified.
1155      */
1156     public OperationStatus delete(final Transaction txn,
1157                                   final DatabaseEntry key)
1158         throws DeleteConstraintException,
1159                LockConflictException,
1160                DatabaseException,
1161                UnsupportedOperationException,
1162                IllegalArgumentException {
1163 
1164         try {
1165             checkEnv();
1166             DatabaseUtil.checkForNullDbt(key, "key", true);
1167             checkOpen("Can't call Database.delete:");
1168             trace(Level.FINEST, "Database.delete", txn, key, null, null);
1169             deleteStat.increment();
1170 
1171             OperationStatus commitStatus = OperationStatus.NOTFOUND;
1172             Locker locker = null;
1173             try {
1174                 locker = LockerFactory.getWritableLocker(
1175                     envHandle, txn,
1176                     databaseImpl.isInternalDb(),
1177                     isTransactional(),
1178                     databaseImpl.isReplicated()); // autoTxnIsReplicated
1179 
1180                 commitStatus = deleteInternal(locker, key);
1181                 return commitStatus;
1182             } finally {
1183                 if (locker != null) {
1184                     locker.operationEnd(commitStatus);
1185                 }
1186             }
1187         } catch (Error E) {
1188             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1189             throw E;
1190         }
1191     }
1192 
1193     /**
1194      * Internal version of delete() that does no parameter checking.  Notify
1195      * triggers, update secondaries and enforce foreign key constraints.
1196      * Deletes all duplicates.
1197      *
1198      * Note that this algorithm is duplicated in Database and Cursor for
1199      * efficiency reasons: in Cursor delete we must separately fetch the key
1200      * and data, while in Database delete we know the key and have to search
1201      * anyway so we can get the old data when we search.  The two algorithms
1202      * need to be kept in sync.
1203      */
1204     OperationStatus deleteInternal(final Locker locker,
1205                                    final DatabaseEntry key) {
1206 
1207         final boolean hasUserTriggers = (databaseImpl.getTriggers() != null);
1208         final boolean hasAssociations = hasSecondaryOrForeignKeyAssociations();
1209 
1210         if (hasAssociations) {
1211             try {
1212                 databaseImpl.getEnv().getSecondaryAssociationLock().
1213                     readLock().lockInterruptibly();
1214             } catch (InterruptedException e) {
1215                 throw new ThreadInterruptedException(
1216                     databaseImpl.getEnv(), e);
1217             }
1218         }
1219         try {
1220 
1221             /*
1222              * Get secondaries from the association and determine whether the
1223              * old data is needed.
1224              */
1225             final Collection<SecondaryDatabase> secondaries;
1226             final Collection<SecondaryDatabase> fkSecondaries;
1227             final boolean needOldData;
1228             if (hasAssociations) {
1229                 secondaries = secAssoc.getSecondaries(key);
1230                 fkSecondaries = foreignKeySecondaries;
1231                 needOldData = hasUserTriggers ||
1232                     SecondaryDatabase.needOldDataForDelete(secondaries);
1233             } else {
1234                 secondaries = null;
1235                 fkSecondaries = null;
1236                 needOldData = hasUserTriggers;
1237             }
1238 
1239             final Cursor cursor = new Cursor(this, locker, null);
1240             try {
1241                 cursor.checkUpdatesAllowed();
1242                 cursor.setNonSticky(true);
1243 
1244                 /*
1245                  * Fetch old data with RMW to avoid lock upgrades.  Do not
1246                  * fetch old data unless needed for secondaries.  And if we
1247                  * don't need the data, use dirty read to enable the
1248                  * uncontended lock optimization.  Use dirty-read-all to ensure
1249                  * that we don't skip an uncommitted deleted record for a txn
1250                  * that is later aborted.
1251                  */
1252                 final DatabaseEntry oldData = new DatabaseEntry();
1253                 LockMode lockMode = LockMode.RMW;
1254                 if (!needOldData) {
1255                     oldData.setPartial(0, 0, true);
1256                     if (!cursor.isSerializableIsolation(LockMode.RMW)) {
1257                         lockMode = LockMode.READ_UNCOMMITTED_ALL;
1258                     }
1259                 }
1260 
1261                 /* Position a cursor at the specified data record. */
1262                 OperationStatus searchStatus = cursor.search(
1263                     key, oldData, lockMode, SearchMode.SET);
1264 
1265                 if (searchStatus != OperationStatus.SUCCESS) {
1266                     return OperationStatus.NOTFOUND;
1267                 }
1268 
1269                 while (searchStatus == OperationStatus.SUCCESS) {
1270 
1271                     /*
1272                      * Enforce foreign key constraints before secondary
1273                      * updates, so that ForeignKeyDeleteAction.ABORT is applied
1274                      * before deleting the secondary keys.
1275                      */
1276                     if (fkSecondaries != null) {
1277                         for (final SecondaryDatabase secDb : fkSecondaries) {
1278                             secDb.onForeignKeyDelete(locker, key);
1279                         }
1280                     }
1281 
1282                     final DatabaseEntry notifyOldData =
1283                         needOldData ? oldData : null;
1284 
1285                     /*
1286                      * Update secondaries before deletion, so that a primary
1287                      * record always exists while secondary keys refer to it.
1288                      * This is relied on by secondary read-uncommitted.
1289                      */
1290                     if (secondaries != null) {
1291                         for (final SecondaryDatabase secDb : secondaries) {
1292                             secDb.updateSecondary(locker, null, key,
1293                                                   notifyOldData, null);
1294                         }
1295                     }
1296 
1297                     /* The actual deletion. */
1298                     final OperationStatus deleteStatus =
1299                         cursor.deleteNoNotify(databaseImpl.getRepContext());
1300                     if (deleteStatus != OperationStatus.SUCCESS) {
1301                         return deleteStatus;
1302                     }
1303 
1304                     /* Run triggers after actual deletion. */
1305                     if (hasUserTriggers) {
1306                         TriggerManager.runDeleteTriggers(locker, databaseImpl,
1307                                                          key, notifyOldData);
1308                     }
1309 
1310                     /* Get next duplicate in this database. */
1311                     if (databaseImpl.getSortedDuplicates()) {
1312                         searchStatus = cursor.retrieveNext(
1313                             key, oldData, LockMode.RMW, GetMode.NEXT_DUP);
1314                     } else {
1315                         searchStatus = OperationStatus.NOTFOUND;
1316                     }
1317                 }
1318                 return OperationStatus.SUCCESS;
1319             } finally {
1320                 cursor.close();
1321             }
1322         } finally {
1323             if (hasAssociations) {
1324                 databaseImpl.getEnv().getSecondaryAssociationLock().
1325                     readLock().unlock();
1326             }
1327         }
1328     }
1329 
1330     /**
1331      * Retrieves the key/data pair with the given key.  If the matching key has
1332      * duplicate values, the first data item in the set of duplicates is
1333      * returned. Retrieval of duplicates requires the use of {@link Cursor}
1334      * operations.
1335      *
1336      * @param txn For a transactional database, an explicit transaction may be
1337      * specified to transaction-protect the operation, or null may be specified
1338      * to perform the operation without transaction protection.  For a
1339      * non-transactional database, null must be specified.
1340      *
1341      * @param key the key used as input.  It must be initialized with a
1342      * non-null byte array by the caller.
1343      *
1344      * @param data the data returned as output.  Its byte array does not need
1345      * to be initialized by the caller.
1346      * A <a href="Cursor.html#partialEntry">partial data item</a> may be
1347      * specified to optimize for key only or partial data retrieval.
1348      *
1349      * @param lockMode the locking attributes; if null, default attributes are
1350      * used.
1351      *
1352      * @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
1353      * OperationStatus.NOTFOUND} if no matching key/data pair is found;
1354      * otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
1355      * OperationStatus.SUCCESS}.
1356      *
1357      * @throws OperationFailureException if one of the <a
1358      * href="OperationFailureException.html#readFailures">Read Operation
1359      * Failures</a> occurs.
1360      *
1361      * @throws EnvironmentFailureException if an unexpected, internal or
1362      * environment-wide failure occurs.
1363      *
1364      * @throws IllegalStateException if the database has been closed.
1365      *
1366      * @throws IllegalArgumentException if an invalid parameter is specified.
1367      */
1368     public OperationStatus get(final Transaction txn,
1369                                final DatabaseEntry key,
1370                                final DatabaseEntry data,
1371                                LockMode lockMode)
1372         throws LockConflictException,
1373                DatabaseException,
1374                IllegalArgumentException {
1375 
1376         try {
1377             checkEnv();
1378             DatabaseUtil.checkForNullDbt(key, "key", true);
1379             DatabaseUtil.checkForNullDbt(data, "data", false);
1380             checkOpen("Can't call Database.get:");
1381             trace(Level.FINEST, "Database.get", txn, key, null, lockMode);
1382             getStat.increment();
1383 
1384             CursorConfig cursorConfig = CursorConfig.DEFAULT;
1385             if (lockMode == LockMode.READ_COMMITTED) {
1386                 cursorConfig = CursorConfig.READ_COMMITTED;
1387                 lockMode = null;
1388             }
1389             checkLockModeWithoutTxn(txn, lockMode);
1390 
1391             Locker locker = null;
1392             Cursor cursor = null;
1393             OperationStatus commitStatus = null;
1394             try {
1395                 locker = LockerFactory.getReadableLocker(
1396                     this, txn, cursorConfig.getReadCommitted());
1397 
1398                 cursor = new Cursor(this, locker, cursorConfig);
1399                 cursor.setNonSticky(true);
1400                 commitStatus =
1401                     cursor.search(key, data, lockMode, SearchMode.SET);
1402                 return commitStatus;
1403             } finally {
1404                 if (cursor != null) {
1405                     cursor.close();
1406                 }
1407 
1408                 if (locker != null) {
1409                     locker.operationEnd(commitStatus);
1410                 }
1411             }
1412         } catch (Error E) {
1413             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1414             throw E;
1415         }
1416     }
1417 
1418     /**
1419      * Retrieves the key/data pair with the given key and data value, that is,
1420      * both the key and data items must match.
1421      *
1422      * @param txn For a transactional database, an explicit transaction may be
1423      * specified to transaction-protect the operation, or null may be specified
1424      * to perform the operation without transaction protection.  For a
1425      * non-transactional database, null must be specified.
1426      *
1427      * @param key the key used as input. It must be initialized with a non-null
1428      * byte array by the caller.
1429      *
1430      * @param data the data used as input. It must be initialized with a
1431      * non-null byte array by the caller.
1432      *
1433      * @param lockMode the locking attributes; if null, default attributes are
1434      * used.
1435      *
1436      * @return {@link com.sleepycat.je.OperationStatus#NOTFOUND
1437      * OperationStatus.NOTFOUND} if no matching key/data pair is found;
1438      * otherwise, {@link com.sleepycat.je.OperationStatus#SUCCESS
1439      * OperationStatus.SUCCESS}.
1440      *
1441      * @throws OperationFailureException if one of the <a
1442      * href="OperationFailureException.html#readFailures">Read Operation
1443      * Failures</a> occurs.
1444      *
1445      * @throws EnvironmentFailureException if an unexpected, internal or
1446      * environment-wide failure occurs.
1447      *
1448      * @throws IllegalStateException if the database has been closed.
1449      *
1450      * @throws IllegalArgumentException if an invalid parameter is specified.
1451      */
1452     public OperationStatus getSearchBoth(final Transaction txn,
1453                                          final DatabaseEntry key,
1454                                          final DatabaseEntry data,
1455                                          LockMode lockMode)
1456         throws LockConflictException,
1457                DatabaseException,
1458                IllegalArgumentException {
1459 
1460         try {
1461             checkEnv();
1462             DatabaseUtil.checkForNullDbt(key, "key", true);
1463             DatabaseUtil.checkForNullDbt(data, "data", true);
1464             checkOpen("Can't call Database.getSearchBoth:");
1465             trace(Level.FINEST, "Database.getSearchBoth", txn, key, data,
1466                   lockMode);
1467             getSearchBothStat.increment();
1468 
1469             CursorConfig cursorConfig = CursorConfig.DEFAULT;
1470             if (lockMode == LockMode.READ_COMMITTED) {
1471                 cursorConfig = CursorConfig.READ_COMMITTED;
1472                 lockMode = null;
1473             }
1474             checkLockModeWithoutTxn(txn, lockMode);
1475 
1476             Locker locker = null;
1477             Cursor cursor = null;
1478             OperationStatus commitStatus = null;
1479             try {
1480                 locker = LockerFactory.getReadableLocker(
1481                     this, txn, cursorConfig.getReadCommitted());
1482 
1483                 cursor = new Cursor(this, locker, cursorConfig);
1484                 cursor.setNonSticky(true);
1485                 commitStatus =
1486                     cursor.search(key, data, lockMode, SearchMode.BOTH);
1487                 return commitStatus;
1488             } finally {
1489                 if (cursor != null) {
1490                     cursor.close();
1491                 }
1492 
1493                 if (locker != null) {
1494                     locker.operationEnd(commitStatus);
1495                 }
1496             }
1497         } catch (Error E) {
1498             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1499             throw E;
1500         }
1501     }
1502 
1503     /**
1504      * Stores the key/data pair into the database.
1505      *
1506      * <p>If the key already appears in the database and duplicates are not
1507      * configured, the data associated with the key will be replaced.  If the
1508      * key already appears in the database and sorted duplicates are
1509      * configured, the new data value is inserted at the correct sorted
1510      * location.</p>
1511      *
1512      * @param txn For a transactional database, an explicit transaction may be
1513      * specified, or null may be specified to use auto-commit.  For a
1514      * non-transactional database, null must be specified.
1515      *
1516      * @param key the key {@link com.sleepycat.je.DatabaseEntry DatabaseEntry}
1517      * operated on.
1518      *
1519      * @param data the data {@link com.sleepycat.je.DatabaseEntry
1520      * DatabaseEntry} stored.
1521      *
1522      * @return {@link com.sleepycat.je.OperationStatus#SUCCESS
1523      * OperationStatus.SUCCESS} if the operation succeeds.
1524      *
1525      * @throws OperationFailureException if one of the <a
1526      * href="../je/OperationFailureException.html#writeFailures">Write
1527      * Operation Failures</a> occurs.
1528      *
1529      * @throws OperationFailureException if this exception occurred earlier and
1530      * caused the transaction to be invalidated.
1531      *
1532      * @throws EnvironmentFailureException if an unexpected, internal or
1533      * environment-wide failure occurs.
1534      *
1535      * @throws UnsupportedOperationException if this database is read-only.
1536      *
1537      * @throws IllegalStateException if the database has been closed.
1538      */
1539     public OperationStatus put(final Transaction txn,
1540                                final DatabaseEntry key,
1541                                final DatabaseEntry data)
1542         throws DatabaseException {
1543 
1544         checkEnv();
1545         DatabaseUtil.checkForNullDbt(key, "key", true);
1546         DatabaseUtil.checkForNullDbt(data, "data", true);
1547         DatabaseUtil.checkForPartialKey(key);
1548         checkOpen("Can't call Database.put");
1549         trace(Level.FINEST, "Database.put", txn, key, data, null);
1550         putStat.increment();
1551 
1552         return putInternal(txn, key, data, PutMode.OVERWRITE);
1553     }
1554 
1555     /**
1556      * Stores the key/data pair into the database if the key does not already
1557      * appear in the database.
1558      *
1559      * <p>This method will return {@link
1560      * com.sleepycat.je.OperationStatus#KEYEXIST OpeationStatus.KEYEXIST} if
1561      * the key already exists in the database, even if the database supports
1562      * duplicates.</p>
1563      *
1564      * @param txn For a transactional database, an explicit transaction may be
1565      * specified, or null may be specified to use auto-commit.  For a
1566      * non-transactional database, null must be specified.
1567      *
1568      * @param key the key {@link com.sleepycat.je.DatabaseEntry DatabaseEntry}
1569      * operated on.
1570      *
1571      * @param data the data {@link com.sleepycat.je.DatabaseEntry
1572      * DatabaseEntry} stored.
1573      *
1574      * @return {@link com.sleepycat.je.OperationStatus#KEYEXIST
1575      * OperationStatus.KEYEXIST} if the key already appears in the database,
1576      * else {@link com.sleepycat.je.OperationStatus#SUCCESS
1577      * OperationStatus.SUCCESS}
1578      *
1579      * @throws OperationFailureException if one of the <a
1580      * href="../je/OperationFailureException.html#writeFailures">Write
1581      * Operation Failures</a> occurs.
1582      *
1583      * @throws EnvironmentFailureException if an unexpected, internal or
1584      * environment-wide failure occurs.
1585      *
1586      * @throws UnsupportedOperationException if this database is read-only.
1587      *
1588      * @throws IllegalStateException if the database has been closed.
1589      */
1590     public OperationStatus putNoOverwrite(final Transaction txn,
1591                                           final DatabaseEntry key,
1592                                           final DatabaseEntry data)
1593         throws DatabaseException {
1594 
1595         checkEnv();
1596         DatabaseUtil.checkForNullDbt(key, "key", true);
1597         DatabaseUtil.checkForNullDbt(data, "data", true);
1598         DatabaseUtil.checkForPartialKey(key);
1599         checkOpen("Can't call Database.putNoOverWrite");
1600         trace(Level.FINEST, "Database.putNoOverwrite", txn, key, data, null);
1601         putNoOverwriteStat.increment();
1602 
1603         return putInternal(txn, key, data, PutMode.NO_OVERWRITE);
1604     }
1605 
1606     /**
1607      * Stores the key/data pair into the database if it does not already appear
1608      * in the database.
1609      *
1610      * <p>This method may only be called if the underlying database has been
1611      * configured to support sorted duplicates.</p>
1612      *
1613      * @param txn For a transactional database, an explicit transaction may be
1614      * specified, or null may be specified to use auto-commit.  For a
1615      * non-transactional database, null must be specified.
1616      *
1617      * @param key the key {@link com.sleepycat.je.DatabaseEntry DatabaseEntry}
1618      * operated on.
1619      *
1620      * @param data the data {@link com.sleepycat.je.DatabaseEntry
1621      * DatabaseEntry} stored.
1622      *
1623      * @return {@link com.sleepycat.je.OperationStatus#KEYEXIST
1624      * OperationStatus.KEYEXIST} if the key/data pair already appears in the
1625      * database, else {@link com.sleepycat.je.OperationStatus#SUCCESS
1626      * OperationStatus.SUCCESS}
1627      *
1628      * @throws OperationFailureException if one of the <a
1629      * href="../je/OperationFailureException.html#writeFailures">Write
1630      * Operation Failures</a> occurs.
1631      *
1632      * @throws EnvironmentFailureException if an unexpected, internal or
1633      * environment-wide failure occurs.
1634      *
1635      * @throws UnsupportedOperationException if this database is not configured
1636      * for duplicates, or this database is read-only.
1637      *
1638      * @throws IllegalStateException if the database has been closed.
1639      */
1640     public OperationStatus putNoDupData(final Transaction txn,
1641                                         final DatabaseEntry key,
1642                                         final DatabaseEntry data)
1643         throws DatabaseException {
1644 
1645         checkEnv();
1646         DatabaseUtil.checkForNullDbt(key, "key", true);
1647         DatabaseUtil.checkForNullDbt(data, "data", true);
1648         DatabaseUtil.checkForPartialKey(key);
1649         checkOpen("Can't call Database.putNoDupData");
1650         if (!databaseImpl.getSortedDuplicates()) {
1651             throw new UnsupportedOperationException(
1652                 "Database is not configured for duplicate data.");
1653         }
1654         trace(Level.FINEST, "Database.putNoDupData", txn, key, data, null);
1655         putNoDupDataStat.increment();
1656 
1657         return putInternal(txn, key, data, PutMode.NO_DUP_DATA);
1658     }
1659 
1660     /**
1661      * Internal version of put() that does no parameter checking.
1662      */
1663     OperationStatus putInternal(final Transaction txn,
1664                                 final DatabaseEntry key,
1665                                 final DatabaseEntry data,
1666                                 final PutMode putMode)
1667         throws DatabaseException {
1668 
1669         try {
1670             Locker locker = null;
1671             Cursor cursor = null;
1672             OperationStatus commitStatus = OperationStatus.KEYEXIST;
1673             try {
1674                 locker = LockerFactory.getWritableLocker(
1675                     envHandle, txn,
1676                     databaseImpl.isInternalDb(),
1677                     isTransactional(),
1678                     databaseImpl.isReplicated()); // autoTxnIsReplicated
1679 
1680                 cursor = new Cursor(this, locker, null);
1681                 cursor.setNonSticky(true);
1682                 commitStatus = cursor.putInternal(key, data, putMode);
1683                 return commitStatus;
1684             } finally {
1685                 if (cursor != null) {
1686                     cursor.close();
1687                 }
1688                 if (locker != null) {
1689                     locker.operationEnd(commitStatus);
1690                 }
1691             }
1692         } catch (Error E) {
1693             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1694             throw E;
1695         }
1696     }
1697 
1698     /**
1699      * Creates a specialized join cursor for use in performing equality or
1700      * natural joins on secondary indices.
1701      *
1702      * <p>Each cursor in the <code>cursors</code> array must have been
1703      * initialized to refer to the key on which the underlying database should
1704      * be joined.  Typically, this initialization is done by calling {@link
1705      * Cursor#getSearchKey Cursor.getSearchKey}.</p>
1706      *
1707      * <p>Once the cursors have been passed to this method, they should not be
1708      * accessed or modified until the newly created join cursor has been
1709      * closed, or else inconsistent results may be returned.  However, the
1710      * position of the cursors will not be changed by this method or by the
1711      * methods of the join cursor.</p>
1712      *
1713      * @param cursors an array of cursors associated with this primary
1714      * database.  In a replicated environment, an explicit transaction must be
1715      * specified when opening each cursor, unless read-uncommitted isolation is
1716      * isolation is specified via the {@link CursorConfig} or {@link LockMode}
1717      * parameter.
1718      *
1719      * @param config The join attributes.  If null, default attributes are
1720      * used.
1721      *
1722      * @return a specialized cursor that returns the results of the equality
1723      * join operation.
1724      *
1725      * @throws OperationFailureException if one of the <a
1726      * href="OperationFailureException.html#readFailures">Read Operation
1727      * Failures</a> occurs.
1728      *
1729      * @throws EnvironmentFailureException if an unexpected, internal or
1730      * environment-wide failure occurs.
1731      *
1732      * @throws IllegalStateException if the database has been closed.
1733      *
1734      * @throws IllegalArgumentException if an invalid parameter is specified,
1735      * for example, an invalid {@code JoinConfig} parameter.
1736      *
1737      * @see JoinCursor
1738      */
1739     public JoinCursor join(final Cursor[] cursors, final JoinConfig config)
1740         throws DatabaseException, IllegalArgumentException {
1741 
1742         try {
1743             checkEnv();
1744             checkOpen("Can't call Database.join");
1745             DatabaseUtil.checkForNullParam(cursors, "cursors");
1746             if (cursors.length == 0) {
1747                 throw new IllegalArgumentException(
1748                     "At least one cursor is required.");
1749             }
1750 
1751             /*
1752              * Check that all cursors use the same locker, if any cursor is
1753              * transactional.  And if non-transactional, that all databases are
1754              * in the same environment.
1755              */
1756             Locker locker = cursors[0].getCursorImpl().getLocker();
1757             if (!locker.isTransactional()) {
1758                 EnvironmentImpl env = envHandle.getEnvironmentImpl();
1759                 for (int i = 1; i < cursors.length; i += 1) {
1760                     Locker locker2 = cursors[i].getCursorImpl().getLocker();
1761                     if (locker2.isTransactional()) {
1762                         throw new IllegalArgumentException(
1763                             "All cursors must use the same transaction.");
1764                     }
1765                     EnvironmentImpl env2 =
1766                         cursors[i].getDatabaseImpl().getEnv();
1767                     if (env != env2) {
1768                         throw new IllegalArgumentException(
1769                             "All cursors must use the same environment.");
1770                     }
1771                 }
1772                 locker = null; /* Don't reuse a non-transactional locker. */
1773             } else {
1774                 for (int i = 1; i < cursors.length; i += 1) {
1775                     Locker locker2 = cursors[i].getCursorImpl().getLocker();
1776                     if (locker.getTxnLocker() != locker2.getTxnLocker()) {
1777                         throw new IllegalArgumentException(
1778                             "All cursors must use the same transaction.");
1779                     }
1780                 }
1781             }
1782 
1783             /* Create the join cursor. */
1784             return new JoinCursor(locker, this, cursors, config);
1785         } catch (Error E) {
1786             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
1787             throw E;
1788         }
1789     }
1790 
1791     /**
1792      * Preloads the cache.  This method should only be called when there are no
1793      * operations being performed on the database in other threads.  Executing
1794      * preload during concurrent updates may result in some or all of the tree
1795      * being loaded into the JE cache.  Executing preload during any other
1796      * types of operations may result in JE exceeding its allocated cache
1797      * size. preload() effectively locks the entire database and therefore will
1798      * lock out the checkpointer, cleaner, and compressor, as well as not allow
1799      * eviction to occur.
1800      *
1801      * @deprecated As of JE 2.0.83, replaced by {@link
1802      * Database#preload(PreloadConfig)}.</p>
1803      *
1804      * @param maxBytes The maximum number of bytes to load.  If maxBytes is 0,
1805      * je.evictor.maxMemory is used.
1806      *
1807      * @throws OperationFailureException if one of the <a
1808      * href="OperationFailureException.html#readFailures">Read Operation
1809      * Failures</a> occurs.
1810      *
1811      * @throws EnvironmentFailureException if an unexpected, internal or
1812      * environment-wide failure occurs.
1813      *
1814      * @throws IllegalStateException if the database has been closed.
1815      */
1816     public void preload(final long maxBytes)
1817         throws DatabaseException {
1818 
1819         checkEnv();
1820         checkOpen("Can't call Database.preload");
1821 
1822         PreloadConfig config = new PreloadConfig();
1823         config.setMaxBytes(maxBytes);
1824         databaseImpl.preload(config);
1825     }
1826 
1827     /**
1828      * Preloads the cache.  This method should only be called when there are no
1829      * operations being performed on the database in other threads.  Executing
1830      * preload during concurrent updates may result in some or all of the tree
1831      * being loaded into the JE cache.  Executing preload during any other
1832      * types of operations may result in JE exceeding its allocated cache
1833      * size. preload() effectively locks the entire database and therefore will
1834      * lock out the checkpointer, cleaner, and compressor, as well as not allow
1835      * eviction to occur.
1836      *
1837      * @deprecated As of JE 2.0.101, replaced by {@link
1838      * Database#preload(PreloadConfig)}.</p>
1839      *
1840      * @param maxBytes The maximum number of bytes to load.  If maxBytes is 0,
1841      * je.evictor.maxMemory is used.
1842      *
1843      * @param maxMillisecs The maximum time in milliseconds to use when
1844      * preloading.  Preloading stops once this limit has been reached.  If
1845      * maxMillisecs is 0, preloading can go on indefinitely or until maxBytes
1846      * (if non-0) is reached.
1847      *
1848      * @throws OperationFailureException if one of the <a
1849      * href="OperationFailureException.html#readFailures">Read Operation
1850      * Failures</a> occurs.
1851      *
1852      * @throws EnvironmentFailureException if an unexpected, internal or
1853      * environment-wide failure occurs.
1854      *
1855      * @throws IllegalStateException if the database has been closed.
1856      */
1857     public void preload(final long maxBytes, final long maxMillisecs)
1858         throws DatabaseException {
1859 
1860         checkEnv();
1861         checkOpen("Can't call Database.preload");
1862 
1863         PreloadConfig config = new PreloadConfig();
1864         config.setMaxBytes(maxBytes);
1865         config.setMaxMillisecs(maxMillisecs);
1866         databaseImpl.preload(config);
1867     }
1868 
1869     /**
1870      * Preloads the cache.  This method should only be called when there are no
1871      * operations being performed on the database in other threads.  Executing
1872      * preload during concurrent updates may result in some or all of the tree
1873      * being loaded into the JE cache.  Executing preload during any other
1874      * types of operations may result in JE exceeding its allocated cache
1875      * size. preload() effectively locks the entire database and therefore will
1876      * lock out the checkpointer, cleaner, and compressor, as well as not allow
1877      * eviction to occur.  If the database is replicated and the environment is
1878      * in the replica state, then the replica may become temporarily
1879      * disconnected from the master if the replica needs to replay changes
1880      * against the database and is locked out because the time taken by the
1881      * preload operation exceeds {@link
1882      * com.sleepycat.je.rep.ReplicationConfig#FEEDER_TIMEOUT}.
1883      * <p>
1884      * While this method preloads a single database, {@link
1885      * Environment#preload} lets you preload multiple databases.
1886      *
1887      * @param config The PreloadConfig object that specifies the parameters
1888      * of the preload.
1889      *
1890      * @return A PreloadStats object with various statistics about the
1891      * preload() operation.
1892      *
1893      * @throws OperationFailureException if one of the <a
1894      * href="OperationFailureException.html#readFailures">Read Operation
1895      * Failures</a> occurs.
1896      *
1897      * @throws EnvironmentFailureException if an unexpected, internal or
1898      * environment-wide failure occurs.
1899      *
1900      * @throws IllegalStateException if the database has been closed.
1901      *
1902      * @throws IllegalArgumentException if {@code PreloadConfig.getMaxBytes} is
1903      * greater than size of the JE cache.
1904      */
1905     public PreloadStats preload(final PreloadConfig config)
1906         throws DatabaseException {
1907 
1908         checkEnv();
1909         checkOpen("Can't call Database.preload");
1910 
1911         PreloadConfig useConfig =
1912             (config == null) ? new PreloadConfig() : config;
1913         return databaseImpl.preload(useConfig);
1914     }
1915 
1916     /**
1917      * Counts the key/data pairs in the database. This operation is faster than
1918      * obtaining a count from a cursor based scan of the database, and will not
1919      * perturb the current contents of the cache. However, the count is not
1920      * guaranteed to be accurate if there are concurrent updates. Note that
1921      * this method does scan a significant portion of the database and should
1922      * be considered a fairly expensive operation.
1923      *
1924      * This operation uses the an internal infrastructure and algorithm that is
1925      * similar to the one used for the {@link DiskOrderedCursor}. Specifically,
1926      * it will disable deletion of log files by the JE log cleaner during its
1927      * execution and will consume a certain amount of memory (but without
1928      * affecting the memory that is available for the JE cache). To avoid
1929      * excessive memory consumption (and a potential {@code OutOfMemoryError})
1930      * this method places an internal limit on its memory consumption. If this
1931      * limit is reached, the method will still work properly, but its
1932      * performance will degrade. To specify a different memory limit than the
1933      * one used by this method, use the
1934      * {@link Database#count(long memoryLimit)} method.
1935      *
1936      * Currently, the internal memory limit is calculated as 10% of the
1937      * difference between the max JVM memory (the value returned by
1938      * Runtime.getRuntime().maxMemory()) and the configured JE cache size.
1939      *
1940      * @return The count of key/data pairs in the database.
1941      *
1942      * @throws OperationFailureException if one of the <a
1943      * href="OperationFailureException.html#readFailures">Read Operation
1944      * Failures</a> occurs.
1945      *
1946      * @throws EnvironmentFailureException if an unexpected, internal or
1947      * environment-wide failure occurs.
1948      *
1949      * @throws IllegalStateException if the database has been closed.
1950      */
1951     public long count()
1952         throws DatabaseException {
1953 
1954         checkEnv();
1955         checkOpen("Can't call Database.count");
1956 
1957         return databaseImpl.count(0);
1958     }
1959 
1960     /**
1961      * Counts the key/data pairs in the database. This operation is faster than
1962      * obtaining a count from a cursor based scan of the database, and will not
1963      * perturb the current contents of the cache. However, the count is not
1964      * guaranteed to be accurate if there are concurrent updates. Note that
1965      * this method does scan a significant portion of the database and should
1966      * be considered a fairly expensive operation.
1967      *
1968      * This operation uses the an internal infrastructure and algorithm that is
1969      * similar to the one used for the {@link DiskOrderedCursor}. Specifically,
1970      * it will disable deletion of log files by the JE log cleaner during its
1971      * execution and will consume a certain amount of memory (but without
1972      * affecting the memory that is available for the JE cache). To avoid
1973      * excessive memory consumption (and a potential {@code OutOfMemoryError})
1974      * this method takes as input an upper bound on the memory it may consume.
1975      * If this limit is reached, the method will still work properly, but its
1976      * performance will degrade.
1977      *
1978      * @param memoryLimit The maximum memory (in bytes) that may be consumed
1979      * by this method.
1980      *
1981      * @return The count of key/data pairs in the database.
1982      *
1983      * @throws OperationFailureException if one of the <a
1984      * href="OperationFailureException.html#readFailures">Read Operation
1985      * Failures</a> occurs.
1986      *
1987      * @throws EnvironmentFailureException if an unexpected, internal or
1988      * environment-wide failure occurs.
1989      *
1990      * @throws IllegalStateException if the database has been closed.
1991      */
1992     public long count(long memoryLimit)
1993         throws DatabaseException {
1994 
1995         checkEnv();
1996         checkOpen("Can't call Database.count");
1997 
1998         return databaseImpl.count(memoryLimit);
1999     }
2000 
2001     /**
2002      * Returns database statistics.
2003      *
2004      * <p>If this method has not been configured to avoid expensive operations
2005      * (using the {@link com.sleepycat.je.StatsConfig#setFast
2006      * StatsConfig.setFast} method), it will access some of or all the pages in
2007      * the database, incurring a severe performance penalty as well as possibly
2008      * flushing the underlying cache.</p>
2009      *
2010      * <p>In the presence of multiple threads or processes accessing an active
2011      * database, the information returned by this method may be
2012      * out-of-date.</p>
2013      *
2014      * @param config The statistics returned; if null, default statistics are
2015      * returned.
2016      *
2017      * @return Database statistics.
2018      *
2019      * @throws OperationFailureException if one of the <a
2020      * href="OperationFailureException.html#readFailures">Read Operation
2021      * Failures</a> occurs.
2022      *
2023      * @throws EnvironmentFailureException if an unexpected, internal or
2024      * environment-wide failure occurs.
2025      *
2026      * @throws IllegalStateException if the database has been closed.
2027      */
2028     public DatabaseStats getStats(final StatsConfig config)
2029         throws DatabaseException {
2030 
2031         checkEnv();
2032         checkOpen("Can't call Database.stat");
2033         StatsConfig useConfig =
2034             (config == null) ? StatsConfig.DEFAULT : config;
2035 
2036         if (databaseImpl != null) {
2037             return databaseImpl.stat(useConfig);
2038         }
2039         return null;
2040     }
2041 
2042     /**
2043      * Verifies the integrity of the database.
2044      *
2045      * <p>Verification is an expensive operation that should normally only be
2046      * used for troubleshooting and debugging.</p>
2047      *
2048      * @param config Configures the verify operation; if null, the default
2049      * operation is performed.
2050      *
2051      * @return Database statistics.
2052      *
2053      * @throws OperationFailureException if one of the <a
2054      * href="OperationFailureException.html#readFailures">Read Operation
2055      * Failures</a> occurs.
2056      *
2057      * @throws EnvironmentFailureException if an unexpected, internal or
2058      * environment-wide failure occurs.
2059      *
2060      * @throws IllegalStateException if the database has been closed.
2061      *
2062      * @throws IllegalArgumentException if an invalid parameter is specified.
2063      */
2064     public DatabaseStats verify(final VerifyConfig config)
2065         throws DatabaseException {
2066 
2067         try {
2068             checkEnv();
2069             checkOpen("Can't call Database.verify");
2070             VerifyConfig useConfig =
2071                 (config == null) ? VerifyConfig.DEFAULT : config;
2072 
2073             DatabaseStats stats = databaseImpl.getEmptyStats();
2074             databaseImpl.verify(useConfig, stats);
2075             return stats;
2076         } catch (Error E) {
2077             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
2078             throw E;
2079         }
2080     }
2081 
2082     /**
2083      * Returns the database name.
2084      *
2085      * <p>This method may be called at any time during the life of the
2086      * application.</p>
2087      *
2088      * @return The database name.
2089      */
2090     public String getDatabaseName()
2091         throws DatabaseException {
2092 
2093         try {
2094             checkEnv();
2095             if (databaseImpl != null) {
2096                 return databaseImpl.getName();
2097             }
2098             return null;
2099         } catch (Error E) {
2100             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
2101             throw E;
2102         }
2103     }
2104 
2105     /*
2106      * Non-transactional database name, safe to access when creating error
2107      * messages.
2108      */
2109     String getDebugName() {
2110         if (databaseImpl != null) {
2111             return databaseImpl.getDebugName();
2112         }
2113         return null;
2114     }
2115 
2116     /**
2117      * Returns this Database object's configuration.
2118      *
2119      * <p>This may differ from the configuration used to open this object if
2120      * the database existed previously.</p>
2121      *
2122      * @return This Database object's configuration.
2123      *
2124      * @throws EnvironmentFailureException if an unexpected, internal or
2125      * environment-wide failure occurs.
2126      */
2127     public DatabaseConfig getConfig() {
2128 
2129         try {
2130             return DatabaseConfig.combineConfig(databaseImpl, configuration);
2131         } catch (Error E) {
2132             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
2133             throw E;
2134         }
2135     }
2136 
2137     /**
2138      * Equivalent to getConfig().getTransactional() but cheaper.
2139      */
2140     boolean isTransactional() {
2141         return databaseImpl.isTransactional();
2142     }
2143 
2144     /**
2145      * Returns the {@link com.sleepycat.je.Environment Environment} handle for
2146      * the database environment underlying the {@link
2147      * com.sleepycat.je.Database Database}.
2148      *
2149      * <p>This method may be called at any time during the life of the
2150      * application.</p>
2151      *
2152      * @return The {@link com.sleepycat.je.Environment Environment} handle
2153      * for the database environment underlying the {@link
2154      * com.sleepycat.je.Database Database}.
2155      */
2156     public Environment getEnvironment() {
2157         return envHandle;
2158     }
2159 
2160     /**
2161      * Returns a list of all {@link com.sleepycat.je.SecondaryDatabase
2162      * SecondaryDatabase} objects associated with a primary database.
2163      *
2164      * <p>If no secondaries are associated with this database, an empty list is
2165      * returned.</p>
2166      */
2167     /*
2168      * Replacement for above paragraph when SecondaryAssociation is published:
2169      * <p>If no secondaries are associated with this database, or a {@link
2170      * SecondaryAssociation} is {@link SecondaryCursor#setSecondaryAssociation
2171      * configured}, an empty list is returned.</p>
2172      */
2173     public List<SecondaryDatabase> getSecondaryDatabases() {
2174         return new ArrayList<SecondaryDatabase>(simpleAssocSecondaries);
2175     }
2176 
2177     /**
2178      * Compares two keys using either the default comparator if no BTree
2179      * comparator has been set or the BTree comparator if one has been set.
2180      *
2181      * @return -1 if entry1 compares less than entry2,
2182      *          0 if entry1 compares equal to entry2,
2183      *          1 if entry1 compares greater than entry2
2184      *
2185      * @throws IllegalStateException if the database has been closed.
2186      *
2187      * @throws IllegalArgumentException if either entry is a partial
2188      * DatabaseEntry, or is null.
2189      */
2190     public int compareKeys(final DatabaseEntry entry1,
2191                            final DatabaseEntry entry2) {
2192         return doCompareKeys(entry1, entry2, false/*duplicates*/);
2193     }
2194 
2195     /**
2196      * Compares two data elements using either the default comparator if no
2197      * duplicate comparator has been set or the duplicate comparator if one has
2198      * been set.
2199      *
2200      * @return -1 if entry1 compares less than entry2,
2201      *          0 if entry1 compares equal to entry2,
2202      *          1 if entry1 compares greater than entry2
2203      *
2204      * @throws IllegalStateException if the database has been closed.
2205      *
2206      * @throws IllegalArgumentException if either entry is a partial
2207      * DatabaseEntry, or is null.
2208      */
2209     public int compareDuplicates(final DatabaseEntry entry1,
2210                                  final DatabaseEntry entry2) {
2211         return doCompareKeys(entry1, entry2, true/*duplicates*/);
2212     }
2213 
2214     private int doCompareKeys(final DatabaseEntry entry1,
2215                               final DatabaseEntry entry2,
2216                               final boolean duplicates) {
2217         try {
2218             checkEnv();
2219             checkOpen("Can't compare keys/duplicates");
2220             DatabaseUtil.checkForNullDbt(entry1, "entry1", true);
2221             DatabaseUtil.checkForNullDbt(entry2, "entry2", true);
2222             DatabaseUtil.checkForPartialKey(entry1);
2223             DatabaseUtil.checkForPartialKey(entry2);
2224             return databaseImpl.compareEntries(entry1, entry2, duplicates);
2225         } catch (Error E) {
2226             DbInternal.getEnvironmentImpl(envHandle).invalidate(E);
2227             throw E;
2228         }
2229     }
2230 
2231     /*
2232      * Helpers, not part of the public API
2233      */
2234 
2235     /**
2236      * Returns true if the Database was opened read/write.
2237      *
2238      * @return true if the Database was opened read/write.
2239      */
2240     boolean isWritable() {
2241         return isWritable;
2242     }
2243 
2244     /**
2245      * Returns the databaseImpl object instance.
2246      */
2247     DatabaseImpl getDatabaseImpl() {
2248         return databaseImpl;
2249     }
2250 
2251     /**
2252      * Called during database open to set the handleLocker field.
2253      * @see HandleLocker
2254      */
2255     HandleLocker initHandleLocker(EnvironmentImpl envImpl,
2256                                   Locker openDbLocker) {
2257         handleLocker = HandleLocker.createHandleLocker(envImpl, openDbLocker);
2258         return handleLocker;
2259     }
2260 
2261     @SuppressWarnings("unused")
2262     void removeCursor(final ForwardCursor ignore)
2263         throws DatabaseException {
2264 
2265         /*
2266          * Do not call checkOpen if the handle was preempted, to allow closing
2267          * a cursor after an operation failure.  [#17015]
2268          */
2269         if (state != DbState.PREEMPTED) {
2270             checkOpen("Database was closed while still in use:");
2271         }
2272         openCursors.getAndDecrement();
2273     }
2274 
2275     @SuppressWarnings("unused")
2276     void addCursor(final ForwardCursor ignore)
2277         throws DatabaseException {
2278 
2279         checkOpen("Database was closed while still in use:");
2280         openCursors.getAndIncrement();
2281     }
2282 
2283     void checkOpen(final String msg) {
2284         switch (state) {
2285         case OPEN:
2286             break;
2287         case CLOSED:
2288             throw new IllegalStateException(msg + " Database was closed.");
2289         case INVALID:
2290             throw new IllegalStateException(
2291                 msg +
2292                 " The Transaction used to open the Database was aborted.");
2293         case PREEMPTED:
2294             throw preemptedCause.wrapSelf(msg);
2295         default:
2296             assert false : state;
2297         }
2298     }
2299 
2300     /**
2301      * @throws EnvironmentFailureException if the underlying environment is
2302      * invalid
2303      */
2304     void checkEnv()
2305         throws EnvironmentFailureException {
2306 
2307         envHandle.checkHandleIsValid();
2308         envHandle.checkEnv();
2309     }
2310 
2311     void checkLockModeWithoutTxn(final Transaction userTxn,
2312                                  final LockMode lockMode) {
2313         if (userTxn == null && LockMode.RMW.equals(lockMode)) {
2314             throw new IllegalArgumentException(
2315                 lockMode + " is meaningless and can not be specified " +
2316                 "when a null (autocommit) transaction is used. There " +
2317                 "will never be a follow on operation which will promote " +
2318                 "the lock to WRITE.");
2319         }
2320     }
2321 
2322     /**
2323      * Sends trace messages to the java.util.logger. Don't rely on the logger
2324      * alone to conditionalize whether we send this message, we don't even want
2325      * to construct the message if the level is not enabled.
2326      */
2327     void trace(final Level level,
2328                final String methodName,
2329                final Transaction txn,
2330                final DatabaseEntry key,
2331                final DatabaseEntry data,
2332                final LockMode lockMode)
2333         throws DatabaseException {
2334 
2335         if (logger.isLoggable(level)) {
2336             StringBuilder sb = new StringBuilder();
2337             sb.append(methodName);
2338             if (txn != null) {
2339                 sb.append(" txnId=").append(txn.getId());
2340             }
2341             sb.append(" key=").append(key.dumpData());
2342             if (data != null) {
2343                 sb.append(" data=").append(data.dumpData());
2344             }
2345             if (lockMode != null) {
2346                 sb.append(" lockMode=").append(lockMode);
2347             }
2348             LoggerUtils.logMsg(
2349                 logger, envHandle.getEnvironmentImpl(), level, sb.toString());
2350         }
2351     }
2352 
2353     /**
2354      * Sends trace messages to the java.util.logger. Don't rely on the logger
2355      * alone to conditionalize whether we send this message, we don't even want
2356      * to construct the message if the level is not enabled.
2357      */
2358     void trace(final Level level,
2359                final String methodName,
2360                final Transaction txn,
2361                final Object config)
2362         throws DatabaseException {
2363 
2364         if (logger.isLoggable(level)) {
2365             StringBuilder sb = new StringBuilder();
2366             sb.append(methodName);
2367             sb.append(" name=" + getDebugName());
2368             if (txn != null) {
2369                 sb.append(" txnId=").append(txn.getId());
2370             }
2371             if (config != null) {
2372                 sb.append(" config=").append(config);
2373             }
2374             LoggerUtils.logMsg(
2375                 logger, envHandle.getEnvironmentImpl(), level, sb.toString());
2376         }
2377     }
2378 
2379     boolean hasSecondaryOrForeignKeyAssociations() {
2380         return (!secAssoc.isEmpty() || !foreignKeySecondaries.isEmpty());
2381     }
2382 
2383     /**
2384      * Creates a SecondaryIntegrityException using the information given.
2385      *
2386      * This method is in the Database class, rather than in SecondaryDatabase,
2387      * to support joins with plain Cursors [#21258].
2388      */
2389     SecondaryIntegrityException
2390         secondaryRefersToMissingPrimaryKey(final Locker locker,
2391                                            final DatabaseEntry secKey,
2392                                            final DatabaseEntry priKey)
2393         throws DatabaseException {
2394 
2395         return new SecondaryIntegrityException(
2396             locker,
2397             "Secondary refers to a missing key in the primary database",
2398             getDebugName(), secKey, priKey);
2399     }
2400 }
2401