2
* See the file LICENSE for redistribution information.
4
* Copyright (c) 2002, 2010 Oracle and/or its affiliates. All rights reserved.
8
package com.sleepycat.persist.test;
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;
17
import java.util.ArrayList;
18
import java.util.EnumSet;
19
import java.util.HashSet;
20
import java.util.List;
23
import junit.framework.Test;
25
import com.sleepycat.compat.DbCompat;
26
import com.sleepycat.db.Database;
27
import com.sleepycat.db.DatabaseConfig;
28
import com.sleepycat.db.DatabaseException;
29
import com.sleepycat.db.StatsConfig;
30
import com.sleepycat.db.Transaction;
31
import com.sleepycat.persist.EntityCursor;
32
import com.sleepycat.persist.EntityIndex;
33
import com.sleepycat.persist.EntityStore;
34
import com.sleepycat.persist.PrimaryIndex;
35
import com.sleepycat.persist.SecondaryIndex;
36
import com.sleepycat.persist.StoreConfig;
37
import com.sleepycat.persist.impl.Store;
38
import com.sleepycat.persist.model.Entity;
39
import com.sleepycat.persist.model.KeyField;
40
import com.sleepycat.persist.model.NotPersistent;
41
import com.sleepycat.persist.model.NotTransient;
42
import com.sleepycat.persist.model.Persistent;
43
import com.sleepycat.persist.model.PrimaryKey;
44
import com.sleepycat.persist.model.SecondaryKey;
45
import com.sleepycat.persist.raw.RawStore;
46
import com.sleepycat.util.test.TxnTestCase;
49
* Tests misc store and index operations that are not tested by IndexTest.
53
public class OperationTest extends TxnTestCase {
55
private static final String STORE_NAME = "test";
57
public static Test suite() {
58
testClass = OperationTest.class;
59
return txnTestSuite(null, null);
62
private EntityStore store;
64
private void openReadOnly()
65
throws DatabaseException {
67
StoreConfig config = new StoreConfig();
68
config.setReadOnly(true);
73
throws DatabaseException {
78
private void open(Class clsToRegister)
79
throws DatabaseException {
81
StoreConfig config = new StoreConfig();
82
config.setAllowCreate(envConfig.getAllowCreate());
83
if (clsToRegister != null) {
84
com.sleepycat.persist.model.EntityModel model =
85
new com.sleepycat.persist.model.AnnotationModel();
86
model.registerClass(clsToRegister);
87
config.setModel(model);
92
private void open(StoreConfig config)
93
throws DatabaseException {
95
config.setTransactional(envConfig.getTransactional());
96
store = new EntityStore(env, STORE_NAME, config);
100
throws DatabaseException {
114
* The store must be closed before closing the environment.
117
public void tearDown()
124
} catch (Throwable e) {
125
System.out.println("During tearDown: " + e);
131
public void testReadOnly()
132
throws DatabaseException {
135
PrimaryIndex<Integer, SharedSequenceEntity1> priIndex =
136
store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
137
Transaction txn = txnBegin();
138
SharedSequenceEntity1 e = new SharedSequenceEntity1();
139
priIndex.put(txn, e);
140
assertEquals(1, e.key);
145
* Check that we can open the store read-only and read the records
150
store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
158
public void testUninitializedCursor()
159
throws DatabaseException {
163
PrimaryIndex<Integer, MyEntity> priIndex =
164
store.getPrimaryIndex(Integer.class, MyEntity.class);
166
Transaction txn = txnBeginCursor();
168
MyEntity e = new MyEntity();
171
priIndex.put(txn, e);
173
EntityCursor<MyEntity> entities =
174
priIndex.entities(txn, getWriteCursorConfig());
178
} catch (IllegalStateException expected) {}
182
} catch (IllegalStateException expected) {}
186
} catch (IllegalStateException expected) {}
190
} catch (IllegalStateException expected) {}
194
} catch (IllegalStateException expected) {}
198
} catch (IllegalStateException expected) {}
205
public void testCursorCount()
206
throws DatabaseException {
210
PrimaryIndex<Integer, MyEntity> priIndex =
211
store.getPrimaryIndex(Integer.class, MyEntity.class);
213
SecondaryIndex<Integer, Integer, MyEntity> secIndex =
214
store.getSecondaryIndex(priIndex, Integer.class, "secKey");
216
Transaction txn = txnBeginCursor();
218
MyEntity e = new MyEntity();
221
priIndex.put(txn, e);
223
EntityCursor<MyEntity> cursor = secIndex.entities(txn, null);
225
assertEquals(1, cursor.count());
229
priIndex.put(txn, e);
230
cursor = secIndex.entities(txn, null);
232
assertEquals(2, cursor.count());
239
public void testCursorUpdate()
240
throws DatabaseException {
244
PrimaryIndex<Integer, MyEntity> priIndex =
245
store.getPrimaryIndex(Integer.class, MyEntity.class);
247
SecondaryIndex<Integer, Integer, MyEntity> secIndex =
248
store.getSecondaryIndex(priIndex, Integer.class, "secKey");
250
Transaction txn = txnBeginCursor();
253
MyEntity e = new MyEntity();
256
priIndex.put(txn, e);
258
/* update() with primary entity cursor. */
259
EntityCursor<MyEntity> entities =
260
priIndex.entities(txn, getWriteCursorConfig());
263
assertEquals(1, e.priKey);
264
assertEquals(Integer.valueOf(2), e.secKey);
266
assertTrue(entities.update(e));
267
e = entities.current();
269
assertEquals(1, e.priKey);
270
assertEquals(null, e.secKey);
272
assertTrue(entities.update(e));
273
e = entities.current();
275
assertEquals(1, e.priKey);
276
assertEquals(Integer.valueOf(3), e.secKey);
279
/* update() with primary keys cursor. */
280
EntityCursor<Integer> keys = priIndex.keys(txn,
281
getWriteCursorConfig());
284
assertEquals(Integer.valueOf(1), k);
288
} catch (UnsupportedOperationException expected) {
292
/* update() with secondary entity cursor. */
293
entities = secIndex.entities(txn, null);
296
assertEquals(1, e.priKey);
297
assertEquals(Integer.valueOf(3), e.secKey);
301
} catch (UnsupportedOperationException expected) {
302
} catch (IllegalArgumentException expectedForDbCore) {
306
/* update() with secondary keys cursor. */
307
keys = secIndex.keys(txn, null);
310
assertEquals(Integer.valueOf(3), k);
314
} catch (UnsupportedOperationException expected) {
322
public void testCursorDelete()
323
throws DatabaseException {
327
PrimaryIndex<Integer, MyEntity> priIndex =
328
store.getPrimaryIndex(Integer.class, MyEntity.class);
330
SecondaryIndex<Integer, Integer, MyEntity> secIndex =
331
store.getSecondaryIndex(priIndex, Integer.class, "secKey");
333
Transaction txn = txnBeginCursor();
335
/* delete() with primary and secondary entities cursor. */
337
for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
339
MyEntity e = new MyEntity();
342
priIndex.put(txn, e);
344
priIndex.put(txn, e);
346
EntityCursor<MyEntity> cursor =
347
index.entities(txn, getWriteCursorConfig());
351
assertEquals(1, e.priKey);
352
e = cursor.current();
354
assertEquals(1, e.priKey);
355
assertTrue(cursor.delete());
356
assertTrue(!cursor.delete());
357
assertNull(cursor.current());
361
assertEquals(2, e.priKey);
362
e = cursor.current();
364
assertEquals(2, e.priKey);
365
assertTrue(cursor.delete());
366
assertTrue(!cursor.delete());
367
assertNull(cursor.current());
372
if (index == priIndex) {
376
assertTrue(!cursor.update(e));
382
/* delete() with primary and secondary keys cursor. */
384
for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
386
MyEntity e = new MyEntity();
389
priIndex.put(txn, e);
391
priIndex.put(txn, e);
393
EntityCursor<Integer> cursor = index.keys(txn,
394
getWriteCursorConfig());
396
Integer k = cursor.next();
398
assertEquals(1, k.intValue());
399
k = cursor.current();
401
assertEquals(1, k.intValue());
402
assertTrue(cursor.delete());
403
assertTrue(!cursor.delete());
404
assertNull(cursor.current());
406
int expectKey = (index == priIndex) ? 2 : 1;
409
assertEquals(expectKey, k.intValue());
410
k = cursor.current();
412
assertEquals(expectKey, k.intValue());
413
assertTrue(cursor.delete());
414
assertTrue(!cursor.delete());
415
assertNull(cursor.current());
427
public void testDeleteFromSubIndex()
428
throws DatabaseException {
432
PrimaryIndex<Integer, MyEntity> priIndex =
433
store.getPrimaryIndex(Integer.class, MyEntity.class);
435
SecondaryIndex<Integer, Integer, MyEntity> secIndex =
436
store.getSecondaryIndex(priIndex, Integer.class, "secKey");
438
Transaction txn = txnBegin();
439
MyEntity e = new MyEntity();
442
priIndex.put(txn, e);
444
priIndex.put(txn, e);
446
priIndex.put(txn, e);
448
priIndex.put(txn, e);
451
EntityIndex<Integer, MyEntity> subIndex = secIndex.subIndex(1);
452
txn = txnBeginCursor();
453
e = subIndex.get(txn, 1, null);
454
assertEquals(1, e.priKey);
455
assertEquals(Integer.valueOf(1), e.secKey);
456
e = subIndex.get(txn, 2, null);
457
assertEquals(2, e.priKey);
458
assertEquals(Integer.valueOf(1), e.secKey);
459
e = subIndex.get(txn, 3, null);
460
assertEquals(3, e.priKey);
461
assertEquals(Integer.valueOf(1), e.secKey);
462
e = subIndex.get(txn, 5, null);
465
boolean deleted = subIndex.delete(txn, 1);
467
assertNull(subIndex.get(txn, 1, null));
468
assertNotNull(subIndex.get(txn, 2, null));
470
EntityCursor<MyEntity> cursor =
471
subIndex.entities(txn, getWriteCursorConfig());
472
boolean saw4 = false;
473
for (MyEntity e2 = cursor.first(); e2 != null; e2 = cursor.next()) {
474
if (e2.priKey == 3) {
477
if (e2.priKey == 4) {
483
assertNull(subIndex.get(txn, 1, null));
484
assertNull(subIndex.get(txn, 3, null));
485
assertNotNull(subIndex.get(txn, 2, null));
486
assertNotNull(subIndex.get(txn, 4, null));
493
static class MyEntity {
498
@SecondaryKey(relate=MANY_TO_ONE)
499
private Integer secKey;
501
private MyEntity() {}
504
public void testSharedSequence()
505
throws DatabaseException {
509
PrimaryIndex<Integer, SharedSequenceEntity1> priIndex1 =
510
store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
512
PrimaryIndex<Integer, SharedSequenceEntity2> priIndex2 =
513
store.getPrimaryIndex(Integer.class, SharedSequenceEntity2.class);
515
Transaction txn = txnBegin();
516
SharedSequenceEntity1 e1 = new SharedSequenceEntity1();
517
SharedSequenceEntity2 e2 = new SharedSequenceEntity2();
518
priIndex1.put(txn, e1);
519
assertEquals(1, e1.key);
520
priIndex2.putNoOverwrite(txn, e2);
521
assertEquals(Integer.valueOf(2), e2.key);
523
priIndex1.putNoOverwrite(txn, e1);
524
assertEquals(3, e1.key);
526
priIndex2.put(txn, e2);
527
assertEquals(Integer.valueOf(4), e2.key);
534
static class SharedSequenceEntity1 {
536
@PrimaryKey(sequence="shared")
541
static class SharedSequenceEntity2 {
543
@PrimaryKey(sequence="shared")
547
public void testSeparateSequence()
548
throws DatabaseException {
552
PrimaryIndex<Integer, SeparateSequenceEntity1> priIndex1 =
553
store.getPrimaryIndex
554
(Integer.class, SeparateSequenceEntity1.class);
556
PrimaryIndex<Integer, SeparateSequenceEntity2> priIndex2 =
557
store.getPrimaryIndex
558
(Integer.class, SeparateSequenceEntity2.class);
560
Transaction txn = txnBegin();
561
SeparateSequenceEntity1 e1 = new SeparateSequenceEntity1();
562
SeparateSequenceEntity2 e2 = new SeparateSequenceEntity2();
563
priIndex1.put(txn, e1);
564
assertEquals(1, e1.key);
565
priIndex2.putNoOverwrite(txn, e2);
566
assertEquals(Integer.valueOf(1), e2.key);
568
priIndex1.putNoOverwrite(txn, e1);
569
assertEquals(2, e1.key);
571
priIndex2.put(txn, e2);
572
assertEquals(Integer.valueOf(2), e2.key);
579
static class SeparateSequenceEntity1 {
581
@PrimaryKey(sequence="seq1")
586
static class SeparateSequenceEntity2 {
588
@PrimaryKey(sequence="seq2")
592
public void testCompositeSequence()
593
throws DatabaseException {
597
PrimaryIndex<CompositeSequenceEntity1.Key, CompositeSequenceEntity1>
599
store.getPrimaryIndex
600
(CompositeSequenceEntity1.Key.class,
601
CompositeSequenceEntity1.class);
603
PrimaryIndex<CompositeSequenceEntity2.Key, CompositeSequenceEntity2>
605
store.getPrimaryIndex
606
(CompositeSequenceEntity2.Key.class,
607
CompositeSequenceEntity2.class);
609
Transaction txn = txnBegin();
610
CompositeSequenceEntity1 e1 = new CompositeSequenceEntity1();
611
CompositeSequenceEntity2 e2 = new CompositeSequenceEntity2();
612
priIndex1.put(txn, e1);
613
assertEquals(1, e1.key.key);
614
priIndex2.putNoOverwrite(txn, e2);
615
assertEquals(Integer.valueOf(1), e2.key.key);
617
priIndex1.putNoOverwrite(txn, e1);
618
assertEquals(2, e1.key.key);
620
priIndex2.put(txn, e2);
621
assertEquals(Integer.valueOf(2), e2.key.key);
624
txn = txnBeginCursor();
625
EntityCursor<CompositeSequenceEntity1> c1 =
626
priIndex1.entities(txn, null);
628
assertEquals(2, e1.key.key);
630
assertEquals(1, e1.key.key);
636
txn = txnBeginCursor();
637
EntityCursor<CompositeSequenceEntity2> c2 =
638
priIndex2.entities(txn, null);
640
assertEquals(Integer.valueOf(2), e2.key.key);
642
assertEquals(Integer.valueOf(1), e2.key.key);
652
static class CompositeSequenceEntity1 {
655
static class Key implements Comparable<Key> {
660
public int compareTo(Key o) {
661
/* Reverse the natural order. */
666
@PrimaryKey(sequence="seq1")
671
* Same as CompositeSequenceEntity1 but using Integer rather than int for
675
static class CompositeSequenceEntity2 {
678
static class Key implements Comparable<Key> {
683
public int compareTo(Key o) {
684
/* Reverse the natural order. */
689
@PrimaryKey(sequence="seq2")
694
* When opening read-only, secondaries are not opened when the primary is
695
* opened, causing a different code path to be used for opening
696
* secondaries. For a RawStore in particular, this caused an unreported
697
* NullPointerException in JE 3.0.12. No SR was created because the use
698
* case is very obscure and was discovered by code inspection.
700
public void testOpenRawStoreReadOnly()
701
throws DatabaseException {
704
store.getPrimaryIndex(Integer.class, MyEntity.class);
707
StoreConfig config = new StoreConfig();
708
config.setReadOnly(true);
709
config.setTransactional(envConfig.getTransactional());
710
RawStore rawStore = new RawStore(env, "test", config);
712
String clsName = MyEntity.class.getName();
713
rawStore.getSecondaryIndex(clsName, "secKey");
719
* When opening an X_TO_MANY secondary that has a persistent key class, the
720
* key class was not recognized as being persistent if it was never before
721
* referenced when getSecondaryIndex was called. This was a bug in JE
722
* 3.0.12, reported on OTN. [#15103]
724
public void testToManyKeyClass()
725
throws DatabaseException {
729
PrimaryIndex<Integer, ToManyKeyEntity> priIndex =
730
store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
731
SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex =
732
store.getSecondaryIndex(priIndex, ToManyKey.class, "key2");
734
priIndex.put(new ToManyKeyEntity());
735
secIndex.get(new ToManyKey());
741
* Test a fix for a bug where opening a TO_MANY secondary index would fail
742
* fail with "IllegalArgumentException: Wrong secondary key class: ..."
743
* when the store was opened read-only. [#15156]
745
public void testToManyReadOnly()
746
throws DatabaseException {
749
PrimaryIndex<Integer, ToManyKeyEntity> priIndex =
750
store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
751
priIndex.put(new ToManyKeyEntity());
755
priIndex = store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
756
SecondaryIndex<ToManyKey, Integer, ToManyKeyEntity> secIndex =
757
store.getSecondaryIndex(priIndex, ToManyKey.class, "key2");
758
secIndex.get(new ToManyKey());
763
static class ToManyKey {
770
static class ToManyKeyEntity {
775
@SecondaryKey(relate=ONE_TO_MANY)
779
key2 = new HashSet<ToManyKey>();
780
key2.add(new ToManyKey());
787
* When Y is opened and X has a key with relatedEntity=Y.class, X should
788
* be opened automatically. If X is not opened, foreign key constraints
789
* will not be enforced. [#15358]
791
public void testAutoOpenRelatedEntity()
792
throws DatabaseException {
794
PrimaryIndex<Integer, RelatedY> priY;
795
PrimaryIndex<Integer, RelatedX> priX;
797
/* Opening X should create (and open) Y and enforce constraints. */
799
priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
800
PersistTestUtils.assertDbExists
801
(true, env, STORE_NAME, RelatedY.class.getName(), null);
802
if (isTransactional) {
803
/* Constraint enforcement requires transactions. */
805
priX.put(new RelatedX());
807
} catch (DatabaseException e) {
809
("" + e.getMessage(), (e.getMessage().indexOf
810
("foreign key not allowed: it is not present") >= 0) ||
811
(e.getMessage().indexOf("DB_FOREIGN_CONFLICT") >= 0));
814
priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
815
priY.put(new RelatedY());
816
priX.put(new RelatedX());
819
/* Delete should cascade even when X is not opened explicitly. */
821
priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
822
assertEquals(1, priY.count());
824
assertEquals(0, priY.count());
825
priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
826
assertEquals(0, priX.count()); /* Failed prior to [#15358] fix. */
831
static class RelatedX {
836
@SecondaryKey(relate=ONE_TO_ONE,
837
relatedEntity=RelatedY.class,
838
onRelatedEntityDelete=CASCADE)
846
static class RelatedY {
855
public void testSecondaryBulkLoad1()
856
throws DatabaseException {
858
doSecondaryBulkLoad(true);
861
public void testSecondaryBulkLoad2()
862
throws DatabaseException {
864
doSecondaryBulkLoad(false);
867
private void doSecondaryBulkLoad(boolean closeAndOpenNormally)
868
throws DatabaseException {
870
PrimaryIndex<Integer, RelatedX> priX;
871
PrimaryIndex<Integer, RelatedY> priY;
872
SecondaryIndex<Integer, Integer, RelatedX> secX;
874
/* Open priX with SecondaryBulkLoad=true. */
875
StoreConfig config = new StoreConfig();
876
config.setAllowCreate(true);
877
config.setSecondaryBulkLoad(true);
880
/* Getting priX should not create the secondary index. */
881
priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
882
PersistTestUtils.assertDbExists
883
(false, env, STORE_NAME, RelatedX.class.getName(), "key2");
885
/* We can put records that violate the secondary key constraint. */
886
priX.put(new RelatedX());
888
if (closeAndOpenNormally) {
889
/* Open normally and attempt to populate the secondary. */
892
if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
893
/* Constraint enforcement requires transactions. */
895
/* Before adding the foreign key, constraint is violated. */
896
priX = store.getPrimaryIndex(Integer.class,
899
} catch (DatabaseException e) {
902
e.toString().contains("foreign key not allowed"));
905
/* Open priX with SecondaryBulkLoad=true. */
908
/* Add the foreign key to avoid the constraint error. */
909
priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
910
priY.put(new RelatedY());
911
/* Open normally and the secondary will be populated. */
914
priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
915
PersistTestUtils.assertDbExists
916
(true, env, STORE_NAME, RelatedX.class.getName(), "key2");
917
secX = store.getSecondaryIndex(priX, Integer.class, "key2");
919
/* Get secondary index explicitly and it will be populated. */
920
if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
921
/* Constraint enforcement requires transactions. */
923
/* Before adding the foreign key, constraint is violated. */
924
secX = store.getSecondaryIndex(priX, Integer.class,
927
} catch (DatabaseException e) {
930
e.toString().contains("foreign key not allowed"));
933
/* Add the foreign key. */
934
priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
935
priY.put(new RelatedY());
936
secX = store.getSecondaryIndex(priX, Integer.class, "key2");
937
PersistTestUtils.assertDbExists
938
(true, env, STORE_NAME, RelatedX.class.getName(), "key2");
941
RelatedX x = secX.get(88);
946
public void testPersistentFields()
947
throws DatabaseException {
950
PrimaryIndex<Integer, PersistentFields> pri =
951
store.getPrimaryIndex(Integer.class, PersistentFields.class);
952
PersistentFields o1 = new PersistentFields(-1, 1, 2, 3, 4, 5, 6);
953
assertNull(pri.put(o1));
954
PersistentFields o2 = pri.get(-1);
956
assertEquals(0, o2.transient1);
957
assertEquals(0, o2.transient2);
958
assertEquals(0, o2.transient3);
959
assertEquals(4, o2.persistent1);
960
assertEquals(5, o2.persistent2);
961
assertEquals(6, o2.persistent3);
966
static class PersistentFields {
970
transient int transient1;
971
@NotPersistent int transient2;
972
@NotPersistent transient int transient3;
975
@NotTransient int persistent2;
976
@NotTransient transient int persistent3;
978
PersistentFields(int k,
994
private PersistentFields() {}
998
* When a primary or secondary has a persistent key class, the key class
999
* was not recognized as being persistent when getPrimaryConfig,
1000
* getSecondaryConfig, or getSubclassIndex was called, if that key class
1001
* was not previously referenced. All three cases are tested by calling
1002
* getSecondaryConfig. This was a bug in JE 3.3.69, reported on OTN.
1005
public void testKeyClassInitialization()
1006
throws DatabaseException {
1009
store.getSecondaryConfig(ToManyKeyEntity.class, "key2");
1013
public void testKeyName()
1014
throws DatabaseException {
1018
PrimaryIndex<Long, BookEntity> pri1 =
1019
store.getPrimaryIndex(Long.class, BookEntity.class);
1020
PrimaryIndex<Long, AuthorEntity> pri2 =
1021
store.getPrimaryIndex(Long.class, AuthorEntity.class);
1023
BookEntity book = new BookEntity();
1025
AuthorEntity author = new AuthorEntity();
1026
author.bookIds.add(book.bookId);
1032
pri1 = store.getPrimaryIndex(Long.class, BookEntity.class);
1033
pri2 = store.getPrimaryIndex(Long.class, AuthorEntity.class);
1034
book = pri1.get(1L);
1035
assertNotNull(book);
1036
author = pri2.get(1L);
1037
assertNotNull(author);
1042
static class AuthorEntity {
1044
@PrimaryKey(sequence="authorSeq")
1047
@SecondaryKey(relate=MANY_TO_MANY, relatedEntity=BookEntity.class,
1048
name="bookId", onRelatedEntityDelete=NULLIFY)
1049
Set<Long> bookIds = new HashSet<Long>();
1053
static class BookEntity {
1055
@PrimaryKey(sequence="bookSeq")
1060
* Checks that we get an appropriate exception when storing an entity
1061
* subclass instance, which contains a secondary key, without registering
1062
* the subclass up front. [#16399]
1064
public void testPutEntitySubclassWithoutRegisterClass()
1065
throws DatabaseException {
1069
final PrimaryIndex<Long, Statement> pri =
1070
store.getPrimaryIndex(Long.class, Statement.class);
1072
final Transaction txn = txnBegin();
1073
pri.put(txn, new Statement(1));
1075
pri.put(txn, new ExtendedStatement(2, null));
1077
} catch (IllegalArgumentException expected) {
1078
assertTrue(expected.toString(), expected.getMessage().contains
1079
("Entity subclasses defining a secondary key must be " +
1080
"registered by calling EntityModel.registerClass or " +
1081
"EntityStore.getSubclassIndex before storing an instance " +
1082
"of the subclass: " + ExtendedStatement.class.getName()));
1090
* Checks that registerClass avoids an exception when storing an entity
1091
* subclass instance, which defines a secondary key. [#16399]
1093
public void testPutEntitySubclassWithRegisterClass()
1094
throws DatabaseException {
1096
open(ExtendedStatement.class);
1098
final PrimaryIndex<Long, Statement> pri =
1099
store.getPrimaryIndex(Long.class, Statement.class);
1101
final Transaction txn = txnBegin();
1102
pri.put(txn, new Statement(1));
1103
pri.put(txn, new ExtendedStatement(2, "abc"));
1106
final SecondaryIndex<String, Long, ExtendedStatement> sec =
1107
store.getSubclassIndex(pri, ExtendedStatement.class,
1108
String.class, "name");
1110
ExtendedStatement o = sec.get("abc");
1112
assertEquals(2, o.id);
1118
* Same as testPutEntitySubclassWithRegisterClass but store the first
1119
* instance of the subclass after closing and reopening the store,
1120
* *without* calling registerClass. This ensures that a single call to
1121
* registerClass is sufficient and subsequent use of the store does not
1122
* require it. [#16399]
1124
public void testPutEntitySubclassWithRegisterClass2()
1125
throws DatabaseException {
1127
open(ExtendedStatement.class);
1129
PrimaryIndex<Long, Statement> pri =
1130
store.getPrimaryIndex(Long.class, Statement.class);
1132
Transaction txn = txnBegin();
1133
pri.put(txn, new Statement(1));
1139
pri = store.getPrimaryIndex(Long.class, Statement.class);
1142
pri.put(txn, new ExtendedStatement(2, "abc"));
1145
final SecondaryIndex<String, Long, ExtendedStatement> sec =
1146
store.getSubclassIndex(pri, ExtendedStatement.class,
1147
String.class, "name");
1149
ExtendedStatement o = sec.get("abc");
1151
assertEquals(2, o.id);
1157
* Checks that getSubclassIndex can be used instead of registerClass to
1158
* avoid an exception when storing an entity subclass instance, which
1159
* defines a secondary key. [#16399]
1161
public void testPutEntitySubclassWithGetSubclassIndex()
1162
throws DatabaseException {
1166
final PrimaryIndex<Long, Statement> pri =
1167
store.getPrimaryIndex(Long.class, Statement.class);
1169
final SecondaryIndex<String, Long, ExtendedStatement> sec =
1170
store.getSubclassIndex(pri, ExtendedStatement.class,
1171
String.class, "name");
1173
final Transaction txn = txnBegin();
1174
pri.put(txn, new Statement(1));
1175
pri.put(txn, new ExtendedStatement(2, "abc"));
1178
ExtendedStatement o = sec.get("abc");
1180
assertEquals(2, o.id);
1186
* Same as testPutEntitySubclassWithGetSubclassIndex2 but store the first
1187
* instance of the subclass after closing and reopening the store,
1188
* *without* calling getSubclassIndex. This ensures that a single call to
1189
* getSubclassIndex is sufficient and subsequent use of the store does not
1190
* require it. [#16399]
1192
public void testPutEntitySubclassWithGetSubclassIndex2()
1193
throws DatabaseException {
1197
PrimaryIndex<Long, Statement> pri =
1198
store.getPrimaryIndex(Long.class, Statement.class);
1200
SecondaryIndex<String, Long, ExtendedStatement> sec =
1201
store.getSubclassIndex(pri, ExtendedStatement.class,
1202
String.class, "name");
1204
Transaction txn = txnBegin();
1205
pri.put(txn, new Statement(1));
1211
pri = store.getPrimaryIndex(Long.class, Statement.class);
1214
pri.put(txn, new ExtendedStatement(2, "abc"));
1217
sec = store.getSubclassIndex(pri, ExtendedStatement.class,
1218
String.class, "name");
1220
ExtendedStatement o = sec.get("abc");
1222
assertEquals(2, o.id);
1228
* Checks that secondary population occurs only once when an index is
1229
* created, not every time it is opened, even when it is empty. This is a
1230
* JE-only test because we don't have a portable way to get stats that
1231
* indicate whether primary reads were performed. [#16399]
1235
static class Statement {
1240
Statement(long id) {
1244
private Statement() {}
1248
static class ExtendedStatement extends Statement {
1250
@SecondaryKey(relate=MANY_TO_ONE)
1253
ExtendedStatement(long id, String name) {
1258
private ExtendedStatement() {}
1261
public void testCustomCompare()
1262
throws DatabaseException {
1266
PrimaryIndex<ReverseIntKey, CustomCompareEntity>
1267
priIndex = store.getPrimaryIndex
1268
(ReverseIntKey.class, CustomCompareEntity.class);
1270
SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
1271
secIndex1 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
1274
SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
1275
secIndex2 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
1278
Transaction txn = txnBegin();
1279
for (int i = 1; i <= 5; i += 1) {
1280
assertTrue(priIndex.putNoOverwrite(txn,
1281
new CustomCompareEntity(i)));
1285
txn = txnBeginCursor();
1286
EntityCursor<CustomCompareEntity> c = priIndex.entities(txn, null);
1287
for (int i = 5; i >= 1; i -= 1) {
1288
CustomCompareEntity e = c.next();
1290
assertEquals(new ReverseIntKey(i), e.key);
1295
txn = txnBeginCursor();
1296
c = secIndex1.entities(txn, null);
1297
for (int i = -1; i >= -5; i -= 1) {
1298
CustomCompareEntity e = c.next();
1300
assertEquals(new ReverseIntKey(-i), e.key);
1301
assertEquals(new ReverseIntKey(i), e.secKey1);
1306
txn = txnBeginCursor();
1307
c = secIndex2.entities(txn, null);
1308
for (int i = -1; i >= -5; i -= 1) {
1309
CustomCompareEntity e = c.next();
1311
assertEquals(new ReverseIntKey(-i), e.key);
1312
assertTrue(e.secKey2.contains(new ReverseIntKey(i)));
1321
static class CustomCompareEntity {
1324
private ReverseIntKey key;
1326
@SecondaryKey(relate=MANY_TO_ONE)
1327
private ReverseIntKey secKey1;
1329
@SecondaryKey(relate=ONE_TO_MANY)
1330
private Set<ReverseIntKey> secKey2 = new HashSet<ReverseIntKey>();
1332
private CustomCompareEntity() {}
1334
CustomCompareEntity(int i) {
1335
key = new ReverseIntKey(i);
1336
secKey1 = new ReverseIntKey(-i);
1337
secKey2.add(new ReverseIntKey(-i));
1342
static class ReverseIntKey implements Comparable<ReverseIntKey> {
1347
public int compareTo(ReverseIntKey o) {
1348
/* Reverse the natural order. */
1352
private ReverseIntKey() {}
1354
ReverseIntKey(int key) {
1359
public boolean equals(Object o) {
1360
return key == ((ReverseIntKey) o).key;
1364
public int hashCode() {
1369
public String toString() {
1370
return "Key = " + key;
1375
* Ensures that custom comparators are persisted and work correctly during
1376
* recovery. JE recovery uses comparators, so they are serialized and
1377
* stored in the DatabaseImpl. They are deserialized during recovery prior
1378
* to opening the EntityStore and its format catalog. But the formats are
1379
* needed by the comparator, so they are specially created when needed.
1381
* In particular we need to ensure that enum key fields work correctly,
1382
* since their formats are not static (like simple type formats are).
1385
* Note that we don't need to actually cause a recovery in order to test
1386
* the deserialization and subsequent use of comparators. The JE
1387
* DatabaseConfig.setBtreeComparator method serializes and deserializes the
1388
* comparator. The comparator is initialized on its first use, just as if
1389
* recovery were run.
1391
public void testStoredComparators()
1392
throws DatabaseException {
1396
PrimaryIndex<StoredComparatorEntity.Key,
1397
StoredComparatorEntity> priIndex =
1398
store.getPrimaryIndex(StoredComparatorEntity.Key.class,
1399
StoredComparatorEntity.class);
1401
SecondaryIndex<StoredComparatorEntity.MyEnum,
1402
StoredComparatorEntity.Key,
1403
StoredComparatorEntity> secIndex =
1404
store.getSecondaryIndex
1405
(priIndex, StoredComparatorEntity.MyEnum.class, "secKey");
1407
final StoredComparatorEntity.Key[] priKeys =
1408
new StoredComparatorEntity.Key[] {
1409
new StoredComparatorEntity.Key
1410
(StoredComparatorEntity.MyEnum.A, 1,
1411
StoredComparatorEntity.MyEnum.A),
1412
new StoredComparatorEntity.Key
1413
(StoredComparatorEntity.MyEnum.A, 1,
1414
StoredComparatorEntity.MyEnum.B),
1415
new StoredComparatorEntity.Key
1416
(StoredComparatorEntity.MyEnum.A, 2,
1417
StoredComparatorEntity.MyEnum.A),
1418
new StoredComparatorEntity.Key
1419
(StoredComparatorEntity.MyEnum.A, 2,
1420
StoredComparatorEntity.MyEnum.B),
1421
new StoredComparatorEntity.Key
1422
(StoredComparatorEntity.MyEnum.B, 1,
1423
StoredComparatorEntity.MyEnum.A),
1424
new StoredComparatorEntity.Key
1425
(StoredComparatorEntity.MyEnum.B, 1,
1426
StoredComparatorEntity.MyEnum.B),
1427
new StoredComparatorEntity.Key
1428
(StoredComparatorEntity.MyEnum.C, 0,
1429
StoredComparatorEntity.MyEnum.C),
1432
final StoredComparatorEntity.MyEnum[] secKeys =
1433
new StoredComparatorEntity.MyEnum[] {
1434
StoredComparatorEntity.MyEnum.C,
1435
StoredComparatorEntity.MyEnum.B,
1436
StoredComparatorEntity.MyEnum.A,
1438
StoredComparatorEntity.MyEnum.A,
1439
StoredComparatorEntity.MyEnum.B,
1440
StoredComparatorEntity.MyEnum.C,
1443
assertEquals(priKeys.length, secKeys.length);
1444
final int nEntities = priKeys.length;
1446
Transaction txn = txnBegin();
1447
for (int i = 0; i < nEntities; i += 1) {
1449
new StoredComparatorEntity(priKeys[i], secKeys[i]));
1453
txn = txnBeginCursor();
1454
EntityCursor<StoredComparatorEntity> entities =
1455
priIndex.entities(txn, null);
1456
for (int i = nEntities - 1; i >= 0; i -= 1) {
1457
StoredComparatorEntity e = entities.next();
1459
assertEquals(priKeys[i], e.key);
1460
assertEquals(secKeys[i], e.secKey);
1462
assertNull(entities.next());
1466
txn = txnBeginCursor();
1467
entities = secIndex.entities(txn, null);
1468
for (StoredComparatorEntity.MyEnum myEnum :
1469
EnumSet.allOf(StoredComparatorEntity.MyEnum.class)) {
1470
for (int i = 0; i < nEntities; i += 1) {
1471
if (secKeys[i] == myEnum) {
1472
StoredComparatorEntity e = entities.next();
1474
assertEquals(priKeys[i], e.key);
1475
assertEquals(secKeys[i], e.secKey);
1479
assertNull(entities.next());
1487
static class StoredComparatorEntity {
1489
enum MyEnum { A, B, C };
1492
static class Key implements Comparable<Key> {
1505
Key(MyEnum f1, Integer f2, MyEnum f3) {
1511
public int compareTo(Key o) {
1512
/* Reverse the natural order. */
1513
int i = f1.compareTo(o.f1);
1514
if (i != 0) return -i;
1515
i = f2.compareTo(o.f2);
1516
if (i != 0) return -i;
1517
i = f3.compareTo(o.f3);
1518
if (i != 0) return -i;
1523
public boolean equals(Object other) {
1524
if (!(other instanceof Key)) {
1527
Key o = (Key) other;
1528
return f1 == o.f1 &&
1534
public int hashCode() {
1535
return f1.ordinal() + f2 + f3.ordinal();
1539
public String toString() {
1540
return "[Key " + f1 + ' ' + f2 + ' ' + f3 + ']';
1547
@SecondaryKey(relate=MANY_TO_ONE)
1548
private MyEnum secKey;
1550
private StoredComparatorEntity() {}
1552
StoredComparatorEntity(Key key, MyEnum secKey) {
1554
this.secKey = secKey;
1558
public String toString() {
1559
return "[pri = " + key + " sec = " + secKey + ']';