1 /*-
2  * Copyright (c) 2002, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file LICENSE for license information.
5  *
6  */
7 
8 package com.sleepycat.persist.test;
9 
10 import static com.sleepycat.persist.model.DeleteAction.CASCADE;
11 import static com.sleepycat.persist.model.DeleteAction.NULLIFY;
12 import static com.sleepycat.persist.model.Relationship.MANY_TO_MANY;
13 import static com.sleepycat.persist.model.Relationship.MANY_TO_ONE;
14 import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY;
15 import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
16 import static org.junit.Assert.assertEquals;
17 import static org.junit.Assert.assertNotNull;
18 import static org.junit.Assert.assertNull;
19 import static org.junit.Assert.assertSame;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assert.fail;
22 
23 import java.util.ArrayList;
24 import java.util.EnumSet;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 
31 import com.sleepycat.compat.DbCompat;
32 import com.sleepycat.db.Database;
33 import com.sleepycat.db.DatabaseConfig;
34 import com.sleepycat.db.DatabaseException;
35 import com.sleepycat.db.StatsConfig;
36 import com.sleepycat.db.Transaction;
37 import com.sleepycat.persist.EntityCursor;
38 import com.sleepycat.persist.EntityIndex;
39 import com.sleepycat.persist.EntityStore;
40 import com.sleepycat.persist.PrimaryIndex;
41 import com.sleepycat.persist.SecondaryIndex;
42 import com.sleepycat.persist.StoreConfig;
43 import com.sleepycat.persist.impl.Store;
44 import com.sleepycat.persist.model.Entity;
45 import com.sleepycat.persist.model.KeyField;
46 import com.sleepycat.persist.model.NotPersistent;
47 import com.sleepycat.persist.model.NotTransient;
48 import com.sleepycat.persist.model.Persistent;
49 import com.sleepycat.persist.model.PrimaryKey;
50 import com.sleepycat.persist.model.SecondaryKey;
51 import com.sleepycat.persist.raw.RawStore;
52 import com.sleepycat.util.test.TxnTestCase;
53 
54 import org.junit.After;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.junit.runners.Parameterized;
58 import org.junit.runners.Parameterized.Parameters;
59 
60 /**
61  * Tests misc store and index operations that are not tested by IndexTest.
62  *
63  * @author Mark Hayes
64  */
65 @RunWith(Parameterized.class)
66 public class OperationTest extends TxnTestCase {
67 
68     private static final String STORE_NAME = "test";
69 
70     @Parameters
genParams()71     public static List<Object[]> genParams() {
72         return getTxnParams(null, false);
73     }
74 
OperationTest(String type)75     public OperationTest(String type){
76         initEnvConfig();
77         txnType = type;
78         isTransactional = (txnType != TXN_NULL);
79         customName = txnType;
80     }
81 
82     private EntityStore store;
83 
openReadOnly()84     private void openReadOnly()
85         throws DatabaseException {
86 
87         StoreConfig config = new StoreConfig();
88         config.setReadOnly(true);
89         open(config);
90     }
91 
open()92     private void open()
93         throws DatabaseException {
94 
95         open((Class) null);
96     }
97 
open(Class clsToRegister)98     private void open(Class clsToRegister)
99         throws DatabaseException {
100 
101         StoreConfig config = new StoreConfig();
102         config.setAllowCreate(envConfig.getAllowCreate());
103         if (clsToRegister != null) {
104             com.sleepycat.persist.model.EntityModel model =
105                 new com.sleepycat.persist.model.AnnotationModel();
106             model.registerClass(clsToRegister);
107             config.setModel(model);
108         }
109         open(config);
110     }
111 
open(StoreConfig config)112     private void open(StoreConfig config)
113         throws DatabaseException {
114 
115         config.setTransactional(envConfig.getTransactional());
116         store = new EntityStore(env, STORE_NAME, config);
117     }
118 
close()119     private void close()
120         throws DatabaseException {
121 
122         store.close();
123         store = null;
124     }
125 
126     /**
127      * The store must be closed before closing the environment.
128      */
129     @After
tearDown()130     public void tearDown()
131         throws Exception {
132 
133         try {
134             if (store != null) {
135                 store.close();
136             }
137         } catch (Throwable e) {
138             System.out.println("During tearDown: " + e);
139         }
140         store = null;
141         super.tearDown();
142     }
143 
144     @Test
testReadOnly()145     public void testReadOnly()
146         throws DatabaseException {
147 
148         open();
149         PrimaryIndex<Integer, SharedSequenceEntity1> priIndex =
150             store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
151         Transaction txn = txnBegin();
152         SharedSequenceEntity1 e = new SharedSequenceEntity1();
153         priIndex.put(txn, e);
154         assertEquals(1, e.key);
155         txnCommit(txn);
156         close();
157 
158         /*
159          * Check that we can open the store read-only and read the records
160          * written above.
161          */
162         openReadOnly();
163         priIndex =
164             store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
165         e = priIndex.get(1);
166         assertNotNull(e);
167         close();
168     }
169 
170 
171 
172     @Test
testUninitializedCursor()173     public void testUninitializedCursor()
174         throws DatabaseException {
175 
176         open();
177 
178         PrimaryIndex<Integer, MyEntity> priIndex =
179             store.getPrimaryIndex(Integer.class, MyEntity.class);
180 
181         Transaction txn = txnBeginCursor();
182 
183         MyEntity e = new MyEntity();
184         e.priKey = 1;
185         e.secKey = 1;
186         priIndex.put(txn, e);
187 
188         EntityCursor<MyEntity> entities =
189             priIndex.entities(txn, getWriteCursorConfig());
190         try {
191             entities.nextDup();
192             fail();
193         } catch (IllegalStateException expected) {}
194         try {
195             entities.prevDup();
196             fail();
197         } catch (IllegalStateException expected) {}
198         try {
199             entities.current();
200             fail();
201         } catch (IllegalStateException expected) {}
202         try {
203             entities.delete();
204             fail();
205         } catch (IllegalStateException expected) {}
206         try {
207             entities.update(e);
208             fail();
209         } catch (IllegalStateException expected) {}
210         try {
211             entities.count();
212             fail();
213         } catch (IllegalStateException expected) {}
214 
215         entities.close();
216         txnCommit(txn);
217         close();
218     }
219 
220     @Test
testCursorCount()221     public void testCursorCount()
222         throws DatabaseException {
223 
224         open();
225 
226         PrimaryIndex<Integer, MyEntity> priIndex =
227             store.getPrimaryIndex(Integer.class, MyEntity.class);
228 
229         SecondaryIndex<Integer, Integer, MyEntity> secIndex =
230             store.getSecondaryIndex(priIndex, Integer.class, "secKey");
231 
232         Transaction txn = txnBeginCursor();
233 
234         MyEntity e = new MyEntity();
235         e.priKey = 1;
236         e.secKey = 1;
237         priIndex.put(txn, e);
238 
239         EntityCursor<MyEntity> cursor = secIndex.entities(txn, null);
240         cursor.next();
241         assertEquals(1, cursor.count());
242         cursor.close();
243 
244         e.priKey = 2;
245         priIndex.put(txn, e);
246         cursor = secIndex.entities(txn, null);
247         cursor.next();
248         assertEquals(2, cursor.count());
249         cursor.close();
250 
251         txnCommit(txn);
252         close();
253     }
254 
255     @Test
testCursorUpdate()256     public void testCursorUpdate()
257         throws DatabaseException {
258 
259         open();
260 
261         PrimaryIndex<Integer, MyEntity> priIndex =
262             store.getPrimaryIndex(Integer.class, MyEntity.class);
263 
264         SecondaryIndex<Integer, Integer, MyEntity> secIndex =
265             store.getSecondaryIndex(priIndex, Integer.class, "secKey");
266 
267         Transaction txn = txnBeginCursor();
268 
269         Integer k;
270         MyEntity e = new MyEntity();
271         e.priKey = 1;
272         e.secKey = 2;
273         priIndex.put(txn, e);
274 
275         /* update() with primary entity cursor. */
276         EntityCursor<MyEntity> entities =
277             priIndex.entities(txn, getWriteCursorConfig());
278         e = entities.next();
279         assertNotNull(e);
280         assertEquals(1, e.priKey);
281         assertEquals(Integer.valueOf(2), e.secKey);
282         e.secKey = null;
283         assertTrue(entities.update(e));
284         e = entities.current();
285         assertNotNull(e);
286         assertEquals(1, e.priKey);
287         assertEquals(null, e.secKey);
288         e.secKey = 3;
289         assertTrue(entities.update(e));
290         e = entities.current();
291         assertNotNull(e);
292         assertEquals(1, e.priKey);
293         assertEquals(Integer.valueOf(3), e.secKey);
294         entities.close();
295 
296         /* update() with primary keys cursor. */
297         EntityCursor<Integer> keys = priIndex.keys(txn,
298                                                    getWriteCursorConfig());
299         k = keys.next();
300         assertNotNull(k);
301         assertEquals(Integer.valueOf(1), k);
302         try {
303             keys.update(2);
304             fail();
305         } catch (UnsupportedOperationException expected) {
306         }
307         keys.close();
308 
309         /* update() with secondary entity cursor. */
310         entities = secIndex.entities(txn, null);
311         e = entities.next();
312         assertNotNull(e);
313         assertEquals(1, e.priKey);
314         assertEquals(Integer.valueOf(3), e.secKey);
315         try {
316             entities.update(e);
317             fail();
318         } catch (UnsupportedOperationException expected) {
319         } catch (IllegalArgumentException expectedForDbCore) {
320         }
321         entities.close();
322 
323         /* update() with secondary keys cursor. */
324         keys = secIndex.keys(txn, null);
325         k = keys.next();
326         assertNotNull(k);
327         assertEquals(Integer.valueOf(3), k);
328         try {
329             keys.update(k);
330             fail();
331         } catch (UnsupportedOperationException expected) {
332         }
333         keys.close();
334 
335         txnCommit(txn);
336         close();
337     }
338 
339     @Test
testCursorDelete()340     public void testCursorDelete()
341         throws DatabaseException {
342 
343         open();
344 
345         PrimaryIndex<Integer, MyEntity> priIndex =
346             store.getPrimaryIndex(Integer.class, MyEntity.class);
347 
348         SecondaryIndex<Integer, Integer, MyEntity> secIndex =
349             store.getSecondaryIndex(priIndex, Integer.class, "secKey");
350 
351         Transaction txn = txnBeginCursor();
352 
353         /* delete() with primary and secondary entities cursor. */
354 
355         for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
356 
357             MyEntity e = new MyEntity();
358             e.priKey = 1;
359             e.secKey = 1;
360             priIndex.put(txn, e);
361             e.priKey = 2;
362             priIndex.put(txn, e);
363 
364             EntityCursor<MyEntity> cursor =
365                 index.entities(txn, getWriteCursorConfig());
366 
367             e = cursor.next();
368             assertNotNull(e);
369             assertEquals(1, e.priKey);
370             e = cursor.current();
371             assertNotNull(e);
372             assertEquals(1, e.priKey);
373             assertTrue(cursor.delete());
374             assertTrue(!cursor.delete());
375             assertNull(cursor.current());
376 
377             e = cursor.next();
378             assertNotNull(e);
379             assertEquals(2, e.priKey);
380             e = cursor.current();
381             assertNotNull(e);
382             assertEquals(2, e.priKey);
383             assertTrue(cursor.delete());
384             assertTrue(!cursor.delete());
385             assertNull(cursor.current());
386 
387             e = cursor.next();
388             assertNull(e);
389 
390             if (index == priIndex) {
391                 e = new MyEntity();
392                 e.priKey = 2;
393                 e.secKey = 1;
394                 assertTrue(!cursor.update(e));
395             }
396 
397             cursor.close();
398         }
399 
400         /* delete() with primary and secondary keys cursor. */
401 
402         for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
403 
404             MyEntity e = new MyEntity();
405             e.priKey = 1;
406             e.secKey = 1;
407             priIndex.put(txn, e);
408             e.priKey = 2;
409             priIndex.put(txn, e);
410 
411             EntityCursor<Integer> cursor = index.keys(txn,
412                                                       getWriteCursorConfig());
413 
414             Integer k = cursor.next();
415             assertNotNull(k);
416             assertEquals(1, k.intValue());
417             k = cursor.current();
418             assertNotNull(k);
419             assertEquals(1, k.intValue());
420             assertTrue(cursor.delete());
421             assertTrue(!cursor.delete());
422             assertNull(cursor.current());
423 
424             int expectKey = (index == priIndex) ? 2 : 1;
425             k = cursor.next();
426             assertNotNull(k);
427             assertEquals(expectKey, k.intValue());
428             k = cursor.current();
429             assertNotNull(k);
430             assertEquals(expectKey, k.intValue());
431             assertTrue(cursor.delete());
432             assertTrue(!cursor.delete());
433             assertNull(cursor.current());
434 
435             k = cursor.next();
436             assertNull(k);
437 
438             cursor.close();
439         }
440 
441         txnCommit(txn);
442         close();
443     }
444 
445     @Test
testDeleteFromSubIndex()446     public void testDeleteFromSubIndex()
447         throws DatabaseException {
448 
449         open();
450 
451         PrimaryIndex<Integer, MyEntity> priIndex =
452             store.getPrimaryIndex(Integer.class, MyEntity.class);
453 
454         SecondaryIndex<Integer, Integer, MyEntity> secIndex =
455             store.getSecondaryIndex(priIndex, Integer.class, "secKey");
456 
457         Transaction txn = txnBegin();
458         MyEntity e = new MyEntity();
459         e.secKey = 1;
460         e.priKey = 1;
461         priIndex.put(txn, e);
462         e.priKey = 2;
463         priIndex.put(txn, e);
464         e.priKey = 3;
465         priIndex.put(txn, e);
466         e.priKey = 4;
467         priIndex.put(txn, e);
468         txnCommit(txn);
469 
470         EntityIndex<Integer, MyEntity> subIndex = secIndex.subIndex(1);
471         txn = txnBeginCursor();
472         e = subIndex.get(txn, 1, null);
473         assertEquals(1, e.priKey);
474         assertEquals(Integer.valueOf(1), e.secKey);
475         e = subIndex.get(txn, 2, null);
476         assertEquals(2, e.priKey);
477         assertEquals(Integer.valueOf(1), e.secKey);
478         e = subIndex.get(txn, 3, null);
479         assertEquals(3, e.priKey);
480         assertEquals(Integer.valueOf(1), e.secKey);
481         e = subIndex.get(txn, 5, null);
482         assertNull(e);
483 
484         boolean deleted = subIndex.delete(txn, 1);
485         assertTrue(deleted);
486         assertNull(subIndex.get(txn, 1, null));
487         assertNotNull(subIndex.get(txn, 2, null));
488 
489         EntityCursor<MyEntity> cursor =
490             subIndex.entities(txn, getWriteCursorConfig());
491         boolean saw4 = false;
492         for (MyEntity e2 = cursor.first(); e2 != null; e2 = cursor.next()) {
493             if (e2.priKey == 3) {
494                 cursor.delete();
495             }
496             if (e2.priKey == 4) {
497                 saw4 = true;
498             }
499         }
500         cursor.close();
501         assertTrue(saw4);
502         assertNull(subIndex.get(txn, 1, null));
503         assertNull(subIndex.get(txn, 3, null));
504         assertNotNull(subIndex.get(txn, 2, null));
505         assertNotNull(subIndex.get(txn, 4, null));
506 
507         txnCommit(txn);
508         close();
509     }
510 
511     @Entity
512     static class MyEntity {
513 
514         @PrimaryKey
515         private int priKey;
516 
517         @SecondaryKey(relate=MANY_TO_ONE)
518         private Integer secKey;
519 
MyEntity()520         private MyEntity() {}
521     }
522 
523     @Test
testSharedSequence()524     public void testSharedSequence()
525         throws DatabaseException {
526 
527         open();
528 
529         PrimaryIndex<Integer, SharedSequenceEntity1> priIndex1 =
530             store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
531 
532         PrimaryIndex<Integer, SharedSequenceEntity2> priIndex2 =
533             store.getPrimaryIndex(Integer.class, SharedSequenceEntity2.class);
534 
535         Transaction txn = txnBegin();
536         SharedSequenceEntity1 e1 = new SharedSequenceEntity1();
537         SharedSequenceEntity2 e2 = new SharedSequenceEntity2();
538         priIndex1.put(txn, e1);
539         assertEquals(1, e1.key);
540         priIndex2.putNoOverwrite(txn, e2);
541         assertEquals(Integer.valueOf(2), e2.key);
542         e1.key = 0;
543         priIndex1.putNoOverwrite(txn, e1);
544         assertEquals(3, e1.key);
545         e2.key = null;
546         priIndex2.put(txn, e2);
547         assertEquals(Integer.valueOf(4), e2.key);
548         txnCommit(txn);
549 
550         close();
551     }
552 
553     @Entity
554     static class SharedSequenceEntity1 {
555 
556         @PrimaryKey(sequence="shared")
557         private int key;
558     }
559 
560     @Entity
561     static class SharedSequenceEntity2 {
562 
563         @PrimaryKey(sequence="shared")
564         private Integer key;
565     }
566 
567     @Test
testSeparateSequence()568     public void testSeparateSequence()
569         throws DatabaseException {
570 
571         open();
572 
573         PrimaryIndex<Integer, SeparateSequenceEntity1> priIndex1 =
574             store.getPrimaryIndex
575                 (Integer.class, SeparateSequenceEntity1.class);
576 
577         PrimaryIndex<Integer, SeparateSequenceEntity2> priIndex2 =
578             store.getPrimaryIndex
579                 (Integer.class, SeparateSequenceEntity2.class);
580 
581         Transaction txn = txnBegin();
582         SeparateSequenceEntity1 e1 = new SeparateSequenceEntity1();
583         SeparateSequenceEntity2 e2 = new SeparateSequenceEntity2();
584         priIndex1.put(txn, e1);
585         assertEquals(1, e1.key);
586         priIndex2.putNoOverwrite(txn, e2);
587         assertEquals(Integer.valueOf(1), e2.key);
588         e1.key = 0;
589         priIndex1.putNoOverwrite(txn, e1);
590         assertEquals(2, e1.key);
591         e2.key = null;
592         priIndex2.put(txn, e2);
593         assertEquals(Integer.valueOf(2), e2.key);
594         txnCommit(txn);
595 
596         close();
597     }
598 
599     @Entity
600     static class SeparateSequenceEntity1 {
601 
602         @PrimaryKey(sequence="seq1")
603         private int key;
604     }
605 
606     @Entity
607     static class SeparateSequenceEntity2 {
608 
609         @PrimaryKey(sequence="seq2")
610         private Integer key;
611     }
612 
613     @Test
testCompositeSequence()614     public void testCompositeSequence()
615         throws DatabaseException {
616 
617         open();
618 
619         PrimaryIndex<CompositeSequenceEntity1.Key, CompositeSequenceEntity1>
620             priIndex1 =
621             store.getPrimaryIndex
622                 (CompositeSequenceEntity1.Key.class,
623                  CompositeSequenceEntity1.class);
624 
625         PrimaryIndex<CompositeSequenceEntity2.Key, CompositeSequenceEntity2>
626             priIndex2 =
627             store.getPrimaryIndex
628                 (CompositeSequenceEntity2.Key.class,
629                  CompositeSequenceEntity2.class);
630 
631         Transaction txn = txnBegin();
632         CompositeSequenceEntity1 e1 = new CompositeSequenceEntity1();
633         CompositeSequenceEntity2 e2 = new CompositeSequenceEntity2();
634         priIndex1.put(txn, e1);
635         assertEquals(1, e1.key.key);
636         priIndex2.putNoOverwrite(txn, e2);
637         assertEquals(Integer.valueOf(1), e2.key.key);
638         e1.key = null;
639         priIndex1.putNoOverwrite(txn, e1);
640         assertEquals(2, e1.key.key);
641         e2.key = null;
642         priIndex2.put(txn, e2);
643         assertEquals(Integer.valueOf(2), e2.key.key);
644         txnCommit(txn);
645 
646         txn = txnBeginCursor();
647         EntityCursor<CompositeSequenceEntity1> c1 =
648             priIndex1.entities(txn, null);
649         e1 = c1.next();
650         assertEquals(2, e1.key.key);
651         e1 = c1.next();
652         assertEquals(1, e1.key.key);
653         e1 = c1.next();
654         assertNull(e1);
655         c1.close();
656         txnCommit(txn);
657 
658         txn = txnBeginCursor();
659         EntityCursor<CompositeSequenceEntity2> c2 =
660             priIndex2.entities(txn, null);
661         e2 = c2.next();
662         assertEquals(Integer.valueOf(2), e2.key.key);
663         e2 = c2.next();
664         assertEquals(Integer.valueOf(1), e2.key.key);
665         e2 = c2.next();
666         assertNull(e2);
667         c2.close();
668         txnCommit(txn);
669 
670         close();
671     }
672 
673     @Entity
674     static class CompositeSequenceEntity1 {
675 
676         @Persistent
677         static class Key implements Comparable<Key> {
678 
679             @KeyField(1)
680             private int key;
681 
compareTo(Key o)682             public int compareTo(Key o) {
683                 /* Reverse the natural order. */
684                 return o.key - key;
685             }
686         }
687 
688         @PrimaryKey(sequence="seq1")
689         private Key key;
690     }
691 
692     /**
693      * Same as CompositeSequenceEntity1 but using Integer rather than int for
694      * the key type.
695      */
696     @Entity
697     static class CompositeSequenceEntity2 {
698 
699         @Persistent
700         static class Key implements Comparable<Key> {
701 
702             @KeyField(1)
703             private Integer key;
704 
compareTo(Key o)705             public int compareTo(Key o) {
706                 /* Reverse the natural order. */
707                 return o.key - key;
708             }
709         }
710 
711         @PrimaryKey(sequence="seq2")
712         private Key key;
713     }
714 
715     /**
716      * When opening read-only, secondaries are not opened when the primary is
717      * opened, causing a different code path to be used for opening
718      * secondaries.  For a RawStore in particular, this caused an unreported
719      * NullPointerException in JE 3.0.12.  No SR was created because the use
720      * case is very obscure and was discovered by code inspection.
721      */
722     @Test
testOpenRawStoreReadOnly()723     public void testOpenRawStoreReadOnly()
724         throws DatabaseException {
725 
726         open();
727         store.getPrimaryIndex(Integer.class, MyEntity.class);
728         close();
729 
730         StoreConfig config = new StoreConfig();
731         config.setReadOnly(true);
732         config.setTransactional(envConfig.getTransactional());
733         RawStore rawStore = new RawStore(env, "test", config);
734 
735         String clsName = MyEntity.class.getName();
736         rawStore.getSecondaryIndex(clsName, "secKey");
737 
738         rawStore.close();
739     }
740 
741     /**
742      * When opening an X_TO_MANY secondary that has a persistent key class, the
743      * key class was not recognized as being persistent if it was never before
744      * referenced when getSecondaryIndex was called.  This was a bug in JE
745      * 3.0.12, reported on OTN.  [#15103]
746      */
747     @Test
testToManyKeyClass()748     public void testToManyKeyClass()
749         throws DatabaseException {
750 
751         open();
752 
753         PrimaryIndex<Integer, ToManyKeyEntity> priIndex =
754             store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
755         SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex =
756             store.getSecondaryIndex(priIndex, ToManyKey.class, "key2");
757 
758         priIndex.put(new ToManyKeyEntity());
759         secIndex.get(new ToManyKey());
760 
761         close();
762     }
763 
764     /**
765      * Test a fix for a bug where opening a TO_MANY secondary index would fail
766      * fail with "IllegalArgumentException: Wrong secondary key class: ..."
767      * when the store was opened read-only.  [#15156]
768      */
769     @Test
testToManyReadOnly()770     public void testToManyReadOnly()
771         throws DatabaseException {
772 
773         open();
774         PrimaryIndex<Integer, ToManyKeyEntity> priIndex =
775             store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
776         priIndex.put(new ToManyKeyEntity());
777         close();
778 
779         openReadOnly();
780         priIndex = store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
781         SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex =
782             store.getSecondaryIndex(priIndex, ToManyKey.class, "key2");
783         secIndex.get(new ToManyKey());
784         close();
785     }
786 
787     @Persistent
788     static class ToManyKey {
789 
790         @KeyField(1)
791         int value = 99;
792     }
793 
794     @Entity
795     static class ToManyKeyEntity {
796 
797         @PrimaryKey
798         int key = 88;
799 
800         @SecondaryKey(relate=ONE_TO_MANY)
801         Set<ToManyKey> key2;
802 
ToManyKeyEntity()803         ToManyKeyEntity() {
804             key2 = new HashSet<ToManyKey>();
805             key2.add(new ToManyKey());
806         }
807     }
808 
809 
810 
811     /**
812      * When Y is opened and X has a key with relatedEntity=Y.class, X should
813      * be opened automatically.  If X is not opened, foreign key constraints
814      * will not be enforced. [#15358]
815      */
816     @Test
testAutoOpenRelatedEntity()817     public void testAutoOpenRelatedEntity()
818         throws DatabaseException {
819 
820         PrimaryIndex<Integer, RelatedY> priY;
821         PrimaryIndex<Integer, RelatedX> priX;
822 
823         /* Opening X should create (and open) Y and enforce constraints. */
824         open();
825         priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
826         PersistTestUtils.assertDbExists
827             (true, env, STORE_NAME, RelatedY.class.getName(), null);
828         if (isTransactional) {
829             /* Constraint enforcement requires transactions. */
830             try {
831                 priX.put(new RelatedX());
832                 fail();
833             } catch (DatabaseException e) {
834                 assertTrue
835                     ("" + e.getMessage(), (e.getMessage().indexOf
836                       ("foreign key not allowed: it is not present") >= 0) ||
837                      (e.getMessage().indexOf("DB_FOREIGN_CONFLICT") >= 0));
838             }
839         }
840         priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
841         priY.put(new RelatedY());
842         priX.put(new RelatedX());
843         close();
844 
845         /* Delete should cascade even when X is not opened explicitly. */
846         open();
847         priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
848         assertEquals(1, priY.count());
849         priY.delete(88);
850         assertEquals(0, priY.count());
851         priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
852         assertEquals(0, priX.count()); /* Failed prior to [#15358] fix. */
853         close();
854     }
855 
856     @Entity
857     static class RelatedX {
858 
859         @PrimaryKey
860         int key = 99;
861 
862         @SecondaryKey(relate=ONE_TO_ONE,
863                       relatedEntity=RelatedY.class,
864                       onRelatedEntityDelete=CASCADE)
865         int key2 = 88;
866 
RelatedX()867         RelatedX() {
868         }
869     }
870 
871     @Entity
872     static class RelatedY {
873 
874         @PrimaryKey
875         int key = 88;
876 
RelatedY()877         RelatedY() {
878         }
879     }
880 
881     @Test
testSecondaryBulkLoad1()882     public void testSecondaryBulkLoad1()
883         throws DatabaseException {
884 
885         doSecondaryBulkLoad(true);
886     }
887 
888     @Test
testSecondaryBulkLoad2()889     public void testSecondaryBulkLoad2()
890         throws DatabaseException {
891 
892         doSecondaryBulkLoad(false);
893     }
894 
doSecondaryBulkLoad(boolean closeAndOpenNormally)895     private void doSecondaryBulkLoad(boolean closeAndOpenNormally)
896         throws DatabaseException {
897 
898         PrimaryIndex<Integer, RelatedX> priX;
899         PrimaryIndex<Integer, RelatedY> priY;
900         SecondaryIndex<Integer, Integer, RelatedX> secX;
901 
902         /* Open priX with SecondaryBulkLoad=true. */
903         StoreConfig config = new StoreConfig();
904         config.setAllowCreate(true);
905         config.setSecondaryBulkLoad(true);
906         open(config);
907 
908         /* Getting priX should not create the secondary index. */
909         priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
910         PersistTestUtils.assertDbExists
911             (false, env, STORE_NAME, RelatedX.class.getName(), "key2");
912 
913         /* We can put records that violate the secondary key constraint. */
914         priX.put(new RelatedX());
915 
916         if (closeAndOpenNormally) {
917             /* Open normally and attempt to populate the secondary. */
918             close();
919             open();
920             if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
921                 /* Constraint enforcement requires transactions. */
922                 try {
923                     /* Before adding the foreign key, constraint is violated. */
924                     priX = store.getPrimaryIndex(Integer.class,
925                                                  RelatedX.class);
926                     fail();
927                 } catch (DatabaseException e) {
928                     assertTrue
929                         (e.toString(),
930                          e.toString().contains("foreign key not allowed"));
931                 }
932             }
933             /* Open priX with SecondaryBulkLoad=true. */
934             close();
935             open(config);
936             /* Add the foreign key to avoid the constraint error. */
937             priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
938             priY.put(new RelatedY());
939             /* Open normally and the secondary will be populated. */
940             close();
941             open();
942             priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
943             PersistTestUtils.assertDbExists
944                 (true, env, STORE_NAME, RelatedX.class.getName(), "key2");
945             secX = store.getSecondaryIndex(priX, Integer.class, "key2");
946         } else {
947             /* Get secondary index explicitly and it will be populated. */
948             if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
949                 /* Constraint enforcement requires transactions. */
950                 try {
951                     /* Before adding the foreign key, constraint is violated. */
952                     secX = store.getSecondaryIndex(priX, Integer.class,
953                                                    "key2");
954                     fail();
955                 } catch (DatabaseException e) {
956                     assertTrue
957                         (e.toString(),
958                          e.toString().contains("foreign key not allowed"));
959                 }
960             }
961             /* Add the foreign key. */
962             priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
963             priY.put(new RelatedY());
964             secX = store.getSecondaryIndex(priX, Integer.class, "key2");
965             PersistTestUtils.assertDbExists
966                 (true, env, STORE_NAME, RelatedX.class.getName(), "key2");
967         }
968 
969         RelatedX x = secX.get(88);
970         assertNotNull(x);
971         close();
972     }
973 
974     @Test
testPersistentFields()975     public void testPersistentFields()
976         throws DatabaseException {
977 
978         open();
979         PrimaryIndex<Integer, PersistentFields> pri =
980             store.getPrimaryIndex(Integer.class, PersistentFields.class);
981         PersistentFields o1 = new PersistentFields(-1, 1, 2, 3, 4, 5, 6);
982         assertNull(pri.put(o1));
983         PersistentFields o2 = pri.get(-1);
984         assertNotNull(o2);
985         assertEquals(0, o2.transient1);
986         assertEquals(0, o2.transient2);
987         assertEquals(0, o2.transient3);
988         assertEquals(4, o2.persistent1);
989         assertEquals(5, o2.persistent2);
990         assertEquals(6, o2.persistent3);
991         close();
992     }
993 
994     @Entity
995     static class PersistentFields {
996 
997         @PrimaryKey int key;
998 
999         transient int transient1;
1000         @NotPersistent int transient2;
1001         @NotPersistent transient int transient3;
1002 
1003         int persistent1;
1004         @NotTransient int persistent2;
1005         @NotTransient transient int persistent3;
1006 
PersistentFields(int k, int t1, int t2, int t3, int p1, int p2, int p3)1007         PersistentFields(int k,
1008                          int t1,
1009                          int t2,
1010                          int t3,
1011                          int p1,
1012                          int p2,
1013                          int p3) {
1014             key = k;
1015             transient1 = t1;
1016             transient2 = t2;
1017             transient3 = t3;
1018             persistent1 = p1;
1019             persistent2 = p2;
1020             persistent3 = p3;
1021         }
1022 
PersistentFields()1023         private PersistentFields() {}
1024     }
1025 
1026     /**
1027      * When a primary or secondary has a persistent key class, the key class
1028      * was not recognized as being persistent when getPrimaryConfig,
1029      * getSecondaryConfig, or getSubclassIndex was called, if that key class
1030      * was not previously referenced.  All three cases are tested by calling
1031      * getSecondaryConfig.  This was a bug in JE 3.3.69, reported on OTN.
1032      * [#16407]
1033      */
1034     @Test
testKeyClassInitialization()1035     public void testKeyClassInitialization()
1036         throws DatabaseException {
1037 
1038         open();
1039         store.getSecondaryConfig(ToManyKeyEntity.class, "key2");
1040         close();
1041     }
1042 
1043     @Test
testKeyName()1044     public void testKeyName()
1045         throws DatabaseException {
1046 
1047         open();
1048 
1049         PrimaryIndex<Long, BookEntity> pri1 =
1050             store.getPrimaryIndex(Long.class, BookEntity.class);
1051         PrimaryIndex<Long, AuthorEntity> pri2 =
1052             store.getPrimaryIndex(Long.class, AuthorEntity.class);
1053 
1054         BookEntity book = new BookEntity();
1055         pri1.put(book);
1056         AuthorEntity author = new AuthorEntity();
1057         author.bookIds.add(book.bookId);
1058         pri2.put(author);
1059 
1060         close();
1061 
1062         open();
1063         pri1 = store.getPrimaryIndex(Long.class, BookEntity.class);
1064         pri2 = store.getPrimaryIndex(Long.class, AuthorEntity.class);
1065         book = pri1.get(1L);
1066         assertNotNull(book);
1067         author = pri2.get(1L);
1068         assertNotNull(author);
1069         close();
1070     }
1071 
1072     @Entity
1073     static class AuthorEntity {
1074 
1075         @PrimaryKey(sequence="authorSeq")
1076         long authorId;
1077 
1078         @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=BookEntity.class,
1079                       name="bookId", onRelatedEntityDelete=NULLIFY)
1080         Set<Long> bookIds = new HashSet<Long>();
1081     }
1082 
1083     @Entity
1084     static class BookEntity {
1085 
1086         @PrimaryKey(sequence="bookSeq")
1087         long bookId;
1088     }
1089 
1090     /**
1091      * Checks that we get an appropriate exception when storing an entity
1092      * subclass instance, which contains a secondary key, without registering
1093      * the subclass up front. [#16399]
1094      */
1095     @Test
testPutEntitySubclassWithoutRegisterClass()1096     public void testPutEntitySubclassWithoutRegisterClass()
1097         throws DatabaseException {
1098 
1099         open();
1100 
1101         final PrimaryIndex<Long, Statement> pri =
1102             store.getPrimaryIndex(Long.class, Statement.class);
1103 
1104         final Transaction txn = txnBegin();
1105         pri.put(txn, new Statement(1));
1106         try {
1107             pri.put(txn, new ExtendedStatement(2, null));
1108             fail();
1109         } catch (IllegalArgumentException expected) {
1110             assertTrue(expected.toString(), expected.getMessage().contains
1111                 ("Entity subclasses defining a secondary key must be " +
1112                  "registered by calling EntityModel.registerClass or " +
1113                  "EntityStore.getSubclassIndex before storing an instance " +
1114                  "of the subclass: " + ExtendedStatement.class.getName()));
1115         }
1116         txnAbort(txn);
1117 
1118         close();
1119     }
1120 
1121     /**
1122      * Checks that registerClass avoids an exception when storing an entity
1123      * subclass instance, which defines a secondary key. [#16399]
1124      */
1125     @Test
testPutEntitySubclassWithRegisterClass()1126     public void testPutEntitySubclassWithRegisterClass()
1127         throws DatabaseException {
1128 
1129         open(ExtendedStatement.class);
1130 
1131         final PrimaryIndex<Long, Statement> pri =
1132             store.getPrimaryIndex(Long.class, Statement.class);
1133 
1134         final Transaction txn = txnBegin();
1135         pri.put(txn, new Statement(1));
1136         pri.put(txn, new ExtendedStatement(2, "abc"));
1137         txnCommit(txn);
1138 
1139         final SecondaryIndex<String, Long, ExtendedStatement> sec =
1140             store.getSubclassIndex(pri, ExtendedStatement.class,
1141                                    String.class, "name");
1142 
1143         ExtendedStatement o = sec.get("abc");
1144         assertNotNull(o);
1145         assertEquals(2, o.id);
1146 
1147         close();
1148     }
1149 
1150     /**
1151      * Same as testPutEntitySubclassWithRegisterClass but store the first
1152      * instance of the subclass after closing and reopening the store,
1153      * *without* calling registerClass.  This ensures that a single call to
1154      * registerClass is sufficient and subsequent use of the store does not
1155      * require it.  [#16399]
1156      */
1157     @Test
testPutEntitySubclassWithRegisterClass2()1158     public void testPutEntitySubclassWithRegisterClass2()
1159         throws DatabaseException {
1160 
1161         open(ExtendedStatement.class);
1162 
1163         PrimaryIndex<Long, Statement> pri =
1164             store.getPrimaryIndex(Long.class, Statement.class);
1165 
1166         Transaction txn = txnBegin();
1167         pri.put(txn, new Statement(1));
1168         txnCommit(txn);
1169 
1170         close();
1171         open();
1172 
1173         pri = store.getPrimaryIndex(Long.class, Statement.class);
1174 
1175         txn = txnBegin();
1176         pri.put(txn, new ExtendedStatement(2, "abc"));
1177         txnCommit(txn);
1178 
1179         final SecondaryIndex<String, Long, ExtendedStatement> sec =
1180             store.getSubclassIndex(pri, ExtendedStatement.class,
1181                                    String.class, "name");
1182 
1183         ExtendedStatement o = sec.get("abc");
1184         assertNotNull(o);
1185         assertEquals(2, o.id);
1186 
1187         close();
1188     }
1189 
1190     /**
1191      * Checks that getSubclassIndex can be used instead of registerClass to
1192      * avoid an exception when storing an entity subclass instance, which
1193      * defines a secondary key. [#16399]
1194      */
1195     @Test
testPutEntitySubclassWithGetSubclassIndex()1196     public void testPutEntitySubclassWithGetSubclassIndex()
1197         throws DatabaseException {
1198 
1199         open();
1200 
1201         final PrimaryIndex<Long, Statement> pri =
1202             store.getPrimaryIndex(Long.class, Statement.class);
1203 
1204         final SecondaryIndex<String, Long, ExtendedStatement> sec =
1205             store.getSubclassIndex(pri, ExtendedStatement.class,
1206                                    String.class, "name");
1207 
1208         final Transaction txn = txnBegin();
1209         pri.put(txn, new Statement(1));
1210         pri.put(txn, new ExtendedStatement(2, "abc"));
1211         txnCommit(txn);
1212 
1213         ExtendedStatement o = sec.get("abc");
1214         assertNotNull(o);
1215         assertEquals(2, o.id);
1216 
1217         close();
1218     }
1219 
1220     /**
1221      * Same as testPutEntitySubclassWithGetSubclassIndex2 but store the first
1222      * instance of the subclass after closing and reopening the store,
1223      * *without* calling getSubclassIndex.  This ensures that a single call to
1224      * getSubclassIndex is sufficient and subsequent use of the store does not
1225      * require it.  [#16399]
1226      */
1227     @Test
testPutEntitySubclassWithGetSubclassIndex2()1228     public void testPutEntitySubclassWithGetSubclassIndex2()
1229         throws DatabaseException {
1230 
1231         open();
1232 
1233         PrimaryIndex<Long, Statement> pri =
1234             store.getPrimaryIndex(Long.class, Statement.class);
1235 
1236         SecondaryIndex<String, Long, ExtendedStatement> sec =
1237             store.getSubclassIndex(pri, ExtendedStatement.class,
1238                                    String.class, "name");
1239 
1240         Transaction txn = txnBegin();
1241         pri.put(txn, new Statement(1));
1242         txnCommit(txn);
1243 
1244         close();
1245         open();
1246 
1247         pri = store.getPrimaryIndex(Long.class, Statement.class);
1248 
1249         txn = txnBegin();
1250         pri.put(txn, new ExtendedStatement(2, "abc"));
1251         txnCommit(txn);
1252 
1253         sec = store.getSubclassIndex(pri, ExtendedStatement.class,
1254                                      String.class, "name");
1255 
1256         ExtendedStatement o = sec.get("abc");
1257         assertNotNull(o);
1258         assertEquals(2, o.id);
1259 
1260         close();
1261     }
1262 
1263     /**
1264      * Checks that secondary population occurs only once when an index is
1265      * created, not every time it is opened, even when it is empty.  This is a
1266      * JE-only test because we don't have a portable way to get stats that
1267      * indicate whether primary reads were performed.  [#16399]
1268      */
1269 
1270     @Entity
1271     static class Statement {
1272 
1273         @PrimaryKey
1274         long id;
1275 
Statement(long id)1276         Statement(long id) {
1277             this.id = id;
1278         }
1279 
Statement()1280         private Statement() {}
1281     }
1282 
1283     @Persistent
1284     static class ExtendedStatement extends Statement {
1285 
1286         @SecondaryKey(relate=MANY_TO_ONE)
1287         String name;
1288 
ExtendedStatement(long id, String name)1289         ExtendedStatement(long id, String name) {
1290             super(id);
1291             this.name = name;
1292         }
1293 
ExtendedStatement()1294         private ExtendedStatement() {}
1295     }
1296 
1297     @Test
testCustomCompare()1298     public void testCustomCompare()
1299         throws DatabaseException {
1300 
1301         open();
1302 
1303         PrimaryIndex<ReverseIntKey, CustomCompareEntity>
1304             priIndex = store.getPrimaryIndex
1305                 (ReverseIntKey.class, CustomCompareEntity.class);
1306 
1307         SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
1308             secIndex1 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
1309                                                 "secKey1");
1310 
1311         SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
1312             secIndex2 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
1313                                                 "secKey2");
1314 
1315         Transaction txn = txnBegin();
1316         for (int i = 1; i <= 5; i += 1) {
1317             assertTrue(priIndex.putNoOverwrite(txn,
1318                                                new CustomCompareEntity(i)));
1319         }
1320         txnCommit(txn);
1321 
1322         txn = txnBeginCursor();
1323         EntityCursor<CustomCompareEntity> c = priIndex.entities(txn, null);
1324         for (int i = 5; i >= 1; i -= 1) {
1325             CustomCompareEntity e = c.next();
1326             assertNotNull(e);
1327             assertEquals(new ReverseIntKey(i), e.key);
1328         }
1329         c.close();
1330         txnCommit(txn);
1331 
1332         txn = txnBeginCursor();
1333         c = secIndex1.entities(txn, null);
1334         for (int i = -1; i >= -5; i -= 1) {
1335             CustomCompareEntity e = c.next();
1336             assertNotNull(e);
1337             assertEquals(new ReverseIntKey(-i), e.key);
1338             assertEquals(new ReverseIntKey(i), e.secKey1);
1339         }
1340         c.close();
1341         txnCommit(txn);
1342 
1343         txn = txnBeginCursor();
1344         c = secIndex2.entities(txn, null);
1345         for (int i = -1; i >= -5; i -= 1) {
1346             CustomCompareEntity e = c.next();
1347             assertNotNull(e);
1348             assertEquals(new ReverseIntKey(-i), e.key);
1349             assertTrue(e.secKey2.contains(new ReverseIntKey(i)));
1350         }
1351         c.close();
1352         txnCommit(txn);
1353 
1354         close();
1355     }
1356 
1357     @Entity
1358     static class CustomCompareEntity {
1359 
1360         @PrimaryKey
1361         private ReverseIntKey key;
1362 
1363         @SecondaryKey(relate=MANY_TO_ONE)
1364         private ReverseIntKey secKey1;
1365 
1366         @SecondaryKey(relate=ONE_TO_MANY)
1367         private final Set<ReverseIntKey> secKey2 = new HashSet<ReverseIntKey>();
1368 
CustomCompareEntity()1369         private CustomCompareEntity() {}
1370 
CustomCompareEntity(int i)1371         CustomCompareEntity(int i) {
1372             key = new ReverseIntKey(i);
1373             secKey1 = new ReverseIntKey(-i);
1374             secKey2.add(new ReverseIntKey(-i));
1375         }
1376     }
1377 
1378     @Persistent
1379     static class ReverseIntKey implements Comparable<ReverseIntKey> {
1380 
1381         @KeyField(1)
1382         private int key;
1383 
compareTo(ReverseIntKey o)1384         public int compareTo(ReverseIntKey o) {
1385             /* Reverse the natural order. */
1386             return o.key - key;
1387         }
1388 
ReverseIntKey()1389         private ReverseIntKey() {}
1390 
ReverseIntKey(int key)1391         ReverseIntKey(int key) {
1392             this.key = key;
1393         }
1394 
1395         @Override
equals(Object o)1396         public boolean equals(Object o) {
1397             return key == ((ReverseIntKey) o).key;
1398         }
1399 
1400         @Override
hashCode()1401         public int hashCode() {
1402             return key;
1403         }
1404 
1405         @Override
toString()1406         public String toString() {
1407             return "Key = " + key;
1408         }
1409     }
1410 
1411     /**
1412      * Ensures that custom comparators are persisted and work correctly during
1413      * recovery.  JE recovery uses comparators, so they are serialized and
1414      * stored in the DatabaseImpl.  They are deserialized during recovery prior
1415      * to opening the EntityStore and its format catalog.  But the formats are
1416      * needed by the comparator, so they are specially created when needed.
1417      *
1418      * In particular we need to ensure that enum key fields work correctly,
1419      * since their formats are not static (like simple type formats are).
1420      * [#17140]
1421      *
1422      * Note that we don't need to actually cause a recovery in order to test
1423      * the deserialization and subsequent use of comparators.  The JE
1424      * DatabaseConfig.setBtreeComparator method serializes and deserializes the
1425      * comparator.  The comparator is initialized on its first use, just as if
1426      * recovery were run.
1427      */
1428     @Test
testStoredComparators()1429     public void testStoredComparators()
1430         throws DatabaseException {
1431 
1432         open();
1433 
1434         PrimaryIndex<StoredComparatorEntity.Key,
1435                      StoredComparatorEntity> priIndex =
1436             store.getPrimaryIndex(StoredComparatorEntity.Key.class,
1437                                   StoredComparatorEntity.class);
1438 
1439         SecondaryIndex<StoredComparatorEntity.MyEnum,
1440                        StoredComparatorEntity.Key,
1441                        StoredComparatorEntity> secIndex =
1442             store.getSecondaryIndex
1443                 (priIndex, StoredComparatorEntity.MyEnum.class, "secKey");
1444 
1445         final StoredComparatorEntity.Key[] priKeys =
1446             new StoredComparatorEntity.Key[] {
1447                 new StoredComparatorEntity.Key
1448                     (StoredComparatorEntity.MyEnum.A, 1,
1449                      StoredComparatorEntity.MyEnum.A),
1450                 new StoredComparatorEntity.Key
1451                     (StoredComparatorEntity.MyEnum.A, 1,
1452                      StoredComparatorEntity.MyEnum.B),
1453                 new StoredComparatorEntity.Key
1454                     (StoredComparatorEntity.MyEnum.A, 2,
1455                      StoredComparatorEntity.MyEnum.A),
1456                 new StoredComparatorEntity.Key
1457                     (StoredComparatorEntity.MyEnum.A, 2,
1458                      StoredComparatorEntity.MyEnum.B),
1459                 new StoredComparatorEntity.Key
1460                     (StoredComparatorEntity.MyEnum.B, 1,
1461                      StoredComparatorEntity.MyEnum.A),
1462                 new StoredComparatorEntity.Key
1463                     (StoredComparatorEntity.MyEnum.B, 1,
1464                      StoredComparatorEntity.MyEnum.B),
1465                 new StoredComparatorEntity.Key
1466                     (StoredComparatorEntity.MyEnum.C, 0,
1467                      StoredComparatorEntity.MyEnum.C),
1468             };
1469 
1470         final StoredComparatorEntity.MyEnum[] secKeys =
1471             new StoredComparatorEntity.MyEnum[] {
1472                 StoredComparatorEntity.MyEnum.C,
1473                 StoredComparatorEntity.MyEnum.B,
1474                 StoredComparatorEntity.MyEnum.A,
1475                 null,
1476                 StoredComparatorEntity.MyEnum.A,
1477                 StoredComparatorEntity.MyEnum.B,
1478                 StoredComparatorEntity.MyEnum.C,
1479             };
1480 
1481         assertEquals(priKeys.length, secKeys.length);
1482         final int nEntities = priKeys.length;
1483 
1484         Transaction txn = txnBegin();
1485         for (int i = 0; i < nEntities; i += 1) {
1486             priIndex.put(txn,
1487                          new StoredComparatorEntity(priKeys[i], secKeys[i]));
1488         }
1489         txnCommit(txn);
1490 
1491         txn = txnBeginCursor();
1492         EntityCursor<StoredComparatorEntity> entities =
1493             priIndex.entities(txn, null);
1494         for (int i = nEntities - 1; i >= 0; i -= 1) {
1495             StoredComparatorEntity e = entities.next();
1496             assertNotNull(e);
1497             assertEquals(priKeys[i], e.key);
1498             assertEquals(secKeys[i], e.secKey);
1499         }
1500         assertNull(entities.next());
1501         entities.close();
1502         txnCommit(txn);
1503 
1504         txn = txnBeginCursor();
1505         entities = secIndex.entities(txn, null);
1506         for (StoredComparatorEntity.MyEnum myEnum :
1507              EnumSet.allOf(StoredComparatorEntity.MyEnum.class)) {
1508             for (int i = nEntities - 1; i >= 0; i -= 1) {
1509                 if (secKeys[i] == myEnum) {
1510                     StoredComparatorEntity e = entities.next();
1511                     assertNotNull(e);
1512                     assertEquals(priKeys[i], e.key);
1513                     assertEquals(secKeys[i], e.secKey);
1514                 }
1515             }
1516         }
1517         assertNull(entities.next());
1518         entities.close();
1519         txnCommit(txn);
1520 
1521         close();
1522     }
1523 
1524     @Entity
1525     static class StoredComparatorEntity {
1526 
1527         enum MyEnum { A, B, C };
1528 
1529         @Persistent
1530         static class Key implements Comparable<Key> {
1531 
1532             @KeyField(1)
1533             MyEnum f1;
1534 
1535             @KeyField(2)
1536             Integer f2;
1537 
1538             @KeyField(3)
1539             MyEnum f3;
1540 
Key()1541             private Key() {}
1542 
Key(MyEnum f1, Integer f2, MyEnum f3)1543             Key(MyEnum f1, Integer f2, MyEnum f3) {
1544                 this.f1 = f1;
1545                 this.f2 = f2;
1546                 this.f3 = f3;
1547             }
1548 
compareTo(Key o)1549             public int compareTo(Key o) {
1550                 /* Reverse the natural order. */
1551                 int i = f1.compareTo(o.f1);
1552                 if (i != 0) return -i;
1553                 i = f2.compareTo(o.f2);
1554                 if (i != 0) return -i;
1555                 i = f3.compareTo(o.f3);
1556                 if (i != 0) return -i;
1557                 return 0;
1558             }
1559 
1560             @Override
equals(Object other)1561             public boolean equals(Object other) {
1562                 if (!(other instanceof Key)) {
1563                     return false;
1564                 }
1565                 Key o = (Key) other;
1566                 return f1 == o.f1 &&
1567                        f2.equals(o.f2) &&
1568                        f3 == o.f3;
1569             }
1570 
1571             @Override
hashCode()1572             public int hashCode() {
1573                 return f1.ordinal() + f2 + f3.ordinal();
1574             }
1575 
1576             @Override
toString()1577             public String toString() {
1578                 return "[Key " + f1 + ' ' + f2 + ' ' + f3 + ']';
1579             }
1580         }
1581 
1582         @PrimaryKey
1583         Key key;
1584 
1585         @SecondaryKey(relate=MANY_TO_ONE)
1586         private MyEnum secKey;
1587 
StoredComparatorEntity()1588         private StoredComparatorEntity() {}
1589 
StoredComparatorEntity(Key key, MyEnum secKey)1590         StoredComparatorEntity(Key key, MyEnum secKey) {
1591             this.key = key;
1592             this.secKey = secKey;
1593         }
1594 
1595         @Override
toString()1596         public String toString() {
1597             return "[pri = " + key + " sec = " + secKey + ']';
1598         }
1599     }
1600 
1601     @Test
testEmbeddedMapTypes()1602     public void testEmbeddedMapTypes()
1603         throws DatabaseException {
1604         open();
1605         PrimaryIndex<Integer, EmbeddedMapTypes> pri =
1606             store.getPrimaryIndex(Integer.class, EmbeddedMapTypes.class);
1607         pri.put(null, new EmbeddedMapTypes());
1608         close();
1609 
1610         open();
1611         pri = store.getPrimaryIndex(Integer.class, EmbeddedMapTypes.class);
1612         EmbeddedMapTypes entity = pri.get(1);
1613         assertNotNull(entity);
1614         EmbeddedMapTypes entity2 = new EmbeddedMapTypes();
1615         assertEquals(entity.getF1(), entity2.getF1());
1616         close();
1617     }
1618 
1619     enum MyEnum { ONE, TWO };
1620 
1621     @Entity
1622     static class EmbeddedMapTypes {
1623 
1624         @PrimaryKey
1625         private final int f0 = 1;
1626         private final Map<MyEnum, HashMap<MyEnum, MyEnum>> f1;
1627 
EmbeddedMapTypes()1628         EmbeddedMapTypes() {
1629             f1 = new HashMap<MyEnum, HashMap<MyEnum, MyEnum>>();
1630             HashMap<MyEnum, MyEnum> f2 = new HashMap<MyEnum, MyEnum>();
1631             f2.put(MyEnum.ONE, MyEnum.ONE);
1632             f1.put(MyEnum.ONE, f2);
1633             f2 = new HashMap<MyEnum, MyEnum>();
1634             f2.put(MyEnum.TWO, MyEnum.TWO);
1635             f1.put(MyEnum.TWO, f2);
1636         }
1637 
getPriKey()1638         public int getPriKey() {
1639             return f0;
1640         }
1641 
getF1()1642         public Map<MyEnum, HashMap<MyEnum, MyEnum>> getF1() {
1643             return f1;
1644         }
1645     }
1646 }
1647