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