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.test;
9 
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertTrue;
12 import static org.junit.Assert.fail;
13 
14 import java.io.File;
15 import java.util.ArrayList;
16 import java.util.List;
17 
18 import org.junit.After;
19 import org.junit.Test;
20 import org.junit.runner.RunWith;
21 import org.junit.runners.Parameterized;
22 import org.junit.runners.Parameterized.Parameters;
23 
24 import com.sleepycat.bind.tuple.IntegerBinding;
25 import com.sleepycat.je.Cursor;
26 import com.sleepycat.je.CursorConfig;
27 import com.sleepycat.je.Database;
28 import com.sleepycat.je.DatabaseConfig;
29 import com.sleepycat.je.DatabaseEntry;
30 import com.sleepycat.je.DatabaseException;
31 import com.sleepycat.je.Durability;
32 import com.sleepycat.je.Environment;
33 import com.sleepycat.je.EnvironmentConfig;
34 import com.sleepycat.je.EnvironmentStats;
35 import com.sleepycat.je.LockConflictException;
36 import com.sleepycat.je.LockMode;
37 import com.sleepycat.je.OperationStatus;
38 import com.sleepycat.je.StatsConfig;
39 import com.sleepycat.je.Transaction;
40 import com.sleepycat.je.TransactionConfig;
41 import com.sleepycat.je.config.EnvironmentParams;
42 import com.sleepycat.je.junit.JUnitThread;
43 import com.sleepycat.je.util.DualTestCase;
44 import com.sleepycat.je.util.TestUtils;
45 import com.sleepycat.util.test.SharedTestUtils;
46 
47 /**
48  * Tests phantom prevention (range locking) added in SR [#10477].
49  *
50  * <p>We test that with a serializable txn, range locking will prevent phantoms
51  * from appearing.  We also test that phantoms *do* appear for non-serializable
52  * isolation levels.  These include read-uncommitted, read-committed and
53  * repeatable-read now.</p>
54  *
55  * <p>Test method names have the suffix _Sucess or _NotFound depending on
56  * whether they're testing a read operation with a SUCCESS or NOTFOUND outcome.
57  * If they're testing duplicates, the _Dup suffix is also added.  Finally, a
58  * suffix is added for the isolation level at run time.</p>
59  *
60  * <p>All tests are for the case where the reader txn locks a range and then
61  * the writer txn tries to insert into the locked range.  The reverse (where
62  * the writer inserts first) works without range locking because the reader
63  * will block on the inserted key, so we don't test that here.</p>
64  *
65  * <p>We test all read operations with and without duplicates (with duplicates
66  * the test name has _Dup appended) except for the following cases which are
67  * meaningless without duplicates because get{Next,Prev}Dup always return
68  * NOTFOUND when duplicates are not configured:
69  * testGetNextDup_Success, testGetNextDup_NotFound,
70  * testGetPrevDup_Success, testGetPrevDup_NotFound.</p>
71  */
72 @RunWith(Parameterized.class)
73 public class PhantomTest extends DualTestCase {
74 
75     private static final TransactionConfig READ_UNCOMMITTED_CONFIG
76                                            = new TransactionConfig();
77     private static final TransactionConfig READ_COMMITTED_CONFIG
78                                            = new TransactionConfig();
79     private static final TransactionConfig REPEATABLE_READ_CONFIG
80                                            = new TransactionConfig();
81     private static final TransactionConfig SERIALIZABLE_CONFIG
82                                            = new TransactionConfig();
83     static {
84         READ_UNCOMMITTED_CONFIG.setReadUncommitted(true);
85         READ_COMMITTED_CONFIG.setReadCommitted(true);
86         SERIALIZABLE_CONFIG.setSerializableIsolation(true);
87     }
88     private static final TransactionConfig[] TXN_CONFIGS = {
89         READ_UNCOMMITTED_CONFIG,
90         READ_COMMITTED_CONFIG,
91         REPEATABLE_READ_CONFIG,
92         SERIALIZABLE_CONFIG,
93     };
94 
95     private static final String DB_NAME = "PhantomTest";
96 
97     private static final int MAX_INSERT_MILLIS = 5000;
98 
99     private File envHome;
100     private Environment env;
101     private Database db;
102     private final TransactionConfig txnConfig;
103     private JUnitThread writerThread;
104     private final boolean txnSerializable;
105     private boolean dups;
106     private boolean insertFinished;
107 
108     @Parameters
genParams()109     public static List<Object[]> genParams() {
110         List<Object[]> list = new ArrayList<Object[]>();
111 
112         for (TransactionConfig txnConfig : TXN_CONFIGS)
113             list.add(new Object[]{txnConfig});
114 
115         return list;
116      }
117 
PhantomTest(TransactionConfig txnConfig)118     public PhantomTest(TransactionConfig txnConfig) {
119         envHome = SharedTestUtils.getTestDir();
120         this.txnConfig = txnConfig;
121         txnSerializable = (txnConfig == SERIALIZABLE_CONFIG);
122         String txnType;
123         if (txnConfig == SERIALIZABLE_CONFIG) {
124             txnType = "-Serializable";
125         } else if (txnConfig == REPEATABLE_READ_CONFIG) {
126             txnType = "-RepeatableRead";
127         } else if (txnConfig == READ_COMMITTED_CONFIG) {
128             txnType = "-ReadCommitted";
129         } else if (txnConfig == READ_UNCOMMITTED_CONFIG) {
130             txnType = "-ReadUncommitted";
131         } else {
132             throw new IllegalStateException();
133         }
134         customName = txnType;
135     }
136 
137     @After
tearDown()138     public void tearDown()
139         throws Exception {
140 
141         super.tearDown();
142         envHome = null;
143         env = null;
144         db = null;
145 
146         if (writerThread != null) {
147             writerThread.shutdown();
148             writerThread = null;
149         }
150     }
151 
152     /**
153      * Opens the environment and database.
154      */
openEnv(boolean dups)155     private void openEnv(boolean dups)
156         throws DatabaseException {
157 
158         openEnv(dups, null);
159     }
160 
161     /**
162      * Opens the environment and database.
163      */
openEnv(boolean dups, EnvironmentConfig envConfig)164     private void openEnv(boolean dups, EnvironmentConfig envConfig)
165         throws DatabaseException {
166 
167         this.dups = dups;
168         if (envConfig == null) {
169             envConfig = TestUtils.initEnvConfig();
170             /* Control over isolation level is required by this test. */
171             TestUtils.clearIsolationLevel(envConfig);
172         }
173 
174         /* Disable the daemons so the don't interfere with stats. */
175         envConfig.setConfigParam
176             (EnvironmentParams.ENV_RUN_EVICTOR.getName(), "false");
177         envConfig.setConfigParam
178             (EnvironmentParams.ENV_RUN_CLEANER.getName(), "false");
179         envConfig.setConfigParam
180             (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), "false");
181         envConfig.setConfigParam
182             (EnvironmentParams.ENV_RUN_INCOMPRESSOR.getName(), "false");
183 
184         envConfig.setAllowCreate(true);
185         envConfig.setTransactional(true);
186         env = create(envHome, envConfig);
187 
188         DatabaseConfig dbConfig = new DatabaseConfig();
189         dbConfig.setAllowCreate(true);
190         dbConfig.setTransactional(true);
191         dbConfig.setSortedDuplicates(dups);
192         db = env.openDatabase(null, DB_NAME, dbConfig);
193     }
194 
195     /**
196      * Closes the environment and database.
197      */
closeEnv()198     private void closeEnv()
199         throws DatabaseException {
200 
201         if (db != null) {
202             db.close();
203             db = null;
204         }
205         if (env != null) {
206             close(env);
207             env = null;
208         }
209     }
210 
211     @Test
testGetSearchKey_Success()212     public void testGetSearchKey_Success()
213         throws DatabaseException {
214 
215         openEnv(false);
216 
217         /* Insert key 2. */
218         insert(2);
219 
220         /* getSearchKey returns key 2. */
221         Transaction readerTxn = env.beginTransaction(null, txnConfig);
222         Cursor cursor = db.openCursor(readerTxn, null);
223         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
224 
225         /* Insertions are never blocked. */
226         try {
227             insert(1);
228             insert(3);
229         } catch (LockConflictException e) {
230             fail();
231         }
232 
233         cursor.close();
234         readerTxn.commit(Durability.COMMIT_NO_SYNC);
235         closeEnv();
236     }
237 
238     @Test
testGetSearchKey_Success_Dup()239     public void testGetSearchKey_Success_Dup()
240         throws DatabaseException, InterruptedException {
241 
242         openEnv(true);
243 
244         /* Insert dups. */
245         insert(1, 2);
246         insert(1, 3);
247 
248         /* getSearchKey returns key {1,2}. */
249         Transaction readerTxn = env.beginTransaction(null, txnConfig);
250         Cursor cursor = db.openCursor(readerTxn, null);
251         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 2));
252 
253         /* Insertions after {1, 2} are never blocked. */
254         try {
255             insert(1, 4);
256         } catch (LockConflictException e) {
257             fail();
258         }
259 
260         /* Insert {1,1} in a writer thread. */
261         startInsert(1, 1);
262 
263         /*
264          * If serializable, getSearchKey should return {1,2} again, otherwise
265          * getSearchKey should see {1,1}.
266          */
267         if (txnSerializable) {
268             assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 2));
269         } else {
270             assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 1));
271         }
272 
273         /* Close reader to allow writer to finish. */
274         cursor.close();
275         readerTxn.commit(Durability.COMMIT_NO_SYNC);
276         waitForInsert();
277 
278         /* getSearchKey returns {1,1}. */
279         readerTxn = env.beginTransaction(null, txnConfig);
280         cursor = db.openCursor(readerTxn, null);
281         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 1));
282         cursor.close();
283         readerTxn.commit();
284 
285         closeEnv();
286     }
287 
288     @Test
testGetSearchKey_NotFound()289     public void testGetSearchKey_NotFound()
290         throws DatabaseException, InterruptedException {
291 
292         openEnv(false);
293 
294         /* Insert key 1. */
295         insert(1);
296 
297         /* getSearchKey for key 2 returns NOTFOUND. */
298         Transaction readerTxn = env.beginTransaction(null, txnConfig);
299         Cursor cursor = db.openCursor(readerTxn, null);
300         assertEquals(OperationStatus.NOTFOUND, searchKey(cursor, 2));
301 
302         /* Insertions before 2 are never blocked. */
303         try {
304             insert(0);
305         } catch (LockConflictException e) {
306             fail();
307         }
308 
309         /* Insert key 2 in a writer thread. */
310         startInsert(2);
311 
312         /*
313          * If serializable, getSearchKey should return NOTFOUND again;
314          * otherwise getSearchKey should see key 2.
315          */
316         if (txnSerializable) {
317             assertEquals(OperationStatus.NOTFOUND, searchKey(cursor, 2));
318         } else {
319             assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
320         }
321 
322         /* Close reader to allow writer to finish. */
323         cursor.close();
324         readerTxn.commit(Durability.COMMIT_NO_SYNC);
325         waitForInsert();
326 
327         /* getSearchKey returns key 2. */
328         readerTxn = env.beginTransaction(null, txnConfig);
329         cursor = db.openCursor(readerTxn, null);
330         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
331         cursor.close();
332         readerTxn.commit();
333 
334         closeEnv();
335     }
336 
337     @Test
testGetSearchKey_NotFound_Dup()338     public void testGetSearchKey_NotFound_Dup()
339         throws DatabaseException, InterruptedException {
340 
341         openEnv(true);
342 
343         /* Insert dups. */
344         insert(2, 1);
345         insert(2, 2);
346 
347         /* getSearchKey for {1,1} returns NOTFOUND. */
348         Transaction readerTxn = env.beginTransaction(null, txnConfig);
349         Cursor cursor = db.openCursor(readerTxn, null);
350         assertEquals(OperationStatus.NOTFOUND, searchKey(cursor, 1, 1));
351 
352         /* Insertions after {2,2} are never blocked. */
353         try {
354             insert(2, 3);
355             insert(3, 0);
356         } catch (LockConflictException e) {
357             fail();
358         }
359 
360         /* Insert {1,1} in a writer thread. */
361         startInsert(1, 1);
362 
363         /*
364          * If serializable, getSearchKey should return NOTFOUND again;
365          * otherwise getSearchKey should see {1,1}.
366          */
367         if (txnSerializable) {
368             assertEquals(OperationStatus.NOTFOUND, searchKey(cursor, 1, 1));
369         } else {
370             assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 1));
371         }
372 
373         /* Close reader to allow writer to finish. */
374         cursor.close();
375         readerTxn.commit(Durability.COMMIT_NO_SYNC);
376         waitForInsert();
377 
378         /* getSearchKey returns {1,1}. */
379         readerTxn = env.beginTransaction(null, txnConfig);
380         cursor = db.openCursor(readerTxn, null);
381         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 1));
382         cursor.close();
383         readerTxn.commit();
384 
385         closeEnv();
386     }
387 
388     @Test
testGetSearchBoth_Success()389     public void testGetSearchBoth_Success()
390         throws DatabaseException {
391 
392         doTestGetSearchBoth_Success(false /*useRangeSearch*/);
393     }
394 
395     /**
396      * In a non-duplicates DB, getSearchBoth and getSearchBothRange are
397      * equivalent.
398      */
doTestGetSearchBoth_Success(boolean useRangeSearch)399     private void doTestGetSearchBoth_Success(boolean useRangeSearch)
400         throws DatabaseException {
401 
402         openEnv(false);
403 
404         /* Insert key 2. */
405         insert(2);
406 
407         /* getSearchBoth[Range] returns {2,0}. */
408         Transaction readerTxn = env.beginTransaction(null, txnConfig);
409         Cursor cursor = db.openCursor(readerTxn, null);
410         assertEquals(OperationStatus.SUCCESS,
411                      searchBoth(cursor, 2, 0, useRangeSearch));
412 
413         /* Insertions are never blocked. */
414         try {
415             insert(1);
416             insert(3);
417         } catch (LockConflictException e) {
418             fail();
419         }
420 
421         cursor.close();
422         readerTxn.commit(Durability.COMMIT_NO_SYNC);
423         closeEnv();
424     }
425 
426     @Test
testGetSearchBoth_Success_Dup()427     public void testGetSearchBoth_Success_Dup()
428         throws DatabaseException {
429 
430         openEnv(true);
431 
432         /* Insert dups. */
433         insert(1, 1);
434         insert(1, 3);
435 
436         /* getSearchBoth returns key {1,3}. */
437         Transaction readerTxn = env.beginTransaction(null, txnConfig);
438         Cursor cursor = db.openCursor(readerTxn, null);
439         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
440 
441         /* Insertions are never blocked. */
442         try {
443             insert(0, 0);
444             insert(1, 0);
445             insert(1, 2);
446             insert(1, 4);
447             insert(2, 0);
448         } catch (LockConflictException e) {
449             fail();
450         }
451 
452         cursor.close();
453         readerTxn.commit(Durability.COMMIT_NO_SYNC);
454         closeEnv();
455     }
456 
457     @Test
testGetSearchBoth_NotFound()458     public void testGetSearchBoth_NotFound()
459         throws DatabaseException, InterruptedException {
460 
461         doTestGetSearchBoth_NotFound(false /*useRangeSearch*/);
462     }
463 
464     /**
465      * In a non-duplicates DB, getSearchBoth and getSearchBothRange are
466      * equivalent.
467      */
doTestGetSearchBoth_NotFound(boolean useRangeSearch)468     private void doTestGetSearchBoth_NotFound(boolean useRangeSearch)
469         throws DatabaseException, InterruptedException {
470 
471         openEnv(false);
472 
473         /* Insert key 1. */
474         insert(1);
475 
476         /* getSearchBoth for key 2 returns NOTFOUND. */
477         Transaction readerTxn = env.beginTransaction(null, txnConfig);
478         Cursor cursor = db.openCursor(readerTxn, null);
479         assertEquals(OperationStatus.NOTFOUND,
480                      searchBoth(cursor, 2, useRangeSearch));
481 
482         /* Insertions before 2 are never blocked. */
483         try {
484             insert(0);
485         } catch (LockConflictException e) {
486             fail();
487         }
488 
489         /* Insert key 2 in a writer thread. */
490         startInsert(2);
491 
492         /*
493          * If serializable, getSearchBoth should return NOTFOUND again;
494          * otherwise getSearchBoth should see key 2.
495          */
496         if (txnSerializable) {
497             assertEquals(OperationStatus.NOTFOUND,
498                          searchBoth(cursor, 2, useRangeSearch));
499         } else {
500             assertEquals(OperationStatus.SUCCESS,
501                          searchBoth(cursor, 2, useRangeSearch));
502         }
503 
504         /* Close reader to allow writer to finish. */
505         cursor.close();
506         readerTxn.commit(Durability.COMMIT_NO_SYNC);
507         waitForInsert();
508 
509         /* getSearchBoth returns key 2. */
510         readerTxn = env.beginTransaction(null, txnConfig);
511         cursor = db.openCursor(readerTxn, null);
512         assertEquals(OperationStatus.SUCCESS,
513                      searchBoth(cursor, 2, useRangeSearch));
514         cursor.close();
515         readerTxn.commit();
516 
517         closeEnv();
518     }
519 
520     @Test
testGetSearchBoth_NotFound_Dup()521     public void testGetSearchBoth_NotFound_Dup()
522         throws DatabaseException, InterruptedException {
523 
524         openEnv(true);
525 
526         /* Insert dups. */
527         insert(1, 1);
528         insert(1, 3);
529 
530         /* getSearchBoth for {1,2} returns NOTFOUND. */
531         Transaction readerTxn = env.beginTransaction(null, txnConfig);
532         Cursor cursor = db.openCursor(readerTxn, null);
533         assertEquals(OperationStatus.NOTFOUND, searchBoth(cursor, 1, 2));
534 
535         /* Insertions before {1,2} or after {1,3} are never blocked. */
536         try {
537             insert(1, 0);
538             insert(0, 0);
539             insert(1, 4);
540             insert(2, 0);
541         } catch (LockConflictException e) {
542             fail();
543         }
544 
545         /* Insert {1,2} in a writer thread. */
546         startInsert(1, 2);
547 
548         /*
549          * If serializable, getSearchBoth should return NOTFOUND again;
550          * otherwise getSearchBoth should see {1,2}.
551          */
552         if (txnSerializable) {
553             assertEquals(OperationStatus.NOTFOUND, searchBoth(cursor, 1, 2));
554         } else {
555             assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
556         }
557 
558         /* Close reader to allow writer to finish. */
559         cursor.close();
560         readerTxn.commit(Durability.COMMIT_NO_SYNC);
561         waitForInsert();
562 
563         /* getSearchBoth returns {1,2}. */
564         readerTxn = env.beginTransaction(null, txnConfig);
565         cursor = db.openCursor(readerTxn, null);
566         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
567         cursor.close();
568         readerTxn.commit();
569 
570         closeEnv();
571     }
572 
573     @Test
testGetSearchKeyRange_Success()574     public void testGetSearchKeyRange_Success()
575         throws DatabaseException, InterruptedException {
576 
577         openEnv(false);
578         DatabaseEntry key = new DatabaseEntry();
579         DatabaseEntry data = new DatabaseEntry();
580         OperationStatus status;
581 
582         /* Insert key 1 and 3. */
583         insert(1);
584         insert(3);
585 
586         /* getSearchKeyRange for key 2 returns key 3. */
587         Transaction readerTxn = env.beginTransaction(null, txnConfig);
588         Cursor cursor = db.openCursor(readerTxn, null);
589         IntegerBinding.intToEntry(2, key);
590         status = cursor.getSearchKeyRange(key, data, null);
591         assertEquals(OperationStatus.SUCCESS, status);
592         assertEquals(3, IntegerBinding.entryToInt(key));
593 
594         /* Insertions before 2 and after 3 are never blocked. */
595         try {
596             insert(0);
597             insert(4);
598         } catch (LockConflictException e) {
599             fail();
600         }
601 
602         /* Insert key 2 in a writer thread. */
603         startInsert(2);
604 
605         /*
606          * If serializable, getSearchKeyRange should return key 3 again;
607          * otherwise getSearchKeyRange should see key 2.
608          */
609         IntegerBinding.intToEntry(2, key);
610         status = cursor.getSearchKeyRange(key, data, null);
611         assertEquals(OperationStatus.SUCCESS, status);
612         if (txnSerializable) {
613             assertEquals(3, IntegerBinding.entryToInt(key));
614         } else {
615             assertEquals(2, IntegerBinding.entryToInt(key));
616         }
617 
618         /* Close reader to allow writer to finish. */
619         cursor.close();
620         readerTxn.commit(Durability.COMMIT_NO_SYNC);
621         waitForInsert();
622 
623         /* getSearchKeyRange returns key 2. */
624         readerTxn = env.beginTransaction(null, txnConfig);
625         cursor = db.openCursor(readerTxn, null);
626         IntegerBinding.intToEntry(2, key);
627         status = cursor.getSearchKeyRange(key, data, null);
628         assertEquals(OperationStatus.SUCCESS, status);
629         assertEquals(2, IntegerBinding.entryToInt(key));
630         cursor.close();
631         readerTxn.commit();
632 
633         closeEnv();
634     }
635 
636     @Test
testGetSearchKeyRange_Success_Dup()637     public void testGetSearchKeyRange_Success_Dup()
638         throws DatabaseException, InterruptedException {
639 
640         openEnv(true);
641         DatabaseEntry key = new DatabaseEntry();
642         DatabaseEntry data = new DatabaseEntry();
643         OperationStatus status;
644 
645         /* Insert dups. */
646         insert(1, 1);
647         insert(1, 2);
648         insert(3, 2);
649         insert(3, 3);
650 
651         /* getSearchKeyRange for key 2 returns {3,2}. */
652         Transaction readerTxn = env.beginTransaction(null, txnConfig);
653         Cursor cursor = db.openCursor(readerTxn, null);
654         IntegerBinding.intToEntry(2, key);
655         status = cursor.getSearchKeyRange(key, data, null);
656         assertEquals(3, IntegerBinding.entryToInt(key));
657         assertEquals(2, IntegerBinding.entryToInt(data));
658         assertEquals(OperationStatus.SUCCESS, status);
659 
660         /* Insertions before 2 and after {3,3} are never blocked. */
661         try {
662             insert(1, 0);
663             insert(0, 0);
664             insert(3, 4);
665             insert(4, 0);
666         } catch (LockConflictException e) {
667             fail();
668         }
669 
670         /* Insert {3,1} in a writer thread. */
671         startInsert(3, 1);
672 
673         /*
674          * If serializable, getSearchKeyRange should return {3,2} again;
675          * otherwise getSearchKeyRange should see {3,1}.
676          */
677         IntegerBinding.intToEntry(2, key);
678         status = cursor.getSearchKeyRange(key, data, null);
679         assertEquals(OperationStatus.SUCCESS, status);
680         if (txnSerializable) {
681             assertEquals(3, IntegerBinding.entryToInt(key));
682             assertEquals(2, IntegerBinding.entryToInt(data));
683         } else {
684             assertEquals(3, IntegerBinding.entryToInt(key));
685             assertEquals(1, IntegerBinding.entryToInt(data));
686         }
687 
688         /* Close reader to allow writer to finish. */
689         cursor.close();
690         readerTxn.commit(Durability.COMMIT_NO_SYNC);
691         waitForInsert();
692 
693         /* getSearchKeyRange returns {3,1}. */
694         readerTxn = env.beginTransaction(null, txnConfig);
695         cursor = db.openCursor(readerTxn, null);
696         IntegerBinding.intToEntry(2, key);
697         status = cursor.getSearchKeyRange(key, data, null);
698         assertEquals(OperationStatus.SUCCESS, status);
699         assertEquals(3, IntegerBinding.entryToInt(key));
700         assertEquals(1, IntegerBinding.entryToInt(data));
701         cursor.close();
702         readerTxn.commit();
703 
704         closeEnv();
705     }
706 
707     @Test
testGetSearchKeyRange_NotFound()708     public void testGetSearchKeyRange_NotFound()
709         throws DatabaseException, InterruptedException {
710 
711         openEnv(false);
712         DatabaseEntry key = new DatabaseEntry();
713         DatabaseEntry data = new DatabaseEntry();
714         OperationStatus status;
715 
716         /* Insert key 1. */
717         insert(1);
718 
719         /* getSearchKeyRange for key 2 returns NOTFOUND. */
720         Transaction readerTxn = env.beginTransaction(null, txnConfig);
721         Cursor cursor = db.openCursor(readerTxn, null);
722         IntegerBinding.intToEntry(2, key);
723         status = cursor.getSearchKeyRange(key, data, null);
724         assertEquals(OperationStatus.NOTFOUND, status);
725 
726         /* Insertions before 2 are never blocked. */
727         try {
728             insert(0);
729         } catch (LockConflictException e) {
730             fail();
731         }
732 
733         /* Insert key 3 in a writer thread. */
734         startInsert(3);
735 
736         /*
737          * If serializable, getSearchKeyRange should return NOTFOUND again;
738          * otherwise getSearchKeyRange should see key 3.
739          */
740         IntegerBinding.intToEntry(2, key);
741         status = cursor.getSearchKeyRange(key, data, null);
742         if (txnSerializable) {
743             assertEquals(OperationStatus.NOTFOUND, status);
744         } else {
745             assertEquals(OperationStatus.SUCCESS, status);
746             assertEquals(3, IntegerBinding.entryToInt(key));
747         }
748 
749         /* Close reader to allow writer to finish. */
750         cursor.close();
751         readerTxn.commit(Durability.COMMIT_NO_SYNC);
752         waitForInsert();
753 
754         /* getSearchKeyRange returns key 3. */
755         readerTxn = env.beginTransaction(null, txnConfig);
756         cursor = db.openCursor(readerTxn, null);
757         IntegerBinding.intToEntry(2, key);
758         status = cursor.getSearchKeyRange(key, data, null);
759         assertEquals(OperationStatus.SUCCESS, status);
760         assertEquals(3, IntegerBinding.entryToInt(key));
761         cursor.close();
762         readerTxn.commit();
763 
764         closeEnv();
765     }
766 
767     @Test
testGetSearchKeyRange_NotFound_Dup()768     public void testGetSearchKeyRange_NotFound_Dup()
769         throws DatabaseException, InterruptedException {
770 
771         openEnv(true);
772         DatabaseEntry key = new DatabaseEntry();
773         DatabaseEntry data = new DatabaseEntry();
774         OperationStatus status;
775 
776         /* Insert dups. */
777         insert(1, 1);
778         insert(1, 2);
779 
780         /* getSearchKeyRange for key 2 returns NOTFOUND. */
781         Transaction readerTxn = env.beginTransaction(null, txnConfig);
782         Cursor cursor = db.openCursor(readerTxn, null);
783         IntegerBinding.intToEntry(2, key);
784         status = cursor.getSearchKeyRange(key, data, null);
785         assertEquals(OperationStatus.NOTFOUND, status);
786 
787         /* Insertions before 2 are never blocked. */
788         try {
789             insert(1, 0);
790             insert(0, 0);
791         } catch (LockConflictException e) {
792             fail();
793         }
794 
795         /* Insert {3,1} in a writer thread. */
796         startInsert(3, 1);
797 
798         /*
799          * If serializable, getSearchKeyRange should return NOTFOUND again;
800          * otherwise getSearchKeyRange should see {3,1}.
801          */
802         IntegerBinding.intToEntry(2, key);
803         status = cursor.getSearchKeyRange(key, data, null);
804         if (txnSerializable) {
805             assertEquals(OperationStatus.NOTFOUND, status);
806         } else {
807             assertEquals(OperationStatus.SUCCESS, status);
808             assertEquals(3, IntegerBinding.entryToInt(key));
809             assertEquals(1, IntegerBinding.entryToInt(data));
810         }
811 
812         /* Close reader to allow writer to finish. */
813         cursor.close();
814         readerTxn.commit(Durability.COMMIT_NO_SYNC);
815         waitForInsert();
816 
817         /* getSearchKeyRange returns {3,1}. */
818         readerTxn = env.beginTransaction(null, txnConfig);
819         cursor = db.openCursor(readerTxn, null);
820         IntegerBinding.intToEntry(2, key);
821         status = cursor.getSearchKeyRange(key, data, null);
822         assertEquals(OperationStatus.SUCCESS, status);
823         assertEquals(3, IntegerBinding.entryToInt(key));
824         assertEquals(1, IntegerBinding.entryToInt(data));
825         cursor.close();
826         readerTxn.commit();
827 
828         closeEnv();
829     }
830 
831     /*
832      * A testGetSearchBothRange_Success test case is not possible because it is
833      * not possible to insert a duplicate when only one LN for the key already
834      * exists, without locking the existing LN.  Therefore, the insert thread
835      * will deadlock with the reader thread, which has the existing LN locked.
836      * This is a testing anomoly, not a bug.
837      */
838 
839     @Test
testGetSearchBothRange_Success_Dup()840     public void testGetSearchBothRange_Success_Dup()
841         throws DatabaseException, InterruptedException {
842 
843         openEnv(true);
844         DatabaseEntry key = new DatabaseEntry();
845         DatabaseEntry data = new DatabaseEntry();
846         OperationStatus status;
847 
848         /* Insert dups. */
849         insert(1, 1);
850         insert(1, 2);
851         insert(3, 2);
852         insert(3, 3);
853 
854         /* getSearchBothRange for {3, 0} returns {3,2}. */
855         Transaction readerTxn = env.beginTransaction(null, txnConfig);
856         Cursor cursor = db.openCursor(readerTxn, null);
857         IntegerBinding.intToEntry(3, key);
858         IntegerBinding.intToEntry(0, data);
859         status = cursor.getSearchBothRange(key, data, null);
860         assertEquals(OperationStatus.SUCCESS, status);
861         assertEquals(3, IntegerBinding.entryToInt(key));
862         assertEquals(2, IntegerBinding.entryToInt(data));
863 
864         /* Insertions before {1,1} and after {3,2} are never blocked. */
865         try {
866             insert(1, 0);
867             insert(0, 0);
868             insert(3, 4);
869         } catch (LockConflictException e) {
870             fail();
871         }
872 
873         /* Insert {3,1} in a writer thread. */
874         startInsert(3, 1);
875 
876         /*
877          * If serializable, getSearchBothRange should return {3,2} again;
878          * otherwise getSearchBothRange should see {3,1}.
879          */
880         IntegerBinding.intToEntry(3, key);
881         IntegerBinding.intToEntry(0, data);
882         status = cursor.getSearchBothRange(key, data, null);
883         assertEquals(OperationStatus.SUCCESS, status);
884         if (txnSerializable) {
885             assertEquals(3, IntegerBinding.entryToInt(key));
886             assertEquals(2, IntegerBinding.entryToInt(data));
887         } else {
888             assertEquals(3, IntegerBinding.entryToInt(key));
889             assertEquals(1, IntegerBinding.entryToInt(data));
890         }
891 
892         /* Close reader to allow writer to finish. */
893         cursor.close();
894         readerTxn.commit(Durability.COMMIT_NO_SYNC);
895         waitForInsert();
896 
897         /* getSearchBothRange returns {3,1}. */
898         readerTxn = env.beginTransaction(null, txnConfig);
899         cursor = db.openCursor(readerTxn, null);
900         IntegerBinding.intToEntry(3, key);
901         IntegerBinding.intToEntry(0, data);
902         status = cursor.getSearchBothRange(key, data, null);
903         assertEquals(OperationStatus.SUCCESS, status);
904         assertEquals(3, IntegerBinding.entryToInt(key));
905         assertEquals(1, IntegerBinding.entryToInt(data));
906         cursor.close();
907         readerTxn.commit();
908 
909         closeEnv();
910     }
911 
912     @Test
testGetSearchBothRange_NotFound()913     public void testGetSearchBothRange_NotFound()
914         throws DatabaseException, InterruptedException {
915 
916         doTestGetSearchBoth_NotFound(true /*useRangeSearch*/);
917     }
918 
919     @Test
testGetSearchBothRange_NotFound_Dup()920     public void testGetSearchBothRange_NotFound_Dup()
921         throws DatabaseException, InterruptedException {
922 
923         openEnv(true);
924         DatabaseEntry key = new DatabaseEntry();
925         DatabaseEntry data = new DatabaseEntry();
926         OperationStatus status;
927 
928         /* Insert dups. */
929         insert(3, 0);
930         insert(3, 1);
931 
932         /* getSearchBothRange for {3, 2} returns NOTFOUND. */
933         Transaction readerTxn = env.beginTransaction(null, txnConfig);
934         Cursor cursor = db.openCursor(readerTxn, null);
935         IntegerBinding.intToEntry(3, key);
936         IntegerBinding.intToEntry(2, data);
937         status = cursor.getSearchBothRange(key, data, null);
938         assertEquals(OperationStatus.NOTFOUND, status);
939 
940         /* Insertions before {3,0} are never blocked. */
941         try {
942             insert(3, -1);
943             insert(2, 0);
944         } catch (LockConflictException e) {
945             fail();
946         }
947 
948         /* Insert {3,3} in a writer thread. */
949         startInsert(3, 3);
950 
951         /*
952          * If serializable, getSearchBothRange should return NOTFOUND again;
953          * otherwise getSearchBothRange should see {3,3}.
954          */
955         IntegerBinding.intToEntry(3, key);
956         IntegerBinding.intToEntry(2, data);
957         status = cursor.getSearchBothRange(key, data, null);
958         if (txnSerializable) {
959             assertEquals(OperationStatus.NOTFOUND, status);
960         } else {
961             assertEquals(OperationStatus.SUCCESS, status);
962             assertEquals(3, IntegerBinding.entryToInt(key));
963             assertEquals(3, IntegerBinding.entryToInt(data));
964         }
965 
966         /* Close reader to allow writer to finish. */
967         cursor.close();
968         readerTxn.commit(Durability.COMMIT_NO_SYNC);
969         waitForInsert();
970 
971         /* getSearchBothRange returns {3,3}. */
972         readerTxn = env.beginTransaction(null, txnConfig);
973         cursor = db.openCursor(readerTxn, null);
974         IntegerBinding.intToEntry(3, key);
975         IntegerBinding.intToEntry(2, data);
976         status = cursor.getSearchBothRange(key, data, null);
977         assertEquals(OperationStatus.SUCCESS, status);
978         assertEquals(3, IntegerBinding.entryToInt(key));
979         assertEquals(3, IntegerBinding.entryToInt(data));
980         cursor.close();
981         readerTxn.commit();
982 
983         closeEnv();
984     }
985 
986     @Test
testGetFirst_Success()987     public void testGetFirst_Success()
988         throws DatabaseException, InterruptedException {
989 
990         openEnv(false);
991         DatabaseEntry key = new DatabaseEntry();
992         DatabaseEntry data = new DatabaseEntry();
993         OperationStatus status;
994 
995         /* Insert key 2. */
996         insert(2);
997 
998         /* getFirst returns key 2. */
999         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1000         Cursor cursor = db.openCursor(readerTxn, null);
1001         status = cursor.getFirst(key, data, null);
1002         assertEquals(OperationStatus.SUCCESS, status);
1003         assertEquals(2, IntegerBinding.entryToInt(key));
1004 
1005         /* Insertions after 2 are never blocked. */
1006         try {
1007             insert(3);
1008         } catch (LockConflictException e) {
1009             fail();
1010         }
1011 
1012         /* Insert key 1 in a writer thread. */
1013         startInsert(1);
1014 
1015         /*
1016          * If serializable, getFirst should return key 2 again; otherwise
1017          * getFirst should see key 1.
1018          */
1019         status = cursor.getFirst(key, data, null);
1020         assertEquals(OperationStatus.SUCCESS, status);
1021         if (txnSerializable) {
1022             assertEquals(2, IntegerBinding.entryToInt(key));
1023         } else {
1024             assertEquals(1, IntegerBinding.entryToInt(key));
1025         }
1026 
1027         /* Close reader to allow writer to finish. */
1028         cursor.close();
1029         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1030         waitForInsert();
1031 
1032         /* getFirst returns key 1. */
1033         readerTxn = env.beginTransaction(null, txnConfig);
1034         cursor = db.openCursor(readerTxn, null);
1035         status = cursor.getFirst(key, data, null);
1036         assertEquals(OperationStatus.SUCCESS, status);
1037         assertEquals(1, IntegerBinding.entryToInt(key));
1038         cursor.close();
1039         readerTxn.commit();
1040 
1041         closeEnv();
1042     }
1043 
1044     @Test
testGetFirst_Success_Dup()1045     public void testGetFirst_Success_Dup()
1046         throws DatabaseException, InterruptedException {
1047 
1048         openEnv(true);
1049         DatabaseEntry key = new DatabaseEntry();
1050         DatabaseEntry data = new DatabaseEntry();
1051         OperationStatus status;
1052 
1053         /* Insert dups. */
1054         insert(1, 2);
1055         insert(1, 3);
1056 
1057         /* getFirst returns {1,2}. */
1058         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1059         Cursor cursor = db.openCursor(readerTxn, null);
1060         status = cursor.getFirst(key, data, null);
1061         assertEquals(OperationStatus.SUCCESS, status);
1062         assertEquals(1, IntegerBinding.entryToInt(key));
1063         assertEquals(2, IntegerBinding.entryToInt(data));
1064 
1065         /* Insertions after {1,3} are never blocked. */
1066         try {
1067             insert(1, 4);
1068             insert(2, 0);
1069         } catch (LockConflictException e) {
1070             fail();
1071         }
1072 
1073         /* Insert {1,1} in a writer thread. */
1074         startInsert(1, 1);
1075 
1076         /*
1077          * If serializable, getFirst should return {1,2} again; otherwise
1078          * getFirst should see {1,1}.
1079          */
1080         status = cursor.getFirst(key, data, null);
1081         assertEquals(OperationStatus.SUCCESS, status);
1082         if (txnSerializable) {
1083             assertEquals(1, IntegerBinding.entryToInt(key));
1084             assertEquals(2, IntegerBinding.entryToInt(data));
1085         } else {
1086             assertEquals(1, IntegerBinding.entryToInt(key));
1087             assertEquals(1, IntegerBinding.entryToInt(data));
1088         }
1089 
1090         /* Close reader to allow writer to finish. */
1091         cursor.close();
1092         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1093         waitForInsert();
1094 
1095         /* getFirst returns {1,1}. */
1096         readerTxn = env.beginTransaction(null, txnConfig);
1097         cursor = db.openCursor(readerTxn, null);
1098         status = cursor.getFirst(key, data, null);
1099         assertEquals(OperationStatus.SUCCESS, status);
1100         assertEquals(1, IntegerBinding.entryToInt(key));
1101         assertEquals(1, IntegerBinding.entryToInt(data));
1102         cursor.close();
1103         readerTxn.commit();
1104 
1105         closeEnv();
1106     }
1107 
1108     @Test
testGetFirst_NotFound()1109     public void testGetFirst_NotFound()
1110         throws DatabaseException, InterruptedException {
1111 
1112         openEnv(false);
1113         DatabaseEntry key = new DatabaseEntry();
1114         DatabaseEntry data = new DatabaseEntry();
1115         OperationStatus status;
1116 
1117         /* getFirst returns NOTFOUND. */
1118         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1119         Cursor cursor = db.openCursor(readerTxn, null);
1120         status = cursor.getFirst(key, data, null);
1121         assertEquals(OperationStatus.NOTFOUND, status);
1122 
1123         /* Insert key 1 in a writer thread. */
1124         startInsert(1);
1125 
1126         /*
1127          * If serializable, getFirst should return NOTFOUND again; otherwise
1128          * getFirst should see key 1.
1129          */
1130         status = cursor.getFirst(key, data, null);
1131         if (txnSerializable) {
1132             assertEquals(OperationStatus.NOTFOUND, status);
1133         } else {
1134             assertEquals(OperationStatus.SUCCESS, status);
1135             assertEquals(1, IntegerBinding.entryToInt(key));
1136         }
1137 
1138         /* Close reader to allow writer to finish. */
1139         cursor.close();
1140         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1141         waitForInsert();
1142 
1143         /* getFirst returns key 1. */
1144         readerTxn = env.beginTransaction(null, txnConfig);
1145         cursor = db.openCursor(readerTxn, null);
1146         status = cursor.getFirst(key, data, null);
1147         assertEquals(OperationStatus.SUCCESS, status);
1148         assertEquals(1, IntegerBinding.entryToInt(key));
1149         cursor.close();
1150         readerTxn.commit();
1151 
1152         closeEnv();
1153     }
1154 
1155     @Test
testGetFirst_NotFound_Dup()1156     public void testGetFirst_NotFound_Dup()
1157         throws DatabaseException, InterruptedException {
1158 
1159         openEnv(true);
1160         DatabaseEntry key = new DatabaseEntry();
1161         DatabaseEntry data = new DatabaseEntry();
1162         OperationStatus status;
1163 
1164         /* getFirst returns NOTFOUND. */
1165         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1166         Cursor cursor = db.openCursor(readerTxn, null);
1167         status = cursor.getFirst(key, data, null);
1168         assertEquals(OperationStatus.NOTFOUND, status);
1169 
1170         /* Insert {1,1} in a writer thread. */
1171         startInsert(1, 1);
1172 
1173         /*
1174          * If serializable, getFirst should return NOTFOUND again; otherwise
1175          * getFirst should see {1,1}.
1176          */
1177         status = cursor.getFirst(key, data, null);
1178         if (txnSerializable) {
1179             assertEquals(OperationStatus.NOTFOUND, status);
1180         } else {
1181             assertEquals(OperationStatus.SUCCESS, status);
1182             assertEquals(1, IntegerBinding.entryToInt(key));
1183             assertEquals(1, IntegerBinding.entryToInt(data));
1184         }
1185 
1186         /* Close reader to allow writer to finish. */
1187         cursor.close();
1188         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1189         waitForInsert();
1190 
1191         /* getFirst returns {1,1}. */
1192         readerTxn = env.beginTransaction(null, txnConfig);
1193         cursor = db.openCursor(readerTxn, null);
1194         status = cursor.getFirst(key, data, null);
1195         assertEquals(OperationStatus.SUCCESS, status);
1196         assertEquals(1, IntegerBinding.entryToInt(key));
1197         cursor.close();
1198         readerTxn.commit();
1199 
1200         closeEnv();
1201     }
1202 
1203     @Test
testGetLast_Success()1204     public void testGetLast_Success()
1205         throws DatabaseException, InterruptedException {
1206 
1207         openEnv(false);
1208         DatabaseEntry key = new DatabaseEntry();
1209         DatabaseEntry data = new DatabaseEntry();
1210         OperationStatus status;
1211 
1212         /* Insert key 1. */
1213         insert(1);
1214 
1215         /* getLast returns key 1. */
1216         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1217         Cursor cursor = db.openCursor(readerTxn, null);
1218         status = cursor.getLast(key, data, null);
1219         assertEquals(OperationStatus.SUCCESS, status);
1220         assertEquals(1, IntegerBinding.entryToInt(key));
1221 
1222         /* Insertions before current position are never blocked. */
1223         try {
1224             insert(0);
1225         } catch (LockConflictException e) {
1226             fail();
1227         }
1228 
1229         /* Insert key 2 in a writer thread. */
1230         startInsert(2);
1231 
1232         /*
1233          * If serializable, getLast should return key 1 again; otherwise
1234          * getLast should see key 2.
1235          */
1236         status = cursor.getLast(key, data, null);
1237         assertEquals(OperationStatus.SUCCESS, status);
1238         if (txnSerializable) {
1239             assertEquals(1, IntegerBinding.entryToInt(key));
1240         } else {
1241             assertEquals(2, IntegerBinding.entryToInt(key));
1242         }
1243 
1244         /* Close reader to allow writer to finish. */
1245         cursor.close();
1246         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1247         waitForInsert();
1248 
1249         /* getLast returns key 2. */
1250         readerTxn = env.beginTransaction(null, txnConfig);
1251         cursor = db.openCursor(readerTxn, null);
1252         status = cursor.getLast(key, data, null);
1253         assertEquals(OperationStatus.SUCCESS, status);
1254         assertEquals(2, IntegerBinding.entryToInt(key));
1255         cursor.close();
1256         readerTxn.commit();
1257 
1258         closeEnv();
1259     }
1260 
1261     @Test
testGetLast_Success_Dup()1262     public void testGetLast_Success_Dup()
1263         throws DatabaseException, InterruptedException {
1264 
1265         openEnv(true);
1266         DatabaseEntry key = new DatabaseEntry();
1267         DatabaseEntry data = new DatabaseEntry();
1268         OperationStatus status;
1269 
1270         /* Insert dups. */
1271         insert(1, 0);
1272         insert(1, 2);
1273 
1274         /* getLast returns {1,2}. */
1275         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1276         Cursor cursor = db.openCursor(readerTxn, null);
1277         status = cursor.getLast(key, data, null);
1278         assertEquals(OperationStatus.SUCCESS, status);
1279         assertEquals(1, IntegerBinding.entryToInt(key));
1280         assertEquals(2, IntegerBinding.entryToInt(data));
1281 
1282         /* Insertions before current position are never blocked. */
1283         try {
1284             insert(1, 1);
1285             insert(0, 0);
1286         } catch (LockConflictException e) {
1287             fail();
1288         }
1289 
1290         /* Insert {1,3} in a writer thread. */
1291         startInsert(1, 3);
1292 
1293         /*
1294          * If serializable, getLast should return {1,2} again; otherwise
1295          * getLast should see {1,3}.
1296          */
1297         status = cursor.getLast(key, data, null);
1298         assertEquals(OperationStatus.SUCCESS, status);
1299         if (txnSerializable) {
1300             assertEquals(1, IntegerBinding.entryToInt(key));
1301             assertEquals(2, IntegerBinding.entryToInt(data));
1302         } else {
1303             assertEquals(1, IntegerBinding.entryToInt(key));
1304             assertEquals(3, IntegerBinding.entryToInt(data));
1305         }
1306 
1307         /* Close reader to allow writer to finish. */
1308         cursor.close();
1309         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1310         waitForInsert();
1311 
1312         /* getLast returns {1,3}. */
1313         readerTxn = env.beginTransaction(null, txnConfig);
1314         cursor = db.openCursor(readerTxn, null);
1315         status = cursor.getLast(key, data, null);
1316         assertEquals(OperationStatus.SUCCESS, status);
1317         assertEquals(1, IntegerBinding.entryToInt(key));
1318         assertEquals(3, IntegerBinding.entryToInt(data));
1319         cursor.close();
1320         readerTxn.commit();
1321 
1322         closeEnv();
1323     }
1324 
1325     @Test
testGetLast_NotFound()1326     public void testGetLast_NotFound()
1327         throws DatabaseException, InterruptedException {
1328 
1329         openEnv(false);
1330         DatabaseEntry key = new DatabaseEntry();
1331         DatabaseEntry data = new DatabaseEntry();
1332         OperationStatus status;
1333 
1334         /* getLast returns NOTFOUND. */
1335         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1336         Cursor cursor = db.openCursor(readerTxn, null);
1337         status = cursor.getLast(key, data, null);
1338         assertEquals(OperationStatus.NOTFOUND, status);
1339 
1340         /* Insert key 1 in a writer thread. */
1341         startInsert(1);
1342 
1343         /*
1344          * If serializable, getLast should return NOTFOUND again; otherwise
1345          * getLast should see key 1.
1346          */
1347         status = cursor.getLast(key, data, null);
1348         if (txnSerializable) {
1349             assertEquals(OperationStatus.NOTFOUND, status);
1350         } else {
1351             assertEquals(OperationStatus.SUCCESS, status);
1352             assertEquals(1, IntegerBinding.entryToInt(key));
1353         }
1354 
1355         /* Close reader to allow writer to finish. */
1356         cursor.close();
1357         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1358         waitForInsert();
1359 
1360         /* getLast returns key 1. */
1361         readerTxn = env.beginTransaction(null, txnConfig);
1362         cursor = db.openCursor(readerTxn, null);
1363         status = cursor.getLast(key, data, null);
1364         assertEquals(OperationStatus.SUCCESS, status);
1365         assertEquals(1, IntegerBinding.entryToInt(key));
1366         cursor.close();
1367         readerTxn.commit();
1368 
1369         closeEnv();
1370     }
1371 
1372     @Test
testGetLast_NotFound_Dup()1373     public void testGetLast_NotFound_Dup()
1374         throws DatabaseException, InterruptedException {
1375 
1376         openEnv(true);
1377         DatabaseEntry key = new DatabaseEntry();
1378         DatabaseEntry data = new DatabaseEntry();
1379         OperationStatus status;
1380 
1381         /* getLast returns NOTFOUND. */
1382         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1383         Cursor cursor = db.openCursor(readerTxn, null);
1384         status = cursor.getLast(key, data, null);
1385         assertEquals(OperationStatus.NOTFOUND, status);
1386 
1387         /* Insert {1,1} in a writer thread. */
1388         startInsert(1, 1);
1389 
1390         /*
1391          * If serializable, getLast should return NOTFOUND again; otherwise
1392          * getLast should see {1,1}.
1393          */
1394         status = cursor.getLast(key, data, null);
1395         if (txnSerializable) {
1396             assertEquals(OperationStatus.NOTFOUND, status);
1397         } else {
1398             assertEquals(OperationStatus.SUCCESS, status);
1399             assertEquals(1, IntegerBinding.entryToInt(key));
1400             assertEquals(1, IntegerBinding.entryToInt(data));
1401         }
1402 
1403         /* Close reader to allow writer to finish. */
1404         cursor.close();
1405         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1406         waitForInsert();
1407 
1408         /* getLast returns {1,1}. */
1409         readerTxn = env.beginTransaction(null, txnConfig);
1410         cursor = db.openCursor(readerTxn, null);
1411         status = cursor.getLast(key, data, null);
1412         assertEquals(OperationStatus.SUCCESS, status);
1413         assertEquals(1, IntegerBinding.entryToInt(key));
1414         assertEquals(1, IntegerBinding.entryToInt(data));
1415         cursor.close();
1416         readerTxn.commit();
1417 
1418         closeEnv();
1419     }
1420 
1421     @Test
testGetNext_Success()1422     public void testGetNext_Success()
1423         throws DatabaseException, InterruptedException {
1424 
1425         openEnv(false);
1426         DatabaseEntry key = new DatabaseEntry();
1427         DatabaseEntry data = new DatabaseEntry();
1428         OperationStatus status;
1429 
1430         /* Insert key 1 and 3. */
1431         insert(1);
1432         insert(3);
1433 
1434         /* getNext returns key 3. */
1435         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1436         Cursor cursor = db.openCursor(readerTxn, null);
1437         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1438         status = cursor.getNext(key, data, null);
1439         assertEquals(OperationStatus.SUCCESS, status);
1440         assertEquals(3, IntegerBinding.entryToInt(key));
1441 
1442         /* Insertions before 1 and after 3 are never blocked. */
1443         try {
1444             insert(0);
1445             insert(4);
1446         } catch (LockConflictException e) {
1447             fail();
1448         }
1449 
1450         /* Insert key 2 in a writer thread. */
1451         startInsert(2);
1452 
1453         /*
1454          * If serializable, getNext should return key 3 again; otherwise
1455          * getNext should see key 2.
1456          */
1457         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1458         status = cursor.getNext(key, data, null);
1459         assertEquals(OperationStatus.SUCCESS, status);
1460         if (txnSerializable) {
1461             assertEquals(3, IntegerBinding.entryToInt(key));
1462         } else {
1463             assertEquals(2, IntegerBinding.entryToInt(key));
1464         }
1465 
1466         /* Close reader to allow writer to finish. */
1467         cursor.close();
1468         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1469         waitForInsert();
1470 
1471         /* getNext returns key 2. */
1472         readerTxn = env.beginTransaction(null, txnConfig);
1473         cursor = db.openCursor(readerTxn, null);
1474         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1475         status = cursor.getNext(key, data, null);
1476         assertEquals(OperationStatus.SUCCESS, status);
1477         assertEquals(2, IntegerBinding.entryToInt(key));
1478         cursor.close();
1479         readerTxn.commit();
1480 
1481         closeEnv();
1482     }
1483 
1484     @Test
testGetNext_Success_Dup()1485     public void testGetNext_Success_Dup()
1486         throws DatabaseException, InterruptedException {
1487 
1488         openEnv(true);
1489         DatabaseEntry key = new DatabaseEntry();
1490         DatabaseEntry data = new DatabaseEntry();
1491         OperationStatus status;
1492 
1493         /* Insert dups. */
1494         insert(1, 1);
1495         insert(1, 3);
1496 
1497         /* getNext returns {1,3}. */
1498         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1499         Cursor cursor = db.openCursor(readerTxn, null);
1500         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1501         status = cursor.getNext(key, data, null);
1502         assertEquals(OperationStatus.SUCCESS, status);
1503         assertEquals(1, IntegerBinding.entryToInt(key));
1504         assertEquals(3, IntegerBinding.entryToInt(data));
1505 
1506         /* Insertions before {1,1} and after {1,3} are never blocked. */
1507         try {
1508             insert(1, 0);
1509             insert(0, 0);
1510             insert(1, 4);
1511             insert(2, 0);
1512         } catch (LockConflictException e) {
1513             fail();
1514         }
1515 
1516         /* Insert {1,2} in a writer thread. */
1517         startInsert(1, 2);
1518 
1519         /*
1520          * If serializable, getNext should return {1,3} again; otherwise
1521          * getNext should see {1,2}.
1522          */
1523         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1524         status = cursor.getNext(key, data, null);
1525         assertEquals(OperationStatus.SUCCESS, status);
1526         if (txnSerializable) {
1527             assertEquals(1, IntegerBinding.entryToInt(key));
1528             assertEquals(3, IntegerBinding.entryToInt(data));
1529         } else {
1530             assertEquals(1, IntegerBinding.entryToInt(key));
1531             assertEquals(2, IntegerBinding.entryToInt(data));
1532         }
1533 
1534         /* Close reader to allow writer to finish. */
1535         cursor.close();
1536         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1537         waitForInsert();
1538 
1539         /* getNext returns {1,2}. */
1540         readerTxn = env.beginTransaction(null, txnConfig);
1541         cursor = db.openCursor(readerTxn, null);
1542         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1543         status = cursor.getNext(key, data, null);
1544         assertEquals(OperationStatus.SUCCESS, status);
1545         assertEquals(1, IntegerBinding.entryToInt(key));
1546         assertEquals(2, IntegerBinding.entryToInt(data));
1547         cursor.close();
1548         readerTxn.commit();
1549 
1550         closeEnv();
1551     }
1552 
1553     @Test
testGetNext_NotFound()1554     public void testGetNext_NotFound()
1555         throws DatabaseException, InterruptedException {
1556 
1557         openEnv(false);
1558         DatabaseEntry key = new DatabaseEntry();
1559         DatabaseEntry data = new DatabaseEntry();
1560         OperationStatus status;
1561 
1562         /* Insert key 1. */
1563         insert(1);
1564 
1565         /* getNext returns NOTFOUND. */
1566         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1567         Cursor cursor = db.openCursor(readerTxn, null);
1568         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1569         status = cursor.getNext(key, data, null);
1570         assertEquals(OperationStatus.NOTFOUND, status);
1571 
1572         /* Insertions before 1 are never blocked. */
1573         try {
1574             insert(0);
1575         } catch (LockConflictException e) {
1576             fail();
1577         }
1578 
1579         /* Insert key 2 in a writer thread. */
1580         startInsert(2);
1581 
1582         /*
1583          * If serializable, getNext should return NOTFOUND again; otherwise
1584          * getNext should see key 2.
1585          */
1586         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1587         status = cursor.getNext(key, data, null);
1588         if (txnSerializable) {
1589             assertEquals(OperationStatus.NOTFOUND, status);
1590         } else {
1591             assertEquals(OperationStatus.SUCCESS, status);
1592             assertEquals(2, IntegerBinding.entryToInt(key));
1593         }
1594 
1595         /* Close reader to allow writer to finish. */
1596         cursor.close();
1597         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1598         waitForInsert();
1599 
1600         /* getNext returns key 2. */
1601         readerTxn = env.beginTransaction(null, txnConfig);
1602         cursor = db.openCursor(readerTxn, null);
1603         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1604         status = cursor.getNext(key, data, null);
1605         assertEquals(OperationStatus.SUCCESS, status);
1606         assertEquals(2, IntegerBinding.entryToInt(key));
1607         cursor.close();
1608         readerTxn.commit();
1609 
1610         closeEnv();
1611     }
1612 
1613     @Test
testGetNext_NotFound_Dup()1614     public void testGetNext_NotFound_Dup()
1615         throws DatabaseException, InterruptedException {
1616 
1617         openEnv(true);
1618         DatabaseEntry key = new DatabaseEntry();
1619         DatabaseEntry data = new DatabaseEntry();
1620         OperationStatus status;
1621 
1622         /* Insert dups. */
1623         insert(1, 1);
1624         insert(1, 2);
1625 
1626         /* getNext returns NOTFOUND. */
1627         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1628         Cursor cursor = db.openCursor(readerTxn, null);
1629         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1630         status = cursor.getNext(key, data, null);
1631         assertEquals(OperationStatus.NOTFOUND, status);
1632 
1633         /* Insertions before {1,1} are never blocked. */
1634         try {
1635             insert(1, 0);
1636             insert(0, 0);
1637         } catch (LockConflictException e) {
1638             fail();
1639         }
1640 
1641         /* Insert {1,3} in a writer thread. */
1642         startInsert(1, 3);
1643 
1644         /*
1645          * If serializable, getNext should return NOTFOUND again; otherwise
1646          * getNext should see {1,3}.
1647          */
1648         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1649         status = cursor.getNext(key, data, null);
1650         if (txnSerializable) {
1651             assertEquals(OperationStatus.NOTFOUND, status);
1652         } else {
1653             assertEquals(OperationStatus.SUCCESS, status);
1654             assertEquals(1, IntegerBinding.entryToInt(key));
1655             assertEquals(3, IntegerBinding.entryToInt(data));
1656         }
1657 
1658         /* Close reader to allow writer to finish. */
1659         cursor.close();
1660         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1661         waitForInsert();
1662 
1663         /* getNext returns {1,3}. */
1664         readerTxn = env.beginTransaction(null, txnConfig);
1665         cursor = db.openCursor(readerTxn, null);
1666         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1667         status = cursor.getNext(key, data, null);
1668         assertEquals(OperationStatus.SUCCESS, status);
1669         assertEquals(1, IntegerBinding.entryToInt(key));
1670         assertEquals(3, IntegerBinding.entryToInt(data));
1671         cursor.close();
1672         readerTxn.commit();
1673 
1674         closeEnv();
1675     }
1676 
1677     @Test
testGetNextDup_Success_Dup()1678     public void testGetNextDup_Success_Dup()
1679         throws DatabaseException, InterruptedException {
1680 
1681         openEnv(true);
1682         DatabaseEntry key = new DatabaseEntry();
1683         DatabaseEntry data = new DatabaseEntry();
1684         OperationStatus status;
1685 
1686         /* Insert dups. */
1687         insert(1, 1);
1688         insert(1, 3);
1689 
1690         /* getNextDup returns {1,3}. */
1691         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1692         Cursor cursor = db.openCursor(readerTxn, null);
1693         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1694         status = cursor.getNextDup(key, data, null);
1695         assertEquals(OperationStatus.SUCCESS, status);
1696         assertEquals(1, IntegerBinding.entryToInt(key));
1697         assertEquals(3, IntegerBinding.entryToInt(data));
1698 
1699         /* Insertions before {1,1} and after {1,3} are never blocked. */
1700         try {
1701             insert(1, 0);
1702             insert(0, 0);
1703             insert(1, 4);
1704             insert(2, 0);
1705         } catch (LockConflictException e) {
1706             fail();
1707         }
1708 
1709         /* Insert {1,2} in a writer thread. */
1710         startInsert(1, 2);
1711 
1712         /*
1713          * If serializable, getNextDup should return {1,3} again; otherwise
1714          * getNextDup should see {1,2}.
1715          */
1716         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1717         status = cursor.getNextDup(key, data, null);
1718         assertEquals(OperationStatus.SUCCESS, status);
1719         if (txnSerializable) {
1720             assertEquals(1, IntegerBinding.entryToInt(key));
1721             assertEquals(3, IntegerBinding.entryToInt(data));
1722         } else {
1723             assertEquals(1, IntegerBinding.entryToInt(key));
1724             assertEquals(2, IntegerBinding.entryToInt(data));
1725         }
1726 
1727         /* Close reader to allow writer to finish. */
1728         cursor.close();
1729         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1730         waitForInsert();
1731 
1732         /* getNextDup returns {1,2}. */
1733         readerTxn = env.beginTransaction(null, txnConfig);
1734         cursor = db.openCursor(readerTxn, null);
1735         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1736         status = cursor.getNextDup(key, data, null);
1737         assertEquals(OperationStatus.SUCCESS, status);
1738         assertEquals(1, IntegerBinding.entryToInt(key));
1739         assertEquals(2, IntegerBinding.entryToInt(data));
1740         cursor.close();
1741         readerTxn.commit();
1742 
1743         closeEnv();
1744     }
1745 
1746     @Test
testGetNextDup_NotFound_Dup()1747     public void testGetNextDup_NotFound_Dup()
1748         throws DatabaseException, InterruptedException {
1749 
1750         openEnv(true);
1751         DatabaseEntry key = new DatabaseEntry();
1752         DatabaseEntry data = new DatabaseEntry();
1753         OperationStatus status;
1754 
1755         /* Insert dups. */
1756         insert(1, 1);
1757         insert(1, 2);
1758         insert(2, 1);
1759         insert(2, 2);
1760 
1761         /* getNextDup returns NOTFOUND. */
1762         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1763         Cursor cursor = db.openCursor(readerTxn, null);
1764         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1765         status = cursor.getNextDup(key, data, null);
1766         assertEquals(OperationStatus.NOTFOUND, status);
1767 
1768         /* Insertions before {1,1} and after {2,2} are never blocked. */
1769         try {
1770             insert(1, 0);
1771             insert(0, 0);
1772             insert(2, 3);
1773             insert(3, 0);
1774         } catch (LockConflictException e) {
1775             fail();
1776         }
1777 
1778         /* Insert {1,3} in a writer thread. */
1779         startInsert(1, 3);
1780 
1781         /*
1782          * If serializable, getNextDup should return NOTFOUND again; otherwise
1783          * getNextDup should see {1,3}.
1784          */
1785         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1786         status = cursor.getNextDup(key, data, null);
1787         if (txnSerializable) {
1788             assertEquals(OperationStatus.NOTFOUND, status);
1789         } else {
1790             assertEquals(OperationStatus.SUCCESS, status);
1791             assertEquals(1, IntegerBinding.entryToInt(key));
1792             assertEquals(3, IntegerBinding.entryToInt(data));
1793         }
1794 
1795         /* Close reader to allow writer to finish. */
1796         cursor.close();
1797         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1798         waitForInsert();
1799 
1800         /* getNextDup returns {1,3}. */
1801         readerTxn = env.beginTransaction(null, txnConfig);
1802         cursor = db.openCursor(readerTxn, null);
1803         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 2));
1804         status = cursor.getNextDup(key, data, null);
1805         assertEquals(OperationStatus.SUCCESS, status);
1806         assertEquals(1, IntegerBinding.entryToInt(key));
1807         assertEquals(3, IntegerBinding.entryToInt(data));
1808         cursor.close();
1809         readerTxn.commit();
1810 
1811         closeEnv();
1812     }
1813 
1814     @Test
testGetNextNoDup_Success()1815     public void testGetNextNoDup_Success()
1816         throws DatabaseException, InterruptedException {
1817 
1818         openEnv(false);
1819         DatabaseEntry key = new DatabaseEntry();
1820         DatabaseEntry data = new DatabaseEntry();
1821         OperationStatus status;
1822 
1823         /* Insert key 1 and 3. */
1824         insert(1);
1825         insert(3);
1826 
1827         /* getNextNoDup returns key 3. */
1828         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1829         Cursor cursor = db.openCursor(readerTxn, null);
1830         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1831         status = cursor.getNextNoDup(key, data, null);
1832         assertEquals(OperationStatus.SUCCESS, status);
1833         assertEquals(3, IntegerBinding.entryToInt(key));
1834 
1835         /* Insertions before 1 and after 3 are never blocked. */
1836         try {
1837             insert(0);
1838             insert(4);
1839         } catch (LockConflictException e) {
1840             fail();
1841         }
1842 
1843         /* Insert key 2 in a writer thread. */
1844         startInsert(2);
1845 
1846         /*
1847          * If serializable, getNextNoDup should return key 3 again; otherwise
1848          * getNextNoDup should see key 2.
1849          */
1850         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1851         status = cursor.getNextNoDup(key, data, null);
1852         assertEquals(OperationStatus.SUCCESS, status);
1853         if (txnSerializable) {
1854             assertEquals(3, IntegerBinding.entryToInt(key));
1855         } else {
1856             assertEquals(2, IntegerBinding.entryToInt(key));
1857         }
1858 
1859         /* Close reader to allow writer to finish. */
1860         cursor.close();
1861         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1862         waitForInsert();
1863 
1864         /* getNextNoDup returns key 2. */
1865         readerTxn = env.beginTransaction(null, txnConfig);
1866         cursor = db.openCursor(readerTxn, null);
1867         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1868         status = cursor.getNextNoDup(key, data, null);
1869         assertEquals(OperationStatus.SUCCESS, status);
1870         assertEquals(2, IntegerBinding.entryToInt(key));
1871         cursor.close();
1872         readerTxn.commit();
1873 
1874         closeEnv();
1875     }
1876 
1877     @Test
testGetNextNoDup_Success_Dup()1878     public void testGetNextNoDup_Success_Dup()
1879         throws DatabaseException, InterruptedException {
1880 
1881         openEnv(true);
1882         DatabaseEntry key = new DatabaseEntry();
1883         DatabaseEntry data = new DatabaseEntry();
1884         OperationStatus status;
1885 
1886         /* Insert dups. */
1887         insert(1, 1);
1888         insert(1, 2);
1889         insert(3, 1);
1890         insert(3, 2);
1891 
1892         /* getNextNoDup returns {3,1}. */
1893         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1894         Cursor cursor = db.openCursor(readerTxn, null);
1895         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1896         status = cursor.getNextNoDup(key, data, null);
1897         assertEquals(OperationStatus.SUCCESS, status);
1898         assertEquals(3, IntegerBinding.entryToInt(key));
1899         assertEquals(1, IntegerBinding.entryToInt(data));
1900 
1901         /* Insertions before {1,1} and after {3,2} are never blocked. */
1902         try {
1903             insert(1, 0);
1904             insert(0, 0);
1905             insert(3, 3);
1906             insert(4, 0);
1907         } catch (LockConflictException e) {
1908             fail();
1909         }
1910 
1911         /* Insert {2,1} in a writer thread. */
1912         startInsert(2, 1);
1913 
1914         /*
1915          * If serializable, getNextNoDup should return {3,1} again; otherwise
1916          * getNextNoDup should see {2,1}.
1917          */
1918         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1919         status = cursor.getNextNoDup(key, data, null);
1920         assertEquals(OperationStatus.SUCCESS, status);
1921         if (txnSerializable) {
1922             assertEquals(3, IntegerBinding.entryToInt(key));
1923             assertEquals(1, IntegerBinding.entryToInt(data));
1924         } else {
1925             assertEquals(2, IntegerBinding.entryToInt(key));
1926             assertEquals(1, IntegerBinding.entryToInt(data));
1927         }
1928 
1929         /* Close reader to allow writer to finish. */
1930         cursor.close();
1931         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1932         waitForInsert();
1933 
1934         /* getNextNoDup returns {2,1}. */
1935         readerTxn = env.beginTransaction(null, txnConfig);
1936         cursor = db.openCursor(readerTxn, null);
1937         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
1938         status = cursor.getNextNoDup(key, data, null);
1939         assertEquals(OperationStatus.SUCCESS, status);
1940         assertEquals(2, IntegerBinding.entryToInt(key));
1941         assertEquals(1, IntegerBinding.entryToInt(data));
1942         cursor.close();
1943         readerTxn.commit();
1944 
1945         closeEnv();
1946     }
1947 
1948     @Test
testGetNextNoDup_NotFound()1949     public void testGetNextNoDup_NotFound()
1950         throws DatabaseException, InterruptedException {
1951 
1952         openEnv(false);
1953         DatabaseEntry key = new DatabaseEntry();
1954         DatabaseEntry data = new DatabaseEntry();
1955         OperationStatus status;
1956 
1957         /* Insert key 1. */
1958         insert(1);
1959 
1960         /* getNextNoDup returns NOTFOUND. */
1961         Transaction readerTxn = env.beginTransaction(null, txnConfig);
1962         Cursor cursor = db.openCursor(readerTxn, null);
1963         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1964         status = cursor.getNextNoDup(key, data, null);
1965         assertEquals(OperationStatus.NOTFOUND, status);
1966 
1967         /* Insertions before 1 are never blocked. */
1968         try {
1969             insert(0);
1970         } catch (LockConflictException e) {
1971             fail();
1972         }
1973 
1974         /* Insert key 2 in a writer thread. */
1975         startInsert(2);
1976 
1977         /*
1978          * If serializable, getNextNoDup should return NOTFOUND again;
1979          * otherwise getNextNoDup should see key 2.
1980          */
1981         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1982         status = cursor.getNextNoDup(key, data, null);
1983         if (txnSerializable) {
1984             assertEquals(OperationStatus.NOTFOUND, status);
1985         } else {
1986             assertEquals(OperationStatus.SUCCESS, status);
1987             assertEquals(2, IntegerBinding.entryToInt(key));
1988         }
1989 
1990         /* Close reader to allow writer to finish. */
1991         cursor.close();
1992         readerTxn.commit(Durability.COMMIT_NO_SYNC);
1993         waitForInsert();
1994 
1995         /* getNextNoDup returns key 2. */
1996         readerTxn = env.beginTransaction(null, txnConfig);
1997         cursor = db.openCursor(readerTxn, null);
1998         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1));
1999         status = cursor.getNextNoDup(key, data, null);
2000         assertEquals(OperationStatus.SUCCESS, status);
2001         assertEquals(2, IntegerBinding.entryToInt(key));
2002         cursor.close();
2003         readerTxn.commit();
2004 
2005         closeEnv();
2006     }
2007 
2008     @Test
testGetNextNoDup_NotFound_Dup()2009     public void testGetNextNoDup_NotFound_Dup()
2010         throws DatabaseException, InterruptedException {
2011 
2012         openEnv(true);
2013         DatabaseEntry key = new DatabaseEntry();
2014         DatabaseEntry data = new DatabaseEntry();
2015         OperationStatus status;
2016 
2017         /* Insert dups. */
2018         insert(1, 1);
2019         insert(1, 2);
2020 
2021         /* getNextNoDup returns NOTFOUND. */
2022         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2023         Cursor cursor = db.openCursor(readerTxn, null);
2024         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
2025         status = cursor.getNextNoDup(key, data, null);
2026         assertEquals(OperationStatus.NOTFOUND, status);
2027 
2028         /* Insertions before {1,1} are never blocked. */
2029         try {
2030             insert(1, 0);
2031             insert(0, 0);
2032         } catch (LockConflictException e) {
2033             fail();
2034         }
2035 
2036         /* Insert {2,1} in a writer thread. */
2037         startInsert(2, 1);
2038 
2039         /*
2040          * If serializable, getNextNoDup should return NOTFOUND again;
2041          * otherwise getNextNoDup should see {2,1}.
2042          */
2043         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
2044         status = cursor.getNextNoDup(key, data, null);
2045         if (txnSerializable) {
2046             assertEquals(OperationStatus.NOTFOUND, status);
2047         } else {
2048             assertEquals(OperationStatus.SUCCESS, status);
2049             assertEquals(2, IntegerBinding.entryToInt(key));
2050             assertEquals(1, IntegerBinding.entryToInt(data));
2051         }
2052 
2053         /* Close reader to allow writer to finish. */
2054         cursor.close();
2055         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2056         waitForInsert();
2057 
2058         /* getNextNoDup returns {2,1}. */
2059         readerTxn = env.beginTransaction(null, txnConfig);
2060         cursor = db.openCursor(readerTxn, null);
2061         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
2062         status = cursor.getNextNoDup(key, data, null);
2063         assertEquals(OperationStatus.SUCCESS, status);
2064         assertEquals(2, IntegerBinding.entryToInt(key));
2065         assertEquals(1, IntegerBinding.entryToInt(data));
2066         cursor.close();
2067         readerTxn.commit();
2068 
2069         closeEnv();
2070     }
2071 
2072     @Test
testGetPrev_Success()2073     public void testGetPrev_Success()
2074         throws DatabaseException, InterruptedException {
2075 
2076         openEnv(false);
2077         DatabaseEntry key = new DatabaseEntry();
2078         DatabaseEntry data = new DatabaseEntry();
2079         OperationStatus status;
2080 
2081         /* Insert key 1 and 3. */
2082         insert(1);
2083         insert(3);
2084 
2085         /* getPrev returns key 1. */
2086         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2087         Cursor cursor = db.openCursor(readerTxn, null);
2088         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2089         status = cursor.getPrev(key, data, null);
2090         assertEquals(OperationStatus.SUCCESS, status);
2091         assertEquals(1, IntegerBinding.entryToInt(key));
2092 
2093         /* Insertions before 1 and after 3 are never blocked. */
2094         try {
2095             insert(0);
2096             insert(4);
2097         } catch (LockConflictException e) {
2098             fail();
2099         }
2100 
2101         /* Insert key 2 in a writer thread. */
2102         startInsert(2);
2103 
2104         /*
2105          * If serializable, getPrev should return key 1 again; otherwise
2106          * getPrev should see key 2.
2107          */
2108         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2109         status = cursor.getPrev(key, data, null);
2110         assertEquals(OperationStatus.SUCCESS, status);
2111         if (txnSerializable) {
2112             assertEquals(1, IntegerBinding.entryToInt(key));
2113         } else {
2114             assertEquals(2, IntegerBinding.entryToInt(key));
2115         }
2116 
2117         /* Close reader to allow writer to finish. */
2118         cursor.close();
2119         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2120         waitForInsert();
2121 
2122         /* getPrev returns key 2. */
2123         readerTxn = env.beginTransaction(null, txnConfig);
2124         cursor = db.openCursor(readerTxn, null);
2125         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2126         status = cursor.getPrev(key, data, null);
2127         assertEquals(OperationStatus.SUCCESS, status);
2128         assertEquals(2, IntegerBinding.entryToInt(key));
2129         cursor.close();
2130         readerTxn.commit();
2131 
2132         closeEnv();
2133     }
2134 
2135     @Test
testGetPrev_Success_Dup()2136     public void testGetPrev_Success_Dup()
2137         throws DatabaseException, InterruptedException {
2138 
2139         openEnv(true);
2140         DatabaseEntry key = new DatabaseEntry();
2141         DatabaseEntry data = new DatabaseEntry();
2142         OperationStatus status;
2143 
2144         /* Insert dups. */
2145         insert(1, 1);
2146         insert(1, 3);
2147 
2148         /* getPrev returns {1,1}. */
2149         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2150         Cursor cursor = db.openCursor(readerTxn, null);
2151         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2152         status = cursor.getPrev(key, data, null);
2153         assertEquals(OperationStatus.SUCCESS, status);
2154         assertEquals(1, IntegerBinding.entryToInt(key));
2155         assertEquals(1, IntegerBinding.entryToInt(data));
2156 
2157         /* Insertions before {1,1} and after {1,3} are never blocked. */
2158         try {
2159             insert(1, 0);
2160             insert(0, 0);
2161             insert(1, 4);
2162             insert(2, 0);
2163         } catch (LockConflictException e) {
2164             fail();
2165         }
2166 
2167         /* Insert {1,2} in a writer thread. */
2168         startInsert(1, 2);
2169 
2170         /*
2171          * If serializable, getPrev should return {1,1} again; otherwise
2172          * getPrev should see {1,2}.
2173          */
2174         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2175         status = cursor.getPrev(key, data, null);
2176         assertEquals(OperationStatus.SUCCESS, status);
2177         if (txnSerializable) {
2178             assertEquals(1, IntegerBinding.entryToInt(key));
2179             assertEquals(1, IntegerBinding.entryToInt(data));
2180         } else {
2181             assertEquals(1, IntegerBinding.entryToInt(key));
2182             assertEquals(2, IntegerBinding.entryToInt(data));
2183         }
2184 
2185         /* Close reader to allow writer to finish. */
2186         cursor.close();
2187         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2188         waitForInsert();
2189 
2190         /* getPrev returns {1,2}. */
2191         readerTxn = env.beginTransaction(null, txnConfig);
2192         cursor = db.openCursor(readerTxn, null);
2193         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2194         status = cursor.getPrev(key, data, null);
2195         assertEquals(OperationStatus.SUCCESS, status);
2196         assertEquals(1, IntegerBinding.entryToInt(key));
2197         assertEquals(2, IntegerBinding.entryToInt(data));
2198         cursor.close();
2199         readerTxn.commit();
2200 
2201         closeEnv();
2202     }
2203 
2204     @Test
testGetPrev_NotFound()2205     public void testGetPrev_NotFound()
2206         throws DatabaseException, InterruptedException {
2207 
2208         openEnv(false);
2209         DatabaseEntry key = new DatabaseEntry();
2210         DatabaseEntry data = new DatabaseEntry();
2211         OperationStatus status;
2212 
2213         /* Insert key 2. */
2214         insert(2);
2215 
2216         /* getPrev returns NOTFOUND. */
2217         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2218         Cursor cursor = db.openCursor(readerTxn, null);
2219         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2220         status = cursor.getPrev(key, data, null);
2221         assertEquals(OperationStatus.NOTFOUND, status);
2222 
2223         /* Insertions after 2 are never blocked. */
2224         try {
2225             insert(3);
2226         } catch (LockConflictException e) {
2227             fail();
2228         }
2229 
2230         /* Insert key 1 in a writer thread. */
2231         startInsert(1);
2232 
2233         /*
2234          * If serializable, getPrev should return NOTFOUND again; otherwise
2235          * getPrev should see key 1.
2236          */
2237         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2238         status = cursor.getPrev(key, data, null);
2239         if (txnSerializable) {
2240             assertEquals(OperationStatus.NOTFOUND, status);
2241         } else {
2242             assertEquals(OperationStatus.SUCCESS, status);
2243             assertEquals(1, IntegerBinding.entryToInt(key));
2244         }
2245 
2246         /* Close reader to allow writer to finish. */
2247         cursor.close();
2248         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2249         waitForInsert();
2250 
2251         /* getPrev returns key 1. */
2252         readerTxn = env.beginTransaction(null, txnConfig);
2253         cursor = db.openCursor(readerTxn, null);
2254         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2255         status = cursor.getPrev(key, data, null);
2256         assertEquals(OperationStatus.SUCCESS, status);
2257         assertEquals(1, IntegerBinding.entryToInt(key));
2258         cursor.close();
2259         readerTxn.commit();
2260 
2261         closeEnv();
2262     }
2263 
2264     @Test
testGetPrev_NotFound_Dup()2265     public void testGetPrev_NotFound_Dup()
2266         throws DatabaseException, InterruptedException {
2267 
2268         openEnv(true);
2269         DatabaseEntry key = new DatabaseEntry();
2270         DatabaseEntry data = new DatabaseEntry();
2271         OperationStatus status;
2272 
2273         /* Insert dups. */
2274         insert(2, 2);
2275         insert(2, 3);
2276 
2277         /* getPrev returns NOTFOUND. */
2278         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2279         Cursor cursor = db.openCursor(readerTxn, null);
2280         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2281         status = cursor.getPrev(key, data, null);
2282         assertEquals(OperationStatus.NOTFOUND, status);
2283 
2284         /* Insertions after {2,3} are never blocked. */
2285         try {
2286             insert(2, 4);
2287             insert(3, 0);
2288         } catch (LockConflictException e) {
2289             fail();
2290         }
2291 
2292         /* Insert {2,1} in a writer thread. */
2293         startInsert(2, 1);
2294 
2295         /*
2296          * If serializable, getPrev should return NOTFOUND again; otherwise
2297          * getPrev should see {2,1}.
2298          */
2299         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2300         status = cursor.getPrev(key, data, null);
2301         if (txnSerializable) {
2302             assertEquals(OperationStatus.NOTFOUND, status);
2303         } else {
2304             assertEquals(OperationStatus.SUCCESS, status);
2305             assertEquals(2, IntegerBinding.entryToInt(key));
2306             assertEquals(1, IntegerBinding.entryToInt(data));
2307         }
2308 
2309         /* Close reader to allow writer to finish. */
2310         cursor.close();
2311         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2312         waitForInsert();
2313 
2314         /* getPrev returns {2,1}. */
2315         readerTxn = env.beginTransaction(null, txnConfig);
2316         cursor = db.openCursor(readerTxn, null);
2317         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2318         status = cursor.getPrev(key, data, null);
2319         assertEquals(OperationStatus.SUCCESS, status);
2320         assertEquals(2, IntegerBinding.entryToInt(key));
2321         assertEquals(1, IntegerBinding.entryToInt(data));
2322         cursor.close();
2323         readerTxn.commit();
2324 
2325         closeEnv();
2326     }
2327 
2328     @Test
testGetPrevDup_Success_Dup()2329     public void testGetPrevDup_Success_Dup()
2330         throws DatabaseException, InterruptedException {
2331 
2332         openEnv(true);
2333         DatabaseEntry key = new DatabaseEntry();
2334         DatabaseEntry data = new DatabaseEntry();
2335         OperationStatus status;
2336 
2337         /* Insert dups. */
2338         insert(1, 1);
2339         insert(1, 3);
2340 
2341         /* getPrevDup returns {1,1}. */
2342         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2343         Cursor cursor = db.openCursor(readerTxn, null);
2344         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2345         status = cursor.getPrevDup(key, data, null);
2346         assertEquals(OperationStatus.SUCCESS, status);
2347         assertEquals(1, IntegerBinding.entryToInt(key));
2348         assertEquals(1, IntegerBinding.entryToInt(data));
2349 
2350         /* Insertions before {1,1} and after {1,3} are never blocked. */
2351         try {
2352             insert(1, 0);
2353             insert(0, 0);
2354             insert(1, 4);
2355             insert(2, 0);
2356         } catch (LockConflictException e) {
2357             fail();
2358         }
2359 
2360         /* Insert {1,2} in a writer thread. */
2361         startInsert(1, 2);
2362 
2363         /*
2364          * If serializable, getPrevDup should return {1,1} again; otherwise
2365          * getPrevDup should see {1,2}.
2366          */
2367         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2368         status = cursor.getPrevDup(key, data, null);
2369         assertEquals(OperationStatus.SUCCESS, status);
2370         if (txnSerializable) {
2371             assertEquals(1, IntegerBinding.entryToInt(key));
2372             assertEquals(1, IntegerBinding.entryToInt(data));
2373         } else {
2374             assertEquals(1, IntegerBinding.entryToInt(key));
2375             assertEquals(2, IntegerBinding.entryToInt(data));
2376         }
2377 
2378         /* Close reader to allow writer to finish. */
2379         cursor.close();
2380         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2381         waitForInsert();
2382 
2383         /* getPrevDup returns {1,2}. */
2384         readerTxn = env.beginTransaction(null, txnConfig);
2385         cursor = db.openCursor(readerTxn, null);
2386         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 3));
2387         status = cursor.getPrevDup(key, data, null);
2388         assertEquals(OperationStatus.SUCCESS, status);
2389         assertEquals(1, IntegerBinding.entryToInt(key));
2390         assertEquals(2, IntegerBinding.entryToInt(data));
2391         cursor.close();
2392         readerTxn.commit();
2393 
2394         closeEnv();
2395     }
2396 
2397     @Test
testGetPrevDup_NotFound_Dup()2398     public void testGetPrevDup_NotFound_Dup()
2399         throws DatabaseException, InterruptedException {
2400 
2401         openEnv(true);
2402         DatabaseEntry key = new DatabaseEntry();
2403         DatabaseEntry data = new DatabaseEntry();
2404         OperationStatus status;
2405 
2406         /* Insert dups. */
2407         insert(2, 2);
2408         insert(2, 3);
2409 
2410         /* getPrevDup returns NOTFOUND. */
2411         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2412         Cursor cursor = db.openCursor(readerTxn, null);
2413         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2414         status = cursor.getPrevDup(key, data, null);
2415         assertEquals(OperationStatus.NOTFOUND, status);
2416 
2417         /* Insertions after {2,3} are never blocked. */
2418         try {
2419             insert(2, 4);
2420             insert(3, 0);
2421         } catch (LockConflictException e) {
2422             fail();
2423         }
2424 
2425         /* Insert {2,1} in a writer thread. */
2426         startInsert(2, 1);
2427 
2428         /*
2429          * If serializable, getPrevDup should return NOTFOUND again; otherwise
2430          * getPrevDup should see {2,1}.
2431          */
2432         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2433         status = cursor.getPrevDup(key, data, null);
2434         if (txnSerializable) {
2435             assertEquals(OperationStatus.NOTFOUND, status);
2436         } else {
2437             assertEquals(OperationStatus.SUCCESS, status);
2438             assertEquals(2, IntegerBinding.entryToInt(key));
2439             assertEquals(1, IntegerBinding.entryToInt(data));
2440         }
2441 
2442         /* Close reader to allow writer to finish. */
2443         cursor.close();
2444         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2445         waitForInsert();
2446 
2447         /* getPrevDup returns {2,1}. */
2448         readerTxn = env.beginTransaction(null, txnConfig);
2449         cursor = db.openCursor(readerTxn, null);
2450         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2451         status = cursor.getPrevDup(key, data, null);
2452         assertEquals(OperationStatus.SUCCESS, status);
2453         assertEquals(2, IntegerBinding.entryToInt(key));
2454         assertEquals(1, IntegerBinding.entryToInt(data));
2455         cursor.close();
2456         readerTxn.commit();
2457 
2458         closeEnv();
2459     }
2460 
2461     @Test
testGetPrevNoDup_Success()2462     public void testGetPrevNoDup_Success()
2463         throws DatabaseException, InterruptedException {
2464 
2465         openEnv(false);
2466         DatabaseEntry key = new DatabaseEntry();
2467         DatabaseEntry data = new DatabaseEntry();
2468         OperationStatus status;
2469 
2470         /* Insert key 1 and 3. */
2471         insert(1);
2472         insert(3);
2473 
2474         /* getPrevNoDup returns key 1. */
2475         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2476         Cursor cursor = db.openCursor(readerTxn, null);
2477         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2478         status = cursor.getPrevNoDup(key, data, null);
2479         assertEquals(OperationStatus.SUCCESS, status);
2480         assertEquals(1, IntegerBinding.entryToInt(key));
2481 
2482         /* Insertions before 1 and after 3 are never blocked. */
2483         try {
2484             insert(0);
2485             insert(4);
2486         } catch (LockConflictException e) {
2487             fail();
2488         }
2489 
2490         /* Insert key 2 in a writer thread. */
2491         startInsert(2);
2492 
2493         /*
2494          * If serializable, getPrevNoDup should return key 1 again; otherwise
2495          * getPrevNoDup should see key 2.
2496          */
2497         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2498         status = cursor.getPrevNoDup(key, data, null);
2499         assertEquals(OperationStatus.SUCCESS, status);
2500         if (txnSerializable) {
2501             assertEquals(1, IntegerBinding.entryToInt(key));
2502         } else {
2503             assertEquals(2, IntegerBinding.entryToInt(key));
2504         }
2505 
2506         /* Close reader to allow writer to finish. */
2507         cursor.close();
2508         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2509         waitForInsert();
2510 
2511         /* getPrevNoDup returns key 2. */
2512         readerTxn = env.beginTransaction(null, txnConfig);
2513         cursor = db.openCursor(readerTxn, null);
2514         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 3));
2515         status = cursor.getPrevNoDup(key, data, null);
2516         assertEquals(OperationStatus.SUCCESS, status);
2517         assertEquals(2, IntegerBinding.entryToInt(key));
2518         cursor.close();
2519         readerTxn.commit();
2520 
2521         closeEnv();
2522     }
2523 
2524     @Test
testGetPrevNoDup_Success_Dup()2525     public void testGetPrevNoDup_Success_Dup()
2526         throws DatabaseException, InterruptedException {
2527 
2528         openEnv(true);
2529         DatabaseEntry key = new DatabaseEntry();
2530         DatabaseEntry data = new DatabaseEntry();
2531         OperationStatus status;
2532 
2533         /* Insert dups. */
2534         insert(1, 0);
2535         insert(1, 2);
2536         insert(3, 1);
2537         insert(3, 2);
2538 
2539         /* getPrevNoDup returns {1,2}. */
2540         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2541         Cursor cursor = db.openCursor(readerTxn, null);
2542         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 3, 2));
2543         status = cursor.getPrevNoDup(key, data, null);
2544         assertEquals(OperationStatus.SUCCESS, status);
2545         assertEquals(1, IntegerBinding.entryToInt(key));
2546         assertEquals(2, IntegerBinding.entryToInt(data));
2547 
2548         /* Insertions before {1,2} and after {3,2} are never blocked. */
2549         try {
2550             insert(1, 1);
2551             insert(0, 0);
2552             insert(3, 3);
2553             insert(4, 0);
2554         } catch (LockConflictException e) {
2555             fail();
2556         }
2557 
2558         /* Insert {2,1} in a writer thread. */
2559         startInsert(2, 1);
2560 
2561         /*
2562          * If serializable, getPrevNoDup should return {1,2} again; otherwise
2563          * getPrevNoDup should see {2,1}.
2564          */
2565         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 3, 2));
2566         status = cursor.getPrevNoDup(key, data, null);
2567         assertEquals(OperationStatus.SUCCESS, status);
2568         if (txnSerializable) {
2569             assertEquals(1, IntegerBinding.entryToInt(key));
2570             assertEquals(2, IntegerBinding.entryToInt(data));
2571         } else {
2572             assertEquals(2, IntegerBinding.entryToInt(key));
2573             assertEquals(1, IntegerBinding.entryToInt(data));
2574         }
2575 
2576         /* Close reader to allow writer to finish. */
2577         cursor.close();
2578         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2579         waitForInsert();
2580 
2581         /* getPrevNoDup returns {2,1}. */
2582         readerTxn = env.beginTransaction(null, txnConfig);
2583         cursor = db.openCursor(readerTxn, null);
2584         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 3, 2));
2585         status = cursor.getPrevNoDup(key, data, null);
2586         assertEquals(OperationStatus.SUCCESS, status);
2587         assertEquals(2, IntegerBinding.entryToInt(key));
2588         assertEquals(1, IntegerBinding.entryToInt(data));
2589         cursor.close();
2590         readerTxn.commit();
2591 
2592         closeEnv();
2593     }
2594 
2595     @Test
testGetPrevNoDup_NotFound()2596     public void testGetPrevNoDup_NotFound()
2597         throws DatabaseException, InterruptedException {
2598 
2599         openEnv(false);
2600         DatabaseEntry key = new DatabaseEntry();
2601         DatabaseEntry data = new DatabaseEntry();
2602         OperationStatus status;
2603 
2604         /* Insert key 2. */
2605         insert(2);
2606 
2607         /* getPrevNoDup returns NOTFOUND. */
2608         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2609         Cursor cursor = db.openCursor(readerTxn, null);
2610         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2611         status = cursor.getPrevNoDup(key, data, null);
2612         assertEquals(OperationStatus.NOTFOUND, status);
2613 
2614         /* Insertions after 2 are never blocked. */
2615         try {
2616             insert(3);
2617         } catch (LockConflictException e) {
2618             fail();
2619         }
2620 
2621         /* Insert key 1 in a writer thread. */
2622         startInsert(1);
2623 
2624         /*
2625          * If serializable, getPrevNoDup should return NOTFOUND again;
2626          * otherwise getPrevNoDup should see key 1.
2627          */
2628         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2629         status = cursor.getPrevNoDup(key, data, null);
2630         if (txnSerializable) {
2631             assertEquals(OperationStatus.NOTFOUND, status);
2632         } else {
2633             assertEquals(OperationStatus.SUCCESS, status);
2634             assertEquals(1, IntegerBinding.entryToInt(key));
2635         }
2636 
2637         /* Close reader to allow writer to finish. */
2638         cursor.close();
2639         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2640         waitForInsert();
2641 
2642         /* getPrevNoDup returns key 1. */
2643         readerTxn = env.beginTransaction(null, txnConfig);
2644         cursor = db.openCursor(readerTxn, null);
2645         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 2));
2646         status = cursor.getPrevNoDup(key, data, null);
2647         assertEquals(OperationStatus.SUCCESS, status);
2648         assertEquals(1, IntegerBinding.entryToInt(key));
2649         cursor.close();
2650         readerTxn.commit();
2651 
2652         closeEnv();
2653     }
2654 
2655     @Test
testGetPrevNoDup_NotFound_Dup()2656     public void testGetPrevNoDup_NotFound_Dup()
2657         throws DatabaseException, InterruptedException {
2658 
2659         openEnv(true);
2660         DatabaseEntry key = new DatabaseEntry();
2661         DatabaseEntry data = new DatabaseEntry();
2662         OperationStatus status;
2663 
2664         /* Insert dups. */
2665         insert(2, 1);
2666         insert(2, 2);
2667 
2668         /* getPrevNoDup returns NOTFOUND. */
2669         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2670         Cursor cursor = db.openCursor(readerTxn, null);
2671         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2672         status = cursor.getPrevNoDup(key, data, null);
2673         assertEquals(OperationStatus.NOTFOUND, status);
2674 
2675         /* Insertions after {2,2} are never blocked. */
2676         try {
2677             insert(2, 3);
2678             insert(3, 0);
2679         } catch (LockConflictException e) {
2680             fail();
2681         }
2682 
2683         /* Insert {1,1} in a writer thread. */
2684         startInsert(1, 1);
2685 
2686         /*
2687          * If serializable, getPrevNoDup should return NOTFOUND again;
2688          * otherwise getPrevNoDup should see {1,1}.
2689          */
2690         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2691         status = cursor.getPrevNoDup(key, data, null);
2692         if (txnSerializable) {
2693             assertEquals(OperationStatus.NOTFOUND, status);
2694         } else {
2695             assertEquals(OperationStatus.SUCCESS, status);
2696             assertEquals(1, IntegerBinding.entryToInt(key));
2697             assertEquals(1, IntegerBinding.entryToInt(data));
2698         }
2699 
2700         /* Close reader to allow writer to finish. */
2701         cursor.close();
2702         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2703         waitForInsert();
2704 
2705         /* getPrevNoDup returns {1,1}. */
2706         readerTxn = env.beginTransaction(null, txnConfig);
2707         cursor = db.openCursor(readerTxn, null);
2708         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 2));
2709         status = cursor.getPrevNoDup(key, data, null);
2710         assertEquals(OperationStatus.SUCCESS, status);
2711         assertEquals(1, IntegerBinding.entryToInt(key));
2712         assertEquals(1, IntegerBinding.entryToInt(data));
2713         cursor.close();
2714         readerTxn.commit();
2715 
2716         closeEnv();
2717     }
2718 
2719     @Test
testIllegalTransactionConfig()2720     public void testIllegalTransactionConfig()
2721         throws DatabaseException {
2722 
2723         openEnv(false);
2724         TransactionConfig config = new TransactionConfig();
2725         config.setSerializableIsolation(true);
2726         config.setReadUncommitted(true);
2727         try {
2728             Transaction txn = env.beginTransaction(null, config);
2729             txn.abort();
2730             fail();
2731         } catch (IllegalArgumentException expected) {
2732         }
2733         closeEnv();
2734     }
2735 
2736     /*
2737      * In other tests we test TransactionConfig.setReadUncommitted and
2738      * TransactionConfig.setSerializableIsolation to make sure they result in
2739      * expected non-serializable or serializable behavior.  Below we check
2740      * EnvironmentConfig.setSerializableIsolation,
2741      * CursorConfig.setSerializableIsolation, CursorConfig.setReadUncommitted
2742      * and LockMode.READ_UNCOMMITTED, although for a single test case only.
2743      */
2744 
2745     @Test
testEnvironmentConfig()2746     public void testEnvironmentConfig()
2747         throws DatabaseException {
2748 
2749         EnvironmentConfig config = TestUtils.initEnvConfig();
2750         /* Control over isolation level is required by this test. */
2751         TestUtils.clearIsolationLevel(config);
2752         checkSerializable(false, config, null, null);
2753 
2754         config.setTxnSerializableIsolation(true);
2755         checkSerializable(true, config, null, null);
2756     }
2757 
2758     @Test
testCursorConfig()2759     public void testCursorConfig()
2760         throws DatabaseException {
2761 
2762         CursorConfig config = new CursorConfig();
2763         checkSerializable(false, null, config, null);
2764 
2765         config.setReadUncommitted(true);
2766         checkSerializable(false, null, config, null);
2767     }
2768 
2769     @Test
testReadUncommittedLockMode()2770     public void testReadUncommittedLockMode()
2771         throws DatabaseException {
2772 
2773         EnvironmentConfig envConfig = TestUtils.initEnvConfig();
2774         /* Control over isolation level is required by this test. */
2775         TestUtils.clearIsolationLevel(envConfig);
2776         envConfig.setTxnSerializableIsolation(true);
2777 
2778         checkSerializable(false, envConfig, null, LockMode.READ_UNCOMMITTED);
2779     }
2780 
checkSerializable(boolean expectSerializable, EnvironmentConfig envConfig, CursorConfig cursorConfig, LockMode lockMode)2781     private void checkSerializable(boolean expectSerializable,
2782                                    EnvironmentConfig envConfig,
2783                                    CursorConfig cursorConfig,
2784                                    LockMode lockMode)
2785         throws DatabaseException {
2786 
2787         openEnv(false, envConfig);
2788         DatabaseEntry key = new DatabaseEntry();
2789         DatabaseEntry data = new DatabaseEntry();
2790         OperationStatus status;
2791 
2792         /* Insert key 2. */
2793         insert(2);
2794 
2795         /* getFirst returns key 2. */
2796         Transaction readerTxn = env.beginTransaction(null, null);
2797         Cursor cursor = db.openCursor(readerTxn, cursorConfig);
2798         status = cursor.getFirst(key, data, lockMode);
2799         assertEquals(OperationStatus.SUCCESS, status);
2800         assertEquals(2, IntegerBinding.entryToInt(key));
2801 
2802         /* Should deadlock iff serializable. */
2803         try {
2804             insert(1);
2805             assertTrue(!expectSerializable);
2806         } catch (LockConflictException e) {
2807             assertTrue(expectSerializable);
2808         }
2809 
2810         cursor.close();
2811         readerTxn.commit();
2812 
2813         /* This method is called multiple times so remove the database. */
2814         db.close();
2815         db = null;
2816         env.removeDatabase(null, DB_NAME);
2817 
2818         closeEnv();
2819     }
2820 
2821     /**
2822      * Tests that with a single degree 3 txn we don't obtain the extra lock
2823      * during insert.
2824      */
2825     @Test
testSingleDegree3TxnOptimization()2826     public void testSingleDegree3TxnOptimization()
2827         throws DatabaseException {
2828 
2829         openEnv(false);
2830 
2831         /* Insert key 2. */
2832         insert(2);
2833 
2834         StatsConfig clearStats = new StatsConfig();
2835         clearStats.setClear(true);
2836 
2837         /* Clear before inserting. */
2838         EnvironmentStats stats = env.getStats(clearStats);
2839 
2840         /* Insert key 1, which would lock key 2 while inserting. */
2841         insert(1);
2842 
2843         /* Expect a single lock was requested. */
2844         stats = env.getStats(clearStats);
2845         assertEquals(1, stats.getNRequests());
2846 
2847         closeEnv();
2848     }
2849 
2850     /**
2851      * Tests a particular getSearchBothRange bug that has come up in several
2852      * contexts.  This test is probably redundant with GetSearchBothTest but
2853      * I've left it here for good measure.
2854      */
2855     @Test
testSingleDatumBug()2856     public void testSingleDatumBug()
2857         throws DatabaseException {
2858 
2859         openEnv(true);
2860         DatabaseEntry key = new DatabaseEntry();
2861         DatabaseEntry data = new DatabaseEntry();
2862         OperationStatus status;
2863 
2864         insert(1, 1);
2865         insert(2, 2);
2866 
2867         /* getSearchBothRange for {2, 1} returns {2, 2}. */
2868         Transaction readerTxn = env.beginTransaction(null, txnConfig);
2869         Cursor cursor = db.openCursor(readerTxn, null);
2870         IntegerBinding.intToEntry(2, key);
2871         IntegerBinding.intToEntry(1, data);
2872         status = cursor.getSearchBothRange(key, data, null);
2873         assertEquals(OperationStatus.SUCCESS, status);
2874         assertEquals(2, IntegerBinding.entryToInt(key));
2875         assertEquals(2, IntegerBinding.entryToInt(data));
2876 
2877         /* If serializable, inserting in the locked range should deadlock. */
2878         try {
2879             insert(1, 2);
2880             if (txnSerializable) {
2881                 fail();
2882             }
2883         } catch (LockConflictException e) {
2884             if (!txnSerializable) {
2885                 fail();
2886             }
2887         }
2888 
2889         cursor.close();
2890         readerTxn.commit(Durability.COMMIT_NO_SYNC);
2891         closeEnv();
2892     }
2893 
2894     /**
2895      * Tests that searchKey returns SUCCESS when it must skip over a deleted
2896      * duplicate.  This did not work at one point and was causing warnings
2897      * (Cursor Not Initialized) in duplicate.conf testing.
2898      */
2899     @Test
testSearchKeySkipDeletedDup()2900     public void testSearchKeySkipDeletedDup()
2901         throws DatabaseException {
2902 
2903         openEnv(true);
2904 
2905         /* Insert {1,1} and {1,2}. */
2906         insert(1, 1);
2907         insert(1, 2);
2908 
2909         /* Delete {1,1}. */
2910         Transaction txn = env.beginTransaction(null, txnConfig);
2911         Cursor cursor = db.openCursor(txn, null);
2912         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 1, 1));
2913         OperationStatus status = cursor.delete();
2914         assertEquals(OperationStatus.SUCCESS, status);
2915 
2916         /* Search for key 1 -- should not return NOTFOUND. */
2917         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 2));
2918 
2919         cursor.close();
2920         txn.commit(Durability.COMMIT_NO_SYNC);
2921         closeEnv();
2922     }
2923 
2924     /**
2925      * Tests that getNextDup returns NOTFOUND when it skips over a deleted
2926      * duplicate for the following main key.  [#19026]
2927      */
2928     @Test
testNextAfterDupDeleteBug()2929     public void testNextAfterDupDeleteBug()
2930         throws DatabaseException, InterruptedException {
2931 
2932         openEnv(true);
2933         DatabaseEntry key = new DatabaseEntry();
2934         DatabaseEntry data = new DatabaseEntry();
2935         OperationStatus status;
2936 
2937         /* Insert dups. */
2938         insert(1, 1);
2939         insert(2, 1);
2940         insert(2, 2);
2941 
2942         /* Delete {2,1}. */
2943         Transaction txn = env.beginTransaction(null, txnConfig);
2944         Cursor cursor = db.openCursor(txn, null);
2945         assertEquals(OperationStatus.SUCCESS, searchBoth(cursor, 2, 1));
2946         assertEquals(OperationStatus.SUCCESS, cursor.delete());
2947         cursor.close();
2948         txn.commit(Durability.COMMIT_NO_SYNC);
2949 
2950         /*
2951          * When positioned on {1,1}, getNextDup should always return NOTFOUND.
2952          * A bug (fixed in [#19026]) caused the cursor to move to {2,2} and
2953          * return SUCCESS.  This only occurred with serializable isolation, and
2954          * when the deleted {2,1} record had not been compressed. The
2955          * underlying cause is that CursorImpl.getNextWithKeyChangeStatus was
2956          * not indicating a key change when skipping over the first deleted
2957          * duplicate ({2,1} in this case) in the duplicate set.
2958          */
2959         txn = env.beginTransaction(null, txnConfig);
2960         cursor = db.openCursor(txn, null);
2961         assertEquals(OperationStatus.SUCCESS, searchKey(cursor, 1, 1));
2962         status = cursor.getNextDup(key, data, null);
2963         assertEquals(OperationStatus.NOTFOUND, status);
2964         cursor.close();
2965         txn.commit(Durability.COMMIT_NO_SYNC);
2966 
2967         closeEnv();
2968     }
2969 
2970     /**
2971      * Performs getSearchKey on the given key, expects data to be zero.
2972      */
searchKey(Cursor cursor, int keyVal)2973     private OperationStatus searchKey(Cursor cursor, int keyVal)
2974         throws DatabaseException {
2975 
2976         return searchKey(cursor, keyVal, 0);
2977     }
2978 
2979     /**
2980      * Performs getSearchKey on the given key, expects given data value.
2981      */
searchKey(Cursor cursor, int keyVal, int dataVal)2982     private OperationStatus searchKey(Cursor cursor, int keyVal, int dataVal)
2983         throws DatabaseException {
2984 
2985         DatabaseEntry key = new DatabaseEntry();
2986         DatabaseEntry data = new DatabaseEntry();
2987         IntegerBinding.intToEntry(keyVal, key);
2988         OperationStatus status = cursor.getSearchKey(key, data, null);
2989         if (status == OperationStatus.SUCCESS) {
2990             assertEquals(keyVal, IntegerBinding.entryToInt(key));
2991             assertEquals(dataVal, IntegerBinding.entryToInt(data));
2992         }
2993         return status;
2994     }
2995 
2996     /**
2997      * Performs getSearchBoth on the given key and zero data.
2998      */
searchBoth(Cursor cursor, int keyVal)2999     private OperationStatus searchBoth(Cursor cursor, int keyVal)
3000         throws DatabaseException {
3001 
3002         return searchBoth(cursor, keyVal, 0, false);
3003     }
3004 
3005     /**
3006      * Performs getSearchBoth on the given key and zero data.
3007      *
3008      * getSearchBoth and getSearchBothRange are equivalent for a non-dup DB, so
3009      * we allowing testing either.
3010      */
searchBoth(Cursor cursor, int keyVal, boolean useRangeSearch)3011     private OperationStatus searchBoth(Cursor cursor,
3012                                        int keyVal,
3013                                        boolean useRangeSearch)
3014         throws DatabaseException {
3015 
3016         return searchBoth(cursor, keyVal, 0, useRangeSearch);
3017     }
3018 
3019     /**
3020      * Performs getSearchBoth on the given key and data.
3021      */
searchBoth(Cursor cursor, int keyVal, int dataVal)3022     private OperationStatus searchBoth(Cursor cursor, int keyVal, int dataVal)
3023         throws DatabaseException {
3024 
3025         return searchBoth(cursor, keyVal, dataVal, false);
3026     }
3027 
3028     /**
3029      * Performs getSearchBoth on the given key and data.
3030      *
3031      * getSearchBoth and getSearchBothRange are equivalent for a non-dup DB, so
3032      * we allowing testing either.
3033      */
searchBoth(Cursor cursor, int keyVal, int dataVal, boolean useRangeSearch)3034     private OperationStatus searchBoth(Cursor cursor,
3035                                        int keyVal,
3036                                        int dataVal,
3037                                        boolean useRangeSearch)
3038         throws DatabaseException {
3039 
3040         DatabaseEntry key = new DatabaseEntry();
3041         DatabaseEntry data = new DatabaseEntry();
3042         IntegerBinding.intToEntry(keyVal, key);
3043         IntegerBinding.intToEntry(dataVal, data);
3044         OperationStatus status;
3045         if (useRangeSearch) {
3046             status = cursor.getSearchBothRange(key, data, null);
3047         } else {
3048             status = cursor.getSearchBoth(key, data, null);
3049         }
3050         if (status == OperationStatus.SUCCESS) {
3051             assertEquals(keyVal, IntegerBinding.entryToInt(key));
3052             assertEquals(dataVal, IntegerBinding.entryToInt(data));
3053         }
3054         return status;
3055     }
3056 
3057     /**
3058      * Inserts the given key in a new transaction and commits it.
3059      */
insert(int keyVal)3060     private void insert(int keyVal)
3061         throws DatabaseException {
3062 
3063         insert(keyVal, 0);
3064     }
3065 
3066     /**
3067      * Inserts the given key and data in a new transaction and commits it.
3068      */
insert(int keyVal, int dataVal)3069     private void insert(int keyVal, int dataVal)
3070         throws DatabaseException {
3071 
3072         DatabaseEntry key = new DatabaseEntry();
3073         DatabaseEntry data = new DatabaseEntry();
3074         IntegerBinding.intToEntry(keyVal, key);
3075         IntegerBinding.intToEntry(dataVal, data);
3076         OperationStatus status;
3077         Transaction writerTxn = env.beginTransaction(null, txnConfig);
3078         try {
3079             if (dups) {
3080                 status = db.putNoDupData(writerTxn, key, data);
3081             } else {
3082                 status = db.putNoOverwrite(writerTxn, key, data);
3083             }
3084         } catch (LockConflictException e) {
3085             writerTxn.abort();
3086             throw e;
3087         }
3088         assertEquals(OperationStatus.SUCCESS, status);
3089         writerTxn.commit(Durability.COMMIT_NO_SYNC);
3090     }
3091 
3092     /**
3093      * Starts writer thread and waits for it to start the insert.
3094      */
startInsert(final int keyVal)3095     private void startInsert(final int keyVal)
3096         throws DatabaseException, InterruptedException {
3097 
3098         startInsert(keyVal, 0);
3099     }
3100 
3101     /**
3102      * Starts writer thread and waits for it to start the insert.
3103      */
startInsert(final int keyVal, final int dataVal)3104     private void startInsert(final int keyVal, final int dataVal)
3105         throws DatabaseException, InterruptedException {
3106 
3107         EnvironmentStats origStats = env.getStats(null);
3108         insertFinished = false;
3109 
3110         writerThread = new JUnitThread("Writer") {
3111             public void testBody()
3112                 throws DatabaseException {
3113                 DatabaseEntry key = new DatabaseEntry();
3114                 DatabaseEntry data = new DatabaseEntry();
3115                 OperationStatus status;
3116                 IntegerBinding.intToEntry(keyVal, key);
3117                 IntegerBinding.intToEntry(dataVal, data);
3118                 Transaction writerTxn = env.beginTransaction(null, txnConfig);
3119                 if (dups) {
3120                     status = db.putNoDupData(writerTxn, key, data);
3121                 } else {
3122                     status = db.putNoOverwrite(writerTxn, key, data);
3123                 }
3124                 assertEquals(OperationStatus.SUCCESS, status);
3125                 writerTxn.commit(Durability.COMMIT_NO_SYNC);
3126                 insertFinished = true;
3127             }
3128         };
3129 
3130         writerThread.start();
3131 
3132         long startTime = System.currentTimeMillis();
3133         while (true) {
3134 
3135             /* Give some time to the writer thread. */
3136             Thread.yield();
3137             Thread.sleep(10);
3138             if (System.currentTimeMillis() - startTime > MAX_INSERT_MILLIS) {
3139                 fail("Timeout doing insert");
3140             }
3141 
3142             if (txnSerializable) {
3143 
3144                 /* Wait for the insert to block. */
3145                 EnvironmentStats stats = env.getStats(null);
3146                 if (stats.getNWaiters() > origStats.getNWaiters()) {
3147                     break;
3148                 }
3149             } else {
3150 
3151                 /* Wait for the operation to complete. */
3152                 if (insertFinished) {
3153                     insertFinished = false;
3154                     break;
3155                 }
3156             }
3157         }
3158     }
3159 
3160     /**
3161      * Waits for the writer thread to finish.
3162      */
waitForInsert()3163     private void waitForInsert() {
3164 
3165         try {
3166             writerThread.finishTest();
3167         } catch (Throwable e) {
3168             e.printStackTrace();
3169             fail(e.toString());
3170         } finally {
3171             writerThread = null;
3172         }
3173     }
3174 }
3175