001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: OperationTest.java,v 1.12.2.7 2008/01/07 15:14:35 cwl Exp $
007: */
008:
009: package com.sleepycat.persist.test;
010:
011: import static com.sleepycat.persist.model.Relationship.MANY_TO_ONE;
012: import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY;
013: import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
014: import static com.sleepycat.persist.model.DeleteAction.CASCADE;
015:
016: import java.util.ArrayList;
017: import java.util.HashSet;
018: import java.util.List;
019: import java.util.Set;
020:
021: import junit.framework.Test;
022:
023: import com.sleepycat.je.Database;
024: import com.sleepycat.je.DatabaseConfig;
025: import com.sleepycat.je.DatabaseException;
026: import com.sleepycat.je.Transaction;
027: import com.sleepycat.je.test.TxnTestCase;
028: import com.sleepycat.persist.EntityCursor;
029: import com.sleepycat.persist.EntityIndex;
030: import com.sleepycat.persist.EntityStore;
031: import com.sleepycat.persist.PrimaryIndex;
032: import com.sleepycat.persist.SecondaryIndex;
033: import com.sleepycat.persist.StoreConfig;
034: import com.sleepycat.persist.impl.Store;
035: import com.sleepycat.persist.model.Entity;
036: import com.sleepycat.persist.model.KeyField;
037: import com.sleepycat.persist.model.Persistent;
038: import com.sleepycat.persist.model.PrimaryKey;
039: import com.sleepycat.persist.model.SecondaryKey;
040: import com.sleepycat.persist.raw.RawStore;
041:
042: /**
043: * Tests misc store and index operations that are not tested by IndexTest.
044: *
045: * @author Mark Hayes
046: */
047: public class OperationTest extends TxnTestCase {
048:
049: private static final String STORE_NAME = "test";
050:
051: public static Test suite() {
052: return txnTestSuite(OperationTest.class, null, null);
053: }
054:
055: private EntityStore store;
056:
057: private void openReadOnly() throws DatabaseException {
058:
059: StoreConfig config = new StoreConfig();
060: config.setReadOnly(true);
061: open(config);
062: }
063:
064: private void open() throws DatabaseException {
065:
066: StoreConfig config = new StoreConfig();
067: config.setAllowCreate(envConfig.getAllowCreate());
068: open(config);
069: }
070:
071: private void open(StoreConfig config) throws DatabaseException {
072:
073: config.setTransactional(envConfig.getTransactional());
074: store = new EntityStore(env, STORE_NAME, config);
075: }
076:
077: private void close() throws DatabaseException {
078:
079: store.close();
080: store = null;
081: }
082:
083: /**
084: * The store must be closed before closing the environment.
085: */
086: public void tearDown() throws Exception {
087:
088: try {
089: if (store != null) {
090: store.close();
091: }
092: } catch (Throwable e) {
093: System.out.println("During tearDown: " + e);
094: }
095: store = null;
096: super .tearDown();
097: }
098:
099: public void testReadOnly() throws DatabaseException {
100:
101: open();
102: PrimaryIndex<Integer, SharedSequenceEntity1> priIndex = store
103: .getPrimaryIndex(Integer.class,
104: SharedSequenceEntity1.class);
105: Transaction txn = txnBegin();
106: SharedSequenceEntity1 e = new SharedSequenceEntity1();
107: priIndex.put(txn, e);
108: assertEquals(1, e.key);
109: txnCommit(txn);
110: close();
111:
112: /*
113: * Check that we can open the store read-only and read the records
114: * written above.
115: */
116: openReadOnly();
117: priIndex = store.getPrimaryIndex(Integer.class,
118: SharedSequenceEntity1.class);
119: e = priIndex.get(1);
120: assertNotNull(e);
121: close();
122: }
123:
124: public void testGetStoreNames() throws DatabaseException {
125:
126: open();
127: close();
128: Set<String> names = EntityStore.getStoreNames(env);
129: assertEquals(1, names.size());
130: assertEquals("test", names.iterator().next());
131: }
132:
133: public void testUninitializedCursor() throws DatabaseException {
134:
135: open();
136:
137: PrimaryIndex<Integer, MyEntity> priIndex = store
138: .getPrimaryIndex(Integer.class, MyEntity.class);
139:
140: Transaction txn = txnBeginCursor();
141:
142: MyEntity e = new MyEntity();
143: e.priKey = 1;
144: e.secKey = 1;
145: priIndex.put(txn, e);
146:
147: EntityCursor<MyEntity> entities = priIndex.entities(txn, null);
148: try {
149: entities.nextDup();
150: fail();
151: } catch (IllegalStateException expected) {
152: }
153: try {
154: entities.prevDup();
155: fail();
156: } catch (IllegalStateException expected) {
157: }
158: try {
159: entities.current();
160: fail();
161: } catch (IllegalStateException expected) {
162: }
163: try {
164: entities.delete();
165: fail();
166: } catch (IllegalStateException expected) {
167: }
168: try {
169: entities.update(e);
170: fail();
171: } catch (IllegalStateException expected) {
172: }
173: try {
174: entities.count();
175: fail();
176: } catch (IllegalStateException expected) {
177: }
178:
179: entities.close();
180: txnCommit(txn);
181: close();
182: }
183:
184: public void testCursorCount() throws DatabaseException {
185:
186: open();
187:
188: PrimaryIndex<Integer, MyEntity> priIndex = store
189: .getPrimaryIndex(Integer.class, MyEntity.class);
190:
191: SecondaryIndex<Integer, Integer, MyEntity> secIndex = store
192: .getSecondaryIndex(priIndex, Integer.class, "secKey");
193:
194: Transaction txn = txnBeginCursor();
195:
196: MyEntity e = new MyEntity();
197: e.priKey = 1;
198: e.secKey = 1;
199: priIndex.put(txn, e);
200:
201: EntityCursor<MyEntity> cursor = secIndex.entities(txn, null);
202: cursor.next();
203: assertEquals(1, cursor.count());
204: cursor.close();
205:
206: e.priKey = 2;
207: priIndex.put(txn, e);
208: cursor = secIndex.entities(txn, null);
209: cursor.next();
210: assertEquals(2, cursor.count());
211: cursor.close();
212:
213: txnCommit(txn);
214: close();
215: }
216:
217: public void testCursorUpdate() throws DatabaseException {
218:
219: open();
220:
221: PrimaryIndex<Integer, MyEntity> priIndex = store
222: .getPrimaryIndex(Integer.class, MyEntity.class);
223:
224: SecondaryIndex<Integer, Integer, MyEntity> secIndex = store
225: .getSecondaryIndex(priIndex, Integer.class, "secKey");
226:
227: Transaction txn = txnBeginCursor();
228:
229: Integer k;
230: MyEntity e = new MyEntity();
231: e.priKey = 1;
232: e.secKey = 2;
233: priIndex.put(txn, e);
234:
235: /* update() with primary entity cursor. */
236: EntityCursor<MyEntity> entities = priIndex.entities(txn, null);
237: e = entities.next();
238: assertNotNull(e);
239: assertEquals(1, e.priKey);
240: assertEquals(Integer.valueOf(2), e.secKey);
241: e.secKey = null;
242: assertTrue(entities.update(e));
243: e = entities.current();
244: assertNotNull(e);
245: assertEquals(1, e.priKey);
246: assertEquals(null, e.secKey);
247: e.secKey = 3;
248: assertTrue(entities.update(e));
249: e = entities.current();
250: assertNotNull(e);
251: assertEquals(1, e.priKey);
252: assertEquals(Integer.valueOf(3), e.secKey);
253: entities.close();
254:
255: /* update() with primary keys cursor. */
256: EntityCursor<Integer> keys = priIndex.keys(txn, null);
257: k = keys.next();
258: assertNotNull(k);
259: assertEquals(Integer.valueOf(1), k);
260: try {
261: keys.update(2);
262: fail();
263: } catch (UnsupportedOperationException expected) {
264: }
265: keys.close();
266:
267: /* update() with secondary entity cursor. */
268: entities = secIndex.entities(txn, null);
269: e = entities.next();
270: assertNotNull(e);
271: assertEquals(1, e.priKey);
272: assertEquals(Integer.valueOf(3), e.secKey);
273: try {
274: entities.update(e);
275: fail();
276: } catch (UnsupportedOperationException expected) {
277: }
278: entities.close();
279:
280: /* update() with secondary keys cursor. */
281: keys = secIndex.keys(txn, null);
282: k = keys.next();
283: assertNotNull(k);
284: assertEquals(Integer.valueOf(3), k);
285: try {
286: keys.update(k);
287: fail();
288: } catch (UnsupportedOperationException expected) {
289: }
290: keys.close();
291:
292: txnCommit(txn);
293: close();
294: }
295:
296: public void testCursorDelete() throws DatabaseException {
297:
298: open();
299:
300: PrimaryIndex<Integer, MyEntity> priIndex = store
301: .getPrimaryIndex(Integer.class, MyEntity.class);
302:
303: SecondaryIndex<Integer, Integer, MyEntity> secIndex = store
304: .getSecondaryIndex(priIndex, Integer.class, "secKey");
305:
306: Transaction txn = txnBeginCursor();
307:
308: /* delete() with primary and secondary entities cursor. */
309:
310: for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
311:
312: MyEntity e = new MyEntity();
313: e.priKey = 1;
314: e.secKey = 1;
315: priIndex.put(txn, e);
316: e.priKey = 2;
317: priIndex.put(txn, e);
318:
319: EntityCursor<MyEntity> cursor = index.entities(txn, null);
320:
321: e = cursor.next();
322: assertNotNull(e);
323: assertEquals(1, e.priKey);
324: e = cursor.current();
325: assertNotNull(e);
326: assertEquals(1, e.priKey);
327: assertTrue(cursor.delete());
328: assertTrue(!cursor.delete());
329: assertNull(cursor.current());
330:
331: e = cursor.next();
332: assertNotNull(e);
333: assertEquals(2, e.priKey);
334: e = cursor.current();
335: assertNotNull(e);
336: assertEquals(2, e.priKey);
337: assertTrue(cursor.delete());
338: assertTrue(!cursor.delete());
339: assertNull(cursor.current());
340:
341: e = cursor.next();
342: assertNull(e);
343:
344: if (index == priIndex) {
345: e = new MyEntity();
346: e.priKey = 2;
347: e.secKey = 1;
348: assertTrue(!cursor.update(e));
349: }
350:
351: cursor.close();
352: }
353:
354: /* delete() with primary and secondary keys cursor. */
355:
356: for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
357:
358: MyEntity e = new MyEntity();
359: e.priKey = 1;
360: e.secKey = 1;
361: priIndex.put(txn, e);
362: e.priKey = 2;
363: priIndex.put(txn, e);
364:
365: EntityCursor<Integer> cursor = index.keys(txn, null);
366:
367: Integer k = cursor.next();
368: assertNotNull(k);
369: assertEquals(1, k.intValue());
370: k = cursor.current();
371: assertNotNull(k);
372: assertEquals(1, k.intValue());
373: assertTrue(cursor.delete());
374: assertTrue(!cursor.delete());
375: assertNull(cursor.current());
376:
377: int expectKey = (index == priIndex) ? 2 : 1;
378: k = cursor.next();
379: assertNotNull(k);
380: assertEquals(expectKey, k.intValue());
381: k = cursor.current();
382: assertNotNull(k);
383: assertEquals(expectKey, k.intValue());
384: assertTrue(cursor.delete());
385: assertTrue(!cursor.delete());
386: assertNull(cursor.current());
387:
388: k = cursor.next();
389: assertNull(k);
390:
391: cursor.close();
392: }
393:
394: txnCommit(txn);
395: close();
396: }
397:
398: public void testDeleteFromSubIndex() throws DatabaseException {
399:
400: open();
401:
402: PrimaryIndex<Integer, MyEntity> priIndex = store
403: .getPrimaryIndex(Integer.class, MyEntity.class);
404:
405: SecondaryIndex<Integer, Integer, MyEntity> secIndex = store
406: .getSecondaryIndex(priIndex, Integer.class, "secKey");
407:
408: Transaction txn = txnBegin();
409: MyEntity e = new MyEntity();
410: e.secKey = 1;
411: e.priKey = 1;
412: priIndex.put(txn, e);
413: e.priKey = 2;
414: priIndex.put(txn, e);
415: e.priKey = 3;
416: priIndex.put(txn, e);
417: e.priKey = 4;
418: priIndex.put(txn, e);
419: txnCommit(txn);
420:
421: EntityIndex<Integer, MyEntity> subIndex = secIndex.subIndex(1);
422: txn = txnBeginCursor();
423: e = subIndex.get(txn, 1, null);
424: assertEquals(1, e.priKey);
425: assertEquals(Integer.valueOf(1), e.secKey);
426: e = subIndex.get(txn, 2, null);
427: assertEquals(2, e.priKey);
428: assertEquals(Integer.valueOf(1), e.secKey);
429: e = subIndex.get(txn, 3, null);
430: assertEquals(3, e.priKey);
431: assertEquals(Integer.valueOf(1), e.secKey);
432: e = subIndex.get(txn, 5, null);
433: assertNull(e);
434:
435: boolean deleted = subIndex.delete(txn, 1);
436: assertTrue(deleted);
437: assertNull(subIndex.get(txn, 1, null));
438: assertNotNull(subIndex.get(txn, 2, null));
439:
440: EntityCursor<MyEntity> cursor = subIndex.entities(txn, null);
441: boolean saw4 = false;
442: for (MyEntity e2 = cursor.first(); e2 != null; e2 = cursor
443: .next()) {
444: if (e2.priKey == 3) {
445: cursor.delete();
446: }
447: if (e2.priKey == 4) {
448: saw4 = true;
449: }
450: }
451: cursor.close();
452: assertTrue(saw4);
453: assertNull(subIndex.get(txn, 1, null));
454: assertNull(subIndex.get(txn, 3, null));
455: assertNotNull(subIndex.get(txn, 2, null));
456: assertNotNull(subIndex.get(txn, 4, null));
457:
458: txnCommit(txn);
459: close();
460: }
461:
462: @Entity
463: static class MyEntity {
464:
465: @PrimaryKey
466: private int priKey;
467:
468: @SecondaryKey(relate=MANY_TO_ONE)
469: private Integer secKey;
470:
471: private MyEntity() {
472: }
473: }
474:
475: public void testSharedSequence() throws DatabaseException {
476:
477: open();
478:
479: PrimaryIndex<Integer, SharedSequenceEntity1> priIndex1 = store
480: .getPrimaryIndex(Integer.class,
481: SharedSequenceEntity1.class);
482:
483: PrimaryIndex<Integer, SharedSequenceEntity2> priIndex2 = store
484: .getPrimaryIndex(Integer.class,
485: SharedSequenceEntity2.class);
486:
487: Transaction txn = txnBegin();
488: SharedSequenceEntity1 e1 = new SharedSequenceEntity1();
489: SharedSequenceEntity2 e2 = new SharedSequenceEntity2();
490: priIndex1.put(txn, e1);
491: assertEquals(1, e1.key);
492: priIndex2.putNoOverwrite(txn, e2);
493: assertEquals(Integer.valueOf(2), e2.key);
494: e1.key = 0;
495: priIndex1.putNoOverwrite(txn, e1);
496: assertEquals(3, e1.key);
497: e2.key = null;
498: priIndex2.put(txn, e2);
499: assertEquals(Integer.valueOf(4), e2.key);
500: txnCommit(txn);
501:
502: close();
503: }
504:
505: @Entity
506: static class SharedSequenceEntity1 {
507:
508: @PrimaryKey(sequence="shared")
509: private int key;
510: }
511:
512: @Entity
513: static class SharedSequenceEntity2 {
514:
515: @PrimaryKey(sequence="shared")
516: private Integer key;
517: }
518:
519: public void testSeparateSequence() throws DatabaseException {
520:
521: open();
522:
523: PrimaryIndex<Integer, SeparateSequenceEntity1> priIndex1 = store
524: .getPrimaryIndex(Integer.class,
525: SeparateSequenceEntity1.class);
526:
527: PrimaryIndex<Integer, SeparateSequenceEntity2> priIndex2 = store
528: .getPrimaryIndex(Integer.class,
529: SeparateSequenceEntity2.class);
530:
531: Transaction txn = txnBegin();
532: SeparateSequenceEntity1 e1 = new SeparateSequenceEntity1();
533: SeparateSequenceEntity2 e2 = new SeparateSequenceEntity2();
534: priIndex1.put(txn, e1);
535: assertEquals(1, e1.key);
536: priIndex2.putNoOverwrite(txn, e2);
537: assertEquals(Integer.valueOf(1), e2.key);
538: e1.key = 0;
539: priIndex1.putNoOverwrite(txn, e1);
540: assertEquals(2, e1.key);
541: e2.key = null;
542: priIndex2.put(txn, e2);
543: assertEquals(Integer.valueOf(2), e2.key);
544: txnCommit(txn);
545:
546: close();
547: }
548:
549: @Entity
550: static class SeparateSequenceEntity1 {
551:
552: @PrimaryKey(sequence="seq1")
553: private int key;
554: }
555:
556: @Entity
557: static class SeparateSequenceEntity2 {
558:
559: @PrimaryKey(sequence="seq2")
560: private Integer key;
561: }
562:
563: public void testCompositeSequence() throws DatabaseException {
564:
565: open();
566:
567: PrimaryIndex<CompositeSequenceEntity1.Key, CompositeSequenceEntity1> priIndex1 = store
568: .getPrimaryIndex(CompositeSequenceEntity1.Key.class,
569: CompositeSequenceEntity1.class);
570:
571: PrimaryIndex<CompositeSequenceEntity2.Key, CompositeSequenceEntity2> priIndex2 = store
572: .getPrimaryIndex(CompositeSequenceEntity2.Key.class,
573: CompositeSequenceEntity2.class);
574:
575: Transaction txn = txnBegin();
576: CompositeSequenceEntity1 e1 = new CompositeSequenceEntity1();
577: CompositeSequenceEntity2 e2 = new CompositeSequenceEntity2();
578: priIndex1.put(txn, e1);
579: assertEquals(1, e1.key.key);
580: priIndex2.putNoOverwrite(txn, e2);
581: assertEquals(Integer.valueOf(1), e2.key.key);
582: e1.key = null;
583: priIndex1.putNoOverwrite(txn, e1);
584: assertEquals(2, e1.key.key);
585: e2.key = null;
586: priIndex2.put(txn, e2);
587: assertEquals(Integer.valueOf(2), e2.key.key);
588: txnCommit(txn);
589:
590: EntityCursor<CompositeSequenceEntity1> c1 = priIndex1
591: .entities();
592: e1 = c1.next();
593: assertEquals(2, e1.key.key);
594: e1 = c1.next();
595: assertEquals(1, e1.key.key);
596: e1 = c1.next();
597: assertNull(e1);
598: c1.close();
599:
600: EntityCursor<CompositeSequenceEntity2> c2 = priIndex2
601: .entities();
602: e2 = c2.next();
603: assertEquals(Integer.valueOf(2), e2.key.key);
604: e2 = c2.next();
605: assertEquals(Integer.valueOf(1), e2.key.key);
606: e2 = c2.next();
607: assertNull(e2);
608: c2.close();
609:
610: close();
611: }
612:
613: @Entity
614: static class CompositeSequenceEntity1 {
615:
616: @Persistent
617: static class Key implements Comparable<Key> {
618:
619: @KeyField(1)
620: private int key;
621:
622: public int compareTo(Key o) {
623: /* Reverse the natural order. */
624: return o.key - key;
625: }
626: }
627:
628: @PrimaryKey(sequence="seq1")
629: private Key key;
630: }
631:
632: @Entity
633: static class CompositeSequenceEntity2 {
634:
635: @Persistent
636: static class Key implements Comparable<Key> {
637:
638: @KeyField(1)
639: private Integer key;
640:
641: public int compareTo(Key o) {
642: /* Reverse the natural order. */
643: return o.key - key;
644: }
645: }
646:
647: @PrimaryKey(sequence="seq2")
648: private Key key;
649: }
650:
651: /**
652: * When opening read-only, secondaries are not opened when the primary is
653: * opened, causing a different code path to be used for opening
654: * secondaries. For a RawStore in particular, this caused an unreported
655: * NullPointerException in JE 3.0.12. No SR was created because the use
656: * case is very obscure and was discovered by code inspection.
657: */
658: public void testOpenRawStoreReadOnly() throws DatabaseException {
659:
660: open();
661: store.getPrimaryIndex(Integer.class, MyEntity.class);
662: close();
663:
664: StoreConfig config = new StoreConfig();
665: config.setReadOnly(true);
666: config.setTransactional(envConfig.getTransactional());
667: RawStore rawStore = new RawStore(env, "test", config);
668:
669: String clsName = MyEntity.class.getName();
670: rawStore.getSecondaryIndex(clsName, "secKey");
671:
672: rawStore.close();
673: }
674:
675: /**
676: * When opening an X_TO_MANY secondary that has a persistent key class, the
677: * key class was not recognized as being persistent if it was never before
678: * referenced when getSecondaryIndex was called. This was a bug in JE
679: * 3.0.12, reported on OTN. [#15103]
680: */
681: public void testToManyKeyClass() throws DatabaseException {
682:
683: open();
684:
685: PrimaryIndex<Integer, ToManyKeyEntity> priIndex = store
686: .getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
687: SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex = store
688: .getSecondaryIndex(priIndex, ToManyKey.class, "key2");
689:
690: priIndex.put(new ToManyKeyEntity());
691: secIndex.get(new ToManyKey());
692:
693: close();
694: }
695:
696: /**
697: * Test a fix for a bug where opening a TO_MANY secondary index would fail
698: * fail with "IllegalArgumentException: Wrong secondary key class: ..."
699: * when the store was opened read-only. [#15156]
700: */
701: public void testToManyReadOnly() throws DatabaseException {
702:
703: open();
704: PrimaryIndex<Integer, ToManyKeyEntity> priIndex = store
705: .getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
706: priIndex.put(new ToManyKeyEntity());
707: close();
708:
709: openReadOnly();
710: priIndex = store.getPrimaryIndex(Integer.class,
711: ToManyKeyEntity.class);
712: SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex = store
713: .getSecondaryIndex(priIndex, ToManyKey.class, "key2");
714: secIndex.get(new ToManyKey());
715: close();
716: }
717:
718: @Persistent
719: static class ToManyKey {
720:
721: @KeyField(1)
722: int value = 99;
723: }
724:
725: @Entity
726: static class ToManyKeyEntity {
727:
728: @PrimaryKey
729: int key = 88;
730:
731: @SecondaryKey(relate=ONE_TO_MANY)
732: Set<ToManyKey> key2;
733:
734: ToManyKeyEntity() {
735: key2 = new HashSet<ToManyKey>();
736: key2.add(new ToManyKey());
737: }
738: }
739:
740: public void testDeferredWrite() throws DatabaseException {
741:
742: if (envConfig.getTransactional()) {
743: /* Deferred write cannot be used with transactions. */
744: return;
745: }
746: StoreConfig storeConfig = new StoreConfig();
747: storeConfig.setDeferredWrite(true);
748: storeConfig.setAllowCreate(true);
749: open(storeConfig);
750: assertTrue(store.getConfig().getDeferredWrite());
751:
752: PrimaryIndex<Integer, MyEntity> priIndex = store
753: .getPrimaryIndex(Integer.class, MyEntity.class);
754:
755: SecondaryIndex<Integer, Integer, MyEntity> secIndex = store
756: .getSecondaryIndex(priIndex, Integer.class, "secKey");
757:
758: DatabaseConfig dbConfig = priIndex.getDatabase().getConfig();
759: assertTrue(dbConfig.getDeferredWrite());
760: dbConfig = secIndex.getDatabase().getConfig();
761: assertTrue(dbConfig.getDeferredWrite());
762:
763: MyEntity e = new MyEntity();
764: e.priKey = 1;
765: e.secKey = 1;
766: priIndex.put(e);
767:
768: EntityCursor<MyEntity> cursor = secIndex.entities();
769: cursor.next();
770: assertEquals(1, cursor.count());
771: cursor.close();
772:
773: e.priKey = 2;
774: priIndex.put(e);
775: cursor = secIndex.entities();
776: cursor.next();
777: assertEquals(2, cursor.count());
778: cursor.close();
779:
780: class MySyncHook implements Store.SyncHook {
781:
782: boolean gotFlush;
783: List<Database> synced = new ArrayList<Database>();
784:
785: public void onSync(Database db, boolean flushLog) {
786: synced.add(db);
787: if (flushLog) {
788: assertTrue(!gotFlush);
789: gotFlush = true;
790: }
791: }
792: }
793:
794: MySyncHook hook = new MySyncHook();
795: Store.setSyncHook(hook);
796: store.sync();
797: assertTrue(hook.gotFlush);
798: assertEquals(2, hook.synced.size());
799: assertTrue(hook.synced.contains(priIndex.getDatabase()));
800: assertTrue(hook.synced.contains(secIndex.getDatabase()));
801:
802: close();
803: }
804:
805: /**
806: * When Y is opened and X has a key with relatedEntity=Y.class, X should
807: * be opened automatically. If X is not opened, foreign key constraints
808: * will not be enforced. [#15358]
809: */
810: public void testAutoOpenRelatedEntity() throws DatabaseException {
811:
812: PrimaryIndex<Integer, RelatedY> priY;
813: PrimaryIndex<Integer, RelatedX> priX;
814:
815: /* Opening X should create (and open) Y and enforce constraints. */
816: open();
817: priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
818: PersistTestUtils.assertDbExists(true, env, STORE_NAME,
819: RelatedY.class.getName(), null);
820: try {
821: priX.put(new RelatedX());
822: fail();
823: } catch (DatabaseException e) {
824: assertTrue(e.getMessage().indexOf(
825: "foreign key not allowed: it is not present") > 0);
826: }
827: priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
828: priY.put(new RelatedY());
829: priX.put(new RelatedX());
830: close();
831:
832: /* Delete should cascade even when X is not opened explicitly. */
833: open();
834: priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
835: assertEquals(1, priY.count());
836: priY.delete(88);
837: assertEquals(0, priY.count());
838: priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
839: assertEquals(0, priX.count()); /* Failed prior to [#15358] fix. */
840: close();
841: }
842:
843: @Entity
844: static class RelatedX {
845:
846: @PrimaryKey
847: int key = 99;
848:
849: @SecondaryKey(relate=ONE_TO_ONE,relatedEntity=RelatedY.class,onRelatedEntityDelete=CASCADE)
850: int key2 = 88;
851:
852: RelatedX() {
853: }
854: }
855:
856: @Entity
857: static class RelatedY {
858:
859: @PrimaryKey
860: int key = 88;
861:
862: RelatedY() {
863: }
864: }
865:
866: public void testSecondaryBulkLoad1() throws DatabaseException {
867:
868: doSecondaryBulkLoad(true);
869: }
870:
871: public void testSecondaryBulkLoad2() throws DatabaseException {
872:
873: doSecondaryBulkLoad(false);
874: }
875:
876: private void doSecondaryBulkLoad(boolean closeAndOpenNormally)
877: throws DatabaseException {
878:
879: PrimaryIndex<Integer, RelatedX> priX;
880: PrimaryIndex<Integer, RelatedY> priY;
881: SecondaryIndex<Integer, Integer, RelatedX> secX;
882:
883: /* Open priX with SecondaryBulkLoad=true. */
884: StoreConfig config = new StoreConfig();
885: config.setAllowCreate(true);
886: config.setSecondaryBulkLoad(true);
887: open(config);
888:
889: /* Getting priX should not create the secondary index. */
890: priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
891: PersistTestUtils.assertDbExists(false, env, STORE_NAME,
892: RelatedX.class.getName(), "key2");
893:
894: /* We can put records that violate the secondary key constraint. */
895: priX.put(new RelatedX());
896:
897: if (closeAndOpenNormally) {
898: /* Open normally and the secondary will be populated. */
899: close();
900: open();
901: try {
902: /* Before adding the foreign key, constraint is violated. */
903: priX = store.getPrimaryIndex(Integer.class,
904: RelatedX.class);
905: } catch (DatabaseException e) {
906: assertTrue(e.toString(), e.toString().contains(
907: "foreign key not allowed"));
908: }
909: /* Add the foreign key to avoid the constraint error. */
910: priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
911: priY.put(new RelatedY());
912: priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
913: PersistTestUtils.assertDbExists(true, env, STORE_NAME,
914: RelatedX.class.getName(), "key2");
915: secX = store.getSecondaryIndex(priX, Integer.class, "key2");
916: } else {
917: /* Get secondary index explicitly and it will be populated. */
918: try {
919: /* Before adding the foreign key, constraint is violated. */
920: secX = store.getSecondaryIndex(priX, Integer.class,
921: "key2");
922: } catch (DatabaseException e) {
923: assertTrue(e.toString(), e.toString().contains(
924: "foreign key not allowed"));
925: }
926: /* Add the foreign key. */
927: priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
928: priY.put(new RelatedY());
929: secX = store.getSecondaryIndex(priX, Integer.class, "key2");
930: PersistTestUtils.assertDbExists(true, env, STORE_NAME,
931: RelatedX.class.getName(), "key2");
932: }
933:
934: RelatedX x = secX.get(88);
935: assertNotNull(x);
936: close();
937: }
938: }
|