1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002,2008 Oracle. All rights reserved. 5 * 6 * $Id: OperationTest.java,v 1.2 2008/02/12 19:15:26 mark Exp $ 7 */ 8 9package com.sleepycat.persist.test; 10 11import static com.sleepycat.persist.model.Relationship.MANY_TO_ONE; 12import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY; 13import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE; 14import static com.sleepycat.persist.model.DeleteAction.CASCADE; 15 16import java.util.ArrayList; 17import java.util.HashSet; 18import java.util.List; 19import java.util.Set; 20 21import junit.framework.Test; 22 23import com.sleepycat.db.Database; 24import com.sleepycat.db.DatabaseConfig; 25import com.sleepycat.db.DatabaseException; 26import com.sleepycat.db.Transaction; 27import com.sleepycat.persist.EntityCursor; 28import com.sleepycat.persist.EntityIndex; 29import com.sleepycat.persist.EntityStore; 30import com.sleepycat.persist.PrimaryIndex; 31import com.sleepycat.persist.SecondaryIndex; 32import com.sleepycat.persist.StoreConfig; 33import com.sleepycat.persist.impl.Store; 34import com.sleepycat.persist.model.Entity; 35import com.sleepycat.persist.model.KeyField; 36import com.sleepycat.persist.model.Persistent; 37import com.sleepycat.persist.model.PrimaryKey; 38import com.sleepycat.persist.model.SecondaryKey; 39import com.sleepycat.persist.raw.RawStore; 40import com.sleepycat.util.test.TxnTestCase; 41 42/** 43 * Tests misc store and index operations that are not tested by IndexTest. 44 * 45 * @author Mark Hayes 46 */ 47public class OperationTest extends TxnTestCase { 48 49 private static final String STORE_NAME = "test"; 50 51 public static Test suite() { 52 return txnTestSuite(OperationTest.class, null, null); 53 } 54 55 private EntityStore store; 56 57 private void openReadOnly() 58 throws DatabaseException { 59 60 StoreConfig config = new StoreConfig(); 61 config.setReadOnly(true); 62 open(config); 63 } 64 65 private void open() 66 throws DatabaseException { 67 68 StoreConfig config = new StoreConfig(); 69 config.setAllowCreate(envConfig.getAllowCreate()); 70 open(config); 71 } 72 73 private void open(StoreConfig config) 74 throws DatabaseException { 75 76 config.setTransactional(envConfig.getTransactional()); 77 store = new EntityStore(env, STORE_NAME, config); 78 } 79 80 private void close() 81 throws DatabaseException { 82 83 store.close(); 84 store = null; 85 } 86 87 /** 88 * The store must be closed before closing the environment. 89 */ 90 public void tearDown() 91 throws Exception { 92 93 try { 94 if (store != null) { 95 store.close(); 96 } 97 } catch (Throwable e) { 98 System.out.println("During tearDown: " + e); 99 } 100 store = null; 101 super.tearDown(); 102 } 103 104 public void testReadOnly() 105 throws DatabaseException { 106 107 open(); 108 PrimaryIndex<Integer,SharedSequenceEntity1> priIndex = 109 store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class); 110 Transaction txn = txnBegin(); 111 SharedSequenceEntity1 e = new SharedSequenceEntity1(); 112 priIndex.put(txn, e); 113 assertEquals(1, e.key); 114 txnCommit(txn); 115 close(); 116 117 /* 118 * Check that we can open the store read-only and read the records 119 * written above. 120 */ 121 openReadOnly(); 122 priIndex = 123 store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class); 124 e = priIndex.get(1); 125 assertNotNull(e); 126 close(); 127 } 128 129 130 public void testUninitializedCursor() 131 throws DatabaseException { 132 133 open(); 134 135 PrimaryIndex<Integer,MyEntity> priIndex = 136 store.getPrimaryIndex(Integer.class, MyEntity.class); 137 138 Transaction txn = txnBeginCursor(); 139 140 MyEntity e = new MyEntity(); 141 e.priKey = 1; 142 e.secKey = 1; 143 priIndex.put(txn, e); 144 145 EntityCursor<MyEntity> entities = priIndex.entities(txn, null); 146 try { 147 entities.nextDup(); 148 fail(); 149 } catch (IllegalStateException expected) {} 150 try { 151 entities.prevDup(); 152 fail(); 153 } catch (IllegalStateException expected) {} 154 try { 155 entities.current(); 156 fail(); 157 } catch (IllegalStateException expected) {} 158 try { 159 entities.delete(); 160 fail(); 161 } catch (IllegalStateException expected) {} 162 try { 163 entities.update(e); 164 fail(); 165 } catch (IllegalStateException expected) {} 166 try { 167 entities.count(); 168 fail(); 169 } catch (IllegalStateException expected) {} 170 171 entities.close(); 172 txnCommit(txn); 173 close(); 174 } 175 176 public void testCursorCount() 177 throws DatabaseException { 178 179 open(); 180 181 PrimaryIndex<Integer,MyEntity> priIndex = 182 store.getPrimaryIndex(Integer.class, MyEntity.class); 183 184 SecondaryIndex<Integer,Integer,MyEntity> secIndex = 185 store.getSecondaryIndex(priIndex, Integer.class, "secKey"); 186 187 Transaction txn = txnBeginCursor(); 188 189 MyEntity e = new MyEntity(); 190 e.priKey = 1; 191 e.secKey = 1; 192 priIndex.put(txn, e); 193 194 EntityCursor<MyEntity> cursor = secIndex.entities(txn, null); 195 cursor.next(); 196 assertEquals(1, cursor.count()); 197 cursor.close(); 198 199 e.priKey = 2; 200 priIndex.put(txn, e); 201 cursor = secIndex.entities(txn, null); 202 cursor.next(); 203 assertEquals(2, cursor.count()); 204 cursor.close(); 205 206 txnCommit(txn); 207 close(); 208 } 209 210 public void testCursorUpdate() 211 throws DatabaseException { 212 213 open(); 214 215 PrimaryIndex<Integer,MyEntity> priIndex = 216 store.getPrimaryIndex(Integer.class, MyEntity.class); 217 218 SecondaryIndex<Integer,Integer,MyEntity> secIndex = 219 store.getSecondaryIndex(priIndex, Integer.class, "secKey"); 220 221 Transaction txn = txnBeginCursor(); 222 223 Integer k; 224 MyEntity e = new MyEntity(); 225 e.priKey = 1; 226 e.secKey = 2; 227 priIndex.put(txn, e); 228 229 /* update() with primary entity cursor. */ 230 EntityCursor<MyEntity> entities = priIndex.entities(txn, null); 231 e = entities.next(); 232 assertNotNull(e); 233 assertEquals(1, e.priKey); 234 assertEquals(Integer.valueOf(2), e.secKey); 235 e.secKey = null; 236 assertTrue(entities.update(e)); 237 e = entities.current(); 238 assertNotNull(e); 239 assertEquals(1, e.priKey); 240 assertEquals(null, e.secKey); 241 e.secKey = 3; 242 assertTrue(entities.update(e)); 243 e = entities.current(); 244 assertNotNull(e); 245 assertEquals(1, e.priKey); 246 assertEquals(Integer.valueOf(3), e.secKey); 247 entities.close(); 248 249 /* update() with primary keys cursor. */ 250 EntityCursor<Integer> keys = priIndex.keys(txn, null); 251 k = keys.next(); 252 assertNotNull(k); 253 assertEquals(Integer.valueOf(1), k); 254 try { 255 keys.update(2); 256 fail(); 257 } catch (UnsupportedOperationException expected) { 258 } 259 keys.close(); 260 261 /* update() with secondary entity cursor. */ 262 entities = secIndex.entities(txn, null); 263 e = entities.next(); 264 assertNotNull(e); 265 assertEquals(1, e.priKey); 266 assertEquals(Integer.valueOf(3), e.secKey); 267 try { 268 entities.update(e); 269 fail(); 270 } catch (UnsupportedOperationException expected) { 271 } catch (IllegalArgumentException expectedForDbCore) { 272 } 273 entities.close(); 274 275 /* update() with secondary keys cursor. */ 276 keys = secIndex.keys(txn, null); 277 k = keys.next(); 278 assertNotNull(k); 279 assertEquals(Integer.valueOf(3), k); 280 try { 281 keys.update(k); 282 fail(); 283 } catch (UnsupportedOperationException expected) { 284 } 285 keys.close(); 286 287 txnCommit(txn); 288 close(); 289 } 290 291 public void testCursorDelete() 292 throws DatabaseException { 293 294 open(); 295 296 PrimaryIndex<Integer,MyEntity> priIndex = 297 store.getPrimaryIndex(Integer.class, MyEntity.class); 298 299 SecondaryIndex<Integer,Integer,MyEntity> secIndex = 300 store.getSecondaryIndex(priIndex, Integer.class, "secKey"); 301 302 Transaction txn = txnBeginCursor(); 303 304 /* delete() with primary and secondary entities cursor. */ 305 306 for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) { 307 308 MyEntity e = new MyEntity(); 309 e.priKey = 1; 310 e.secKey = 1; 311 priIndex.put(txn, e); 312 e.priKey = 2; 313 priIndex.put(txn, e); 314 315 EntityCursor<MyEntity> cursor = index.entities(txn, null); 316 317 e = cursor.next(); 318 assertNotNull(e); 319 assertEquals(1, e.priKey); 320 e = cursor.current(); 321 assertNotNull(e); 322 assertEquals(1, e.priKey); 323 assertTrue(cursor.delete()); 324 assertTrue(!cursor.delete()); 325 assertNull(cursor.current()); 326 327 e = cursor.next(); 328 assertNotNull(e); 329 assertEquals(2, e.priKey); 330 e = cursor.current(); 331 assertNotNull(e); 332 assertEquals(2, e.priKey); 333 assertTrue(cursor.delete()); 334 assertTrue(!cursor.delete()); 335 assertNull(cursor.current()); 336 337 e = cursor.next(); 338 assertNull(e); 339 340 if (index == priIndex) { 341 e = new MyEntity(); 342 e.priKey = 2; 343 e.secKey = 1; 344 assertTrue(!cursor.update(e)); 345 } 346 347 cursor.close(); 348 } 349 350 /* delete() with primary and secondary keys cursor. */ 351 352 for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) { 353 354 MyEntity e = new MyEntity(); 355 e.priKey = 1; 356 e.secKey = 1; 357 priIndex.put(txn, e); 358 e.priKey = 2; 359 priIndex.put(txn, e); 360 361 EntityCursor<Integer> cursor = index.keys(txn, null); 362 363 Integer k = cursor.next(); 364 assertNotNull(k); 365 assertEquals(1, k.intValue()); 366 k = cursor.current(); 367 assertNotNull(k); 368 assertEquals(1, k.intValue()); 369 assertTrue(cursor.delete()); 370 assertTrue(!cursor.delete()); 371 assertNull(cursor.current()); 372 373 int expectKey = (index == priIndex) ? 2 : 1; 374 k = cursor.next(); 375 assertNotNull(k); 376 assertEquals(expectKey, k.intValue()); 377 k = cursor.current(); 378 assertNotNull(k); 379 assertEquals(expectKey, k.intValue()); 380 assertTrue(cursor.delete()); 381 assertTrue(!cursor.delete()); 382 assertNull(cursor.current()); 383 384 k = cursor.next(); 385 assertNull(k); 386 387 cursor.close(); 388 } 389 390 txnCommit(txn); 391 close(); 392 } 393 394 public void testDeleteFromSubIndex() 395 throws DatabaseException { 396 397 open(); 398 399 PrimaryIndex<Integer,MyEntity> priIndex = 400 store.getPrimaryIndex(Integer.class, MyEntity.class); 401 402 SecondaryIndex<Integer,Integer,MyEntity> secIndex = 403 store.getSecondaryIndex(priIndex, Integer.class, "secKey"); 404 405 Transaction txn = txnBegin(); 406 MyEntity e = new MyEntity(); 407 e.secKey = 1; 408 e.priKey = 1; 409 priIndex.put(txn, e); 410 e.priKey = 2; 411 priIndex.put(txn, e); 412 e.priKey = 3; 413 priIndex.put(txn, e); 414 e.priKey = 4; 415 priIndex.put(txn, e); 416 txnCommit(txn); 417 418 EntityIndex<Integer,MyEntity> subIndex = secIndex.subIndex(1); 419 txn = txnBeginCursor(); 420 e = subIndex.get(txn, 1, null); 421 assertEquals(1, e.priKey); 422 assertEquals(Integer.valueOf(1), e.secKey); 423 e = subIndex.get(txn, 2, null); 424 assertEquals(2, e.priKey); 425 assertEquals(Integer.valueOf(1), e.secKey); 426 e = subIndex.get(txn, 3, null); 427 assertEquals(3, e.priKey); 428 assertEquals(Integer.valueOf(1), e.secKey); 429 e = subIndex.get(txn, 5, null); 430 assertNull(e); 431 432 boolean deleted = subIndex.delete(txn, 1); 433 assertTrue(deleted); 434 assertNull(subIndex.get(txn, 1, null)); 435 assertNotNull(subIndex.get(txn, 2, null)); 436 437 EntityCursor<MyEntity> cursor = subIndex.entities(txn, null); 438 boolean saw4 = false; 439 for (MyEntity e2 = cursor.first(); e2 != null; e2 = cursor.next()) { 440 if (e2.priKey == 3) { 441 cursor.delete(); 442 } 443 if (e2.priKey == 4) { 444 saw4 = true; 445 } 446 } 447 cursor.close(); 448 assertTrue(saw4); 449 assertNull(subIndex.get(txn, 1, null)); 450 assertNull(subIndex.get(txn, 3, null)); 451 assertNotNull(subIndex.get(txn, 2, null)); 452 assertNotNull(subIndex.get(txn, 4, null)); 453 454 txnCommit(txn); 455 close(); 456 } 457 458 @Entity 459 static class MyEntity { 460 461 @PrimaryKey 462 private int priKey; 463 464 @SecondaryKey(relate=MANY_TO_ONE) 465 private Integer secKey; 466 467 private MyEntity() {} 468 } 469 470 public void testSharedSequence() 471 throws DatabaseException { 472 473 open(); 474 475 PrimaryIndex<Integer,SharedSequenceEntity1> priIndex1 = 476 store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class); 477 478 PrimaryIndex<Integer,SharedSequenceEntity2> priIndex2 = 479 store.getPrimaryIndex(Integer.class, SharedSequenceEntity2.class); 480 481 Transaction txn = txnBegin(); 482 SharedSequenceEntity1 e1 = new SharedSequenceEntity1(); 483 SharedSequenceEntity2 e2 = new SharedSequenceEntity2(); 484 priIndex1.put(txn, e1); 485 assertEquals(1, e1.key); 486 priIndex2.putNoOverwrite(txn, e2); 487 assertEquals(Integer.valueOf(2), e2.key); 488 e1.key = 0; 489 priIndex1.putNoOverwrite(txn, e1); 490 assertEquals(3, e1.key); 491 e2.key = null; 492 priIndex2.put(txn, e2); 493 assertEquals(Integer.valueOf(4), e2.key); 494 txnCommit(txn); 495 496 close(); 497 } 498 499 @Entity 500 static class SharedSequenceEntity1 { 501 502 @PrimaryKey(sequence="shared") 503 private int key; 504 } 505 506 @Entity 507 static class SharedSequenceEntity2 { 508 509 @PrimaryKey(sequence="shared") 510 private Integer key; 511 } 512 513 public void testSeparateSequence() 514 throws DatabaseException { 515 516 open(); 517 518 PrimaryIndex<Integer,SeparateSequenceEntity1> priIndex1 = 519 store.getPrimaryIndex 520 (Integer.class, SeparateSequenceEntity1.class); 521 522 PrimaryIndex<Integer,SeparateSequenceEntity2> priIndex2 = 523 store.getPrimaryIndex 524 (Integer.class, SeparateSequenceEntity2.class); 525 526 Transaction txn = txnBegin(); 527 SeparateSequenceEntity1 e1 = new SeparateSequenceEntity1(); 528 SeparateSequenceEntity2 e2 = new SeparateSequenceEntity2(); 529 priIndex1.put(txn, e1); 530 assertEquals(1, e1.key); 531 priIndex2.putNoOverwrite(txn, e2); 532 assertEquals(Integer.valueOf(1), e2.key); 533 e1.key = 0; 534 priIndex1.putNoOverwrite(txn, e1); 535 assertEquals(2, e1.key); 536 e2.key = null; 537 priIndex2.put(txn, e2); 538 assertEquals(Integer.valueOf(2), e2.key); 539 txnCommit(txn); 540 541 close(); 542 } 543 544 @Entity 545 static class SeparateSequenceEntity1 { 546 547 @PrimaryKey(sequence="seq1") 548 private int key; 549 } 550 551 @Entity 552 static class SeparateSequenceEntity2 { 553 554 @PrimaryKey(sequence="seq2") 555 private Integer key; 556 } 557 558 public void testCompositeSequence() 559 throws DatabaseException { 560 561 open(); 562 563 PrimaryIndex<CompositeSequenceEntity1.Key,CompositeSequenceEntity1> 564 priIndex1 = 565 store.getPrimaryIndex 566 (CompositeSequenceEntity1.Key.class, 567 CompositeSequenceEntity1.class); 568 569 PrimaryIndex<CompositeSequenceEntity2.Key,CompositeSequenceEntity2> 570 priIndex2 = 571 store.getPrimaryIndex 572 (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.entities(); 591 e1 = c1.next(); 592 assertEquals(2, e1.key.key); 593 e1 = c1.next(); 594 assertEquals(1, e1.key.key); 595 e1 = c1.next(); 596 assertNull(e1); 597 c1.close(); 598 599 EntityCursor<CompositeSequenceEntity2> c2 = priIndex2.entities(); 600 e2 = c2.next(); 601 assertEquals(Integer.valueOf(2), e2.key.key); 602 e2 = c2.next(); 603 assertEquals(Integer.valueOf(1), e2.key.key); 604 e2 = c2.next(); 605 assertNull(e2); 606 c2.close(); 607 608 close(); 609 } 610 611 @Entity 612 static class CompositeSequenceEntity1 { 613 614 @Persistent 615 static class Key implements Comparable<Key> { 616 617 @KeyField(1) 618 private int key; 619 620 public int compareTo(Key o) { 621 /* Reverse the natural order. */ 622 return o.key - key; 623 } 624 } 625 626 @PrimaryKey(sequence="seq1") 627 private Key key; 628 } 629 630 @Entity 631 static class CompositeSequenceEntity2 { 632 633 @Persistent 634 static class Key implements Comparable<Key> { 635 636 @KeyField(1) 637 private Integer key; 638 639 public int compareTo(Key o) { 640 /* Reverse the natural order. */ 641 return o.key - key; 642 } 643 } 644 645 @PrimaryKey(sequence="seq2") 646 private Key key; 647 } 648 649 /** 650 * When opening read-only, secondaries are not opened when the primary is 651 * opened, causing a different code path to be used for opening 652 * secondaries. For a RawStore in particular, this caused an unreported 653 * NullPointerException in JE 3.0.12. No SR was created because the use 654 * case is very obscure and was discovered by code inspection. 655 */ 656 public void testOpenRawStoreReadOnly() 657 throws DatabaseException { 658 659 open(); 660 store.getPrimaryIndex(Integer.class, MyEntity.class); 661 close(); 662 663 StoreConfig config = new StoreConfig(); 664 config.setReadOnly(true); 665 config.setTransactional(envConfig.getTransactional()); 666 RawStore rawStore = new RawStore(env, "test", config); 667 668 String clsName = MyEntity.class.getName(); 669 rawStore.getSecondaryIndex(clsName, "secKey"); 670 671 rawStore.close(); 672 } 673 674 /** 675 * When opening an X_TO_MANY secondary that has a persistent key class, the 676 * key class was not recognized as being persistent if it was never before 677 * referenced when getSecondaryIndex was called. This was a bug in JE 678 * 3.0.12, reported on OTN. [#15103] 679 */ 680 public void testToManyKeyClass() 681 throws DatabaseException { 682 683 open(); 684 685 PrimaryIndex<Integer,ToManyKeyEntity> priIndex = 686 store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class); 687 SecondaryIndex<ToManyKey,Integer,ToManyKeyEntity> secIndex = 688 store.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() 702 throws DatabaseException { 703 704 open(); 705 PrimaryIndex<Integer,ToManyKeyEntity> priIndex = 706 store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class); 707 priIndex.put(new ToManyKeyEntity()); 708 close(); 709 710 openReadOnly(); 711 priIndex = store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class); 712 SecondaryIndex<ToManyKey,Integer,ToManyKeyEntity> secIndex = 713 store.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 741 /** 742 * When Y is opened and X has a key with relatedEntity=Y.class, X should 743 * be opened automatically. If X is not opened, foreign key constraints 744 * will not be enforced. [#15358] 745 */ 746 public void testAutoOpenRelatedEntity() 747 throws DatabaseException { 748 749 PrimaryIndex<Integer,RelatedY> priY; 750 PrimaryIndex<Integer,RelatedX> priX; 751 752 /* Opening X should create (and open) Y and enforce constraints. */ 753 open(); 754 priX = store.getPrimaryIndex(Integer.class, RelatedX.class); 755 PersistTestUtils.assertDbExists 756 (true, env, STORE_NAME, RelatedY.class.getName(), null); 757 try { 758 priX.put(new RelatedX()); 759 fail(); 760 } catch (DatabaseException e) { 761 assertTrue 762 ("" + e.getMessage(), (e.getMessage().indexOf 763 ("foreign key not allowed: it is not present") >= 0) || 764 (e.getMessage().indexOf("DB_FOREIGN_CONFLICT") >= 0)); 765 } 766 priY = store.getPrimaryIndex(Integer.class, RelatedY.class); 767 priY.put(new RelatedY()); 768 priX.put(new RelatedX()); 769 close(); 770 771 /* Delete should cascade even when X is not opened explicitly. */ 772 open(); 773 priY = store.getPrimaryIndex(Integer.class, RelatedY.class); 774 assertEquals(1, priY.count()); 775 priY.delete(88); 776 assertEquals(0, priY.count()); 777 priX = store.getPrimaryIndex(Integer.class, RelatedX.class); 778 assertEquals(0, priX.count()); /* Failed prior to [#15358] fix. */ 779 close(); 780 } 781 782 @Entity 783 static class RelatedX { 784 785 @PrimaryKey 786 int key = 99; 787 788 @SecondaryKey(relate=ONE_TO_ONE, 789 relatedEntity=RelatedY.class, 790 onRelatedEntityDelete=CASCADE) 791 int key2 = 88; 792 793 RelatedX() { 794 } 795 } 796 797 @Entity 798 static class RelatedY { 799 800 @PrimaryKey 801 int key = 88; 802 803 RelatedY() { 804 } 805 } 806 807 public void testSecondaryBulkLoad1() 808 throws DatabaseException { 809 810 doSecondaryBulkLoad(true); 811 } 812 813 public void testSecondaryBulkLoad2() 814 throws DatabaseException { 815 816 doSecondaryBulkLoad(false); 817 } 818 819 private void doSecondaryBulkLoad(boolean closeAndOpenNormally) 820 throws DatabaseException { 821 822 PrimaryIndex<Integer,RelatedX> priX; 823 PrimaryIndex<Integer,RelatedY> priY; 824 SecondaryIndex<Integer,Integer,RelatedX> secX; 825 826 /* Open priX with SecondaryBulkLoad=true. */ 827 StoreConfig config = new StoreConfig(); 828 config.setAllowCreate(true); 829 config.setSecondaryBulkLoad(true); 830 open(config); 831 832 /* Getting priX should not create the secondary index. */ 833 priX = store.getPrimaryIndex(Integer.class, RelatedX.class); 834 PersistTestUtils.assertDbExists 835 (false, env, STORE_NAME, RelatedX.class.getName(), "key2"); 836 837 /* We can put records that violate the secondary key constraint. */ 838 priX.put(new RelatedX()); 839 840 if (closeAndOpenNormally) { 841 /* Open normally and the secondary will be populated. */ 842 close(); 843 open(); 844 try { 845 /* Before adding the foreign key, constraint is violated. */ 846 priX = store.getPrimaryIndex(Integer.class, RelatedX.class); 847 } catch (DatabaseException e) { 848 assertTrue(e.toString(), 849 e.toString().contains("foreign key not allowed")); 850 } 851 /* Add the foreign key to avoid the constraint error. */ 852 priY = store.getPrimaryIndex(Integer.class, RelatedY.class); 853 priY.put(new RelatedY()); 854 priX = store.getPrimaryIndex(Integer.class, RelatedX.class); 855 PersistTestUtils.assertDbExists 856 (true, env, STORE_NAME, RelatedX.class.getName(), "key2"); 857 secX = store.getSecondaryIndex(priX, Integer.class, "key2"); 858 } else { 859 /* Get secondary index explicitly and it will be populated. */ 860 try { 861 /* Before adding the foreign key, constraint is violated. */ 862 secX = store.getSecondaryIndex(priX, Integer.class, "key2"); 863 } catch (DatabaseException e) { 864 assertTrue(e.toString(), 865 e.toString().contains("foreign key not allowed")); 866 } 867 /* Add the foreign key. */ 868 priY = store.getPrimaryIndex(Integer.class, RelatedY.class); 869 priY.put(new RelatedY()); 870 secX = store.getSecondaryIndex(priX, Integer.class, "key2"); 871 PersistTestUtils.assertDbExists 872 (true, env, STORE_NAME, RelatedX.class.getName(), "key2"); 873 } 874 875 RelatedX x = secX.get(88); 876 assertNotNull(x); 877 close(); 878 } 879} 880