~ubuntu-branches/ubuntu/saucy/db/saucy-proposed

« back to all changes in this revision

Viewing changes to test/java/compat/src/com/sleepycat/persist/test/OperationTest.java

  • Committer: Bazaar Package Importer
  • Author(s): Colin Watson
  • Date: 2010-11-05 15:02:09 UTC
  • mfrom: (13.1.12 sid)
  • Revision ID: james.westby@ubuntu.com-20101105150209-ppvyn0619pu014xo
Tags: 5.1.19-1ubuntu1
* Resynchronise with Debian.  Remaining changes:
  - Pass --build/--host to configure to support cross-building, and don't
    override CC.
  - Disable the Java build when cross-building, for now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*-
 
2
 * See the file LICENSE for redistribution information.
 
3
 *
 
4
 * Copyright (c) 2002, 2010 Oracle and/or its affiliates.  All rights reserved.
 
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
 
 
17
import java.util.ArrayList;
 
18
import java.util.EnumSet;
 
19
import java.util.HashSet;
 
20
import java.util.List;
 
21
import java.util.Set;
 
22
 
 
23
import junit.framework.Test;
 
24
 
 
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;
 
47
 
 
48
/**
 
49
 * Tests misc store and index operations that are not tested by IndexTest.
 
50
 *
 
51
 * @author Mark Hayes
 
52
 */
 
53
public class OperationTest extends TxnTestCase {
 
54
 
 
55
    private static final String STORE_NAME = "test";
 
56
 
 
57
    public static Test suite() {
 
58
        testClass = OperationTest.class;
 
59
        return txnTestSuite(null, null);
 
60
    }
 
61
 
 
62
    private EntityStore store;
 
63
 
 
64
    private void openReadOnly()
 
65
        throws DatabaseException {
 
66
 
 
67
        StoreConfig config = new StoreConfig();
 
68
        config.setReadOnly(true);
 
69
        open(config);
 
70
    }
 
71
 
 
72
    private void open()
 
73
        throws DatabaseException {
 
74
 
 
75
        open((Class) null);
 
76
    }
 
77
 
 
78
    private void open(Class clsToRegister)
 
79
        throws DatabaseException {
 
80
 
 
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);
 
88
        }
 
89
        open(config);
 
90
    }
 
91
 
 
92
    private void open(StoreConfig config)
 
93
        throws DatabaseException {
 
94
 
 
95
        config.setTransactional(envConfig.getTransactional());
 
96
        store = new EntityStore(env, STORE_NAME, config);
 
97
    }
 
98
 
 
99
    private void close()
 
100
        throws DatabaseException {
 
101
 
 
102
        store.close();
 
103
        store = null;
 
104
    }
 
105
 
 
106
    @Override
 
107
    public void setUp()
 
108
        throws Exception {
 
109
 
 
110
        super.setUp();
 
111
    }
 
112
 
 
113
    /**
 
114
     * The store must be closed before closing the environment.
 
115
     */
 
116
    @Override
 
117
    public void tearDown()
 
118
        throws Exception {
 
119
 
 
120
        try {
 
121
            if (store != null) {
 
122
                store.close();
 
123
            }
 
124
        } catch (Throwable e) {
 
125
            System.out.println("During tearDown: " + e);
 
126
        }
 
127
        store = null;
 
128
        super.tearDown();
 
129
    }
 
130
 
 
131
    public void testReadOnly()
 
132
        throws DatabaseException {
 
133
 
 
134
        open();
 
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);
 
141
        txnCommit(txn);
 
142
        close();
 
143
 
 
144
        /*
 
145
         * Check that we can open the store read-only and read the records
 
146
         * written above.
 
147
         */
 
148
        openReadOnly();
 
149
        priIndex =
 
150
            store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
 
151
        e = priIndex.get(1);
 
152
        assertNotNull(e);
 
153
        close();
 
154
    }
 
155
 
 
156
 
 
157
 
 
158
    public void testUninitializedCursor()
 
159
        throws DatabaseException {
 
160
 
 
161
        open();
 
162
 
 
163
        PrimaryIndex<Integer, MyEntity> priIndex =
 
164
            store.getPrimaryIndex(Integer.class, MyEntity.class);
 
165
 
 
166
        Transaction txn = txnBeginCursor();
 
167
 
 
168
        MyEntity e = new MyEntity();
 
169
        e.priKey = 1;
 
170
        e.secKey = 1;
 
171
        priIndex.put(txn, e);
 
172
 
 
173
        EntityCursor<MyEntity> entities = 
 
174
            priIndex.entities(txn, getWriteCursorConfig());
 
175
        try {
 
176
            entities.nextDup();
 
177
            fail();
 
178
        } catch (IllegalStateException expected) {}
 
179
        try {
 
180
            entities.prevDup();
 
181
            fail();
 
182
        } catch (IllegalStateException expected) {}
 
183
        try {
 
184
            entities.current();
 
185
            fail();
 
186
        } catch (IllegalStateException expected) {}
 
187
        try {
 
188
            entities.delete();
 
189
            fail();
 
190
        } catch (IllegalStateException expected) {}
 
191
        try {
 
192
            entities.update(e);
 
193
            fail();
 
194
        } catch (IllegalStateException expected) {}
 
195
        try {
 
196
            entities.count();
 
197
            fail();
 
198
        } catch (IllegalStateException expected) {}
 
199
 
 
200
        entities.close();
 
201
        txnCommit(txn);
 
202
        close();
 
203
    }
 
204
 
 
205
    public void testCursorCount()
 
206
        throws DatabaseException {
 
207
 
 
208
        open();
 
209
 
 
210
        PrimaryIndex<Integer, MyEntity> priIndex =
 
211
            store.getPrimaryIndex(Integer.class, MyEntity.class);
 
212
 
 
213
        SecondaryIndex<Integer, Integer, MyEntity> secIndex =
 
214
            store.getSecondaryIndex(priIndex, Integer.class, "secKey");
 
215
 
 
216
        Transaction txn = txnBeginCursor();
 
217
 
 
218
        MyEntity e = new MyEntity();
 
219
        e.priKey = 1;
 
220
        e.secKey = 1;
 
221
        priIndex.put(txn, e);
 
222
 
 
223
        EntityCursor<MyEntity> cursor = secIndex.entities(txn, null);
 
224
        cursor.next();
 
225
        assertEquals(1, cursor.count());
 
226
        cursor.close();
 
227
 
 
228
        e.priKey = 2;
 
229
        priIndex.put(txn, e);
 
230
        cursor = secIndex.entities(txn, null);
 
231
        cursor.next();
 
232
        assertEquals(2, cursor.count());
 
233
        cursor.close();
 
234
 
 
235
        txnCommit(txn);
 
236
        close();
 
237
    }
 
238
 
 
239
    public void testCursorUpdate()
 
240
        throws DatabaseException {
 
241
 
 
242
        open();
 
243
 
 
244
        PrimaryIndex<Integer, MyEntity> priIndex =
 
245
            store.getPrimaryIndex(Integer.class, MyEntity.class);
 
246
 
 
247
        SecondaryIndex<Integer, Integer, MyEntity> secIndex =
 
248
            store.getSecondaryIndex(priIndex, Integer.class, "secKey");
 
249
 
 
250
        Transaction txn = txnBeginCursor();
 
251
 
 
252
        Integer k;
 
253
        MyEntity e = new MyEntity();
 
254
        e.priKey = 1;
 
255
        e.secKey = 2;
 
256
        priIndex.put(txn, e);
 
257
 
 
258
        /* update() with primary entity cursor. */
 
259
        EntityCursor<MyEntity> entities = 
 
260
            priIndex.entities(txn, getWriteCursorConfig());
 
261
        e = entities.next();
 
262
        assertNotNull(e);
 
263
        assertEquals(1, e.priKey);
 
264
        assertEquals(Integer.valueOf(2), e.secKey);
 
265
        e.secKey = null;
 
266
        assertTrue(entities.update(e));
 
267
        e = entities.current();
 
268
        assertNotNull(e);
 
269
        assertEquals(1, e.priKey);
 
270
        assertEquals(null, e.secKey);
 
271
        e.secKey = 3;
 
272
        assertTrue(entities.update(e));
 
273
        e = entities.current();
 
274
        assertNotNull(e);
 
275
        assertEquals(1, e.priKey);
 
276
        assertEquals(Integer.valueOf(3), e.secKey);
 
277
        entities.close();
 
278
 
 
279
        /* update() with primary keys cursor. */
 
280
        EntityCursor<Integer> keys = priIndex.keys(txn, 
 
281
                                                   getWriteCursorConfig());
 
282
        k = keys.next();
 
283
        assertNotNull(k);
 
284
        assertEquals(Integer.valueOf(1), k);
 
285
        try {
 
286
            keys.update(2);
 
287
            fail();
 
288
        } catch (UnsupportedOperationException expected) {
 
289
        }
 
290
        keys.close();
 
291
 
 
292
        /* update() with secondary entity cursor. */
 
293
        entities = secIndex.entities(txn, null);
 
294
        e = entities.next();
 
295
        assertNotNull(e);
 
296
        assertEquals(1, e.priKey);
 
297
        assertEquals(Integer.valueOf(3), e.secKey);
 
298
        try {
 
299
            entities.update(e);
 
300
            fail();
 
301
        } catch (UnsupportedOperationException expected) {
 
302
        } catch (IllegalArgumentException expectedForDbCore) {
 
303
        }
 
304
        entities.close();
 
305
 
 
306
        /* update() with secondary keys cursor. */
 
307
        keys = secIndex.keys(txn, null);
 
308
        k = keys.next();
 
309
        assertNotNull(k);
 
310
        assertEquals(Integer.valueOf(3), k);
 
311
        try {
 
312
            keys.update(k);
 
313
            fail();
 
314
        } catch (UnsupportedOperationException expected) {
 
315
        }
 
316
        keys.close();
 
317
 
 
318
        txnCommit(txn);
 
319
        close();
 
320
    }
 
321
 
 
322
    public void testCursorDelete()
 
323
        throws DatabaseException {
 
324
 
 
325
        open();
 
326
 
 
327
        PrimaryIndex<Integer, MyEntity> priIndex =
 
328
            store.getPrimaryIndex(Integer.class, MyEntity.class);
 
329
 
 
330
        SecondaryIndex<Integer, Integer, MyEntity> secIndex =
 
331
            store.getSecondaryIndex(priIndex, Integer.class, "secKey");
 
332
 
 
333
        Transaction txn = txnBeginCursor();
 
334
 
 
335
        /* delete() with primary and secondary entities cursor. */
 
336
 
 
337
        for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
 
338
 
 
339
            MyEntity e = new MyEntity();
 
340
            e.priKey = 1;
 
341
            e.secKey = 1;
 
342
            priIndex.put(txn, e);
 
343
            e.priKey = 2;
 
344
            priIndex.put(txn, e);
 
345
 
 
346
            EntityCursor<MyEntity> cursor = 
 
347
                index.entities(txn, getWriteCursorConfig());
 
348
 
 
349
            e = cursor.next();
 
350
            assertNotNull(e);
 
351
            assertEquals(1, e.priKey);
 
352
            e = cursor.current();
 
353
            assertNotNull(e);
 
354
            assertEquals(1, e.priKey);
 
355
            assertTrue(cursor.delete());
 
356
            assertTrue(!cursor.delete());
 
357
            assertNull(cursor.current());
 
358
 
 
359
            e = cursor.next();
 
360
            assertNotNull(e);
 
361
            assertEquals(2, e.priKey);
 
362
            e = cursor.current();
 
363
            assertNotNull(e);
 
364
            assertEquals(2, e.priKey);
 
365
            assertTrue(cursor.delete());
 
366
            assertTrue(!cursor.delete());
 
367
            assertNull(cursor.current());
 
368
 
 
369
            e = cursor.next();
 
370
            assertNull(e);
 
371
 
 
372
            if (index == priIndex) {
 
373
                e = new MyEntity();
 
374
                e.priKey = 2;
 
375
                e.secKey = 1;
 
376
                assertTrue(!cursor.update(e));
 
377
            }
 
378
 
 
379
            cursor.close();
 
380
        }
 
381
 
 
382
        /* delete() with primary and secondary keys cursor. */
 
383
 
 
384
        for (EntityIndex index : new EntityIndex[] { priIndex, secIndex }) {
 
385
 
 
386
            MyEntity e = new MyEntity();
 
387
            e.priKey = 1;
 
388
            e.secKey = 1;
 
389
            priIndex.put(txn, e);
 
390
            e.priKey = 2;
 
391
            priIndex.put(txn, e);
 
392
 
 
393
            EntityCursor<Integer> cursor = index.keys(txn, 
 
394
                                                      getWriteCursorConfig());
 
395
 
 
396
            Integer k = cursor.next();
 
397
            assertNotNull(k);
 
398
            assertEquals(1, k.intValue());
 
399
            k = cursor.current();
 
400
            assertNotNull(k);
 
401
            assertEquals(1, k.intValue());
 
402
            assertTrue(cursor.delete());
 
403
            assertTrue(!cursor.delete());
 
404
            assertNull(cursor.current());
 
405
 
 
406
            int expectKey = (index == priIndex) ? 2 : 1;
 
407
            k = cursor.next();
 
408
            assertNotNull(k);
 
409
            assertEquals(expectKey, k.intValue());
 
410
            k = cursor.current();
 
411
            assertNotNull(k);
 
412
            assertEquals(expectKey, k.intValue());
 
413
            assertTrue(cursor.delete());
 
414
            assertTrue(!cursor.delete());
 
415
            assertNull(cursor.current());
 
416
 
 
417
            k = cursor.next();
 
418
            assertNull(k);
 
419
 
 
420
            cursor.close();
 
421
        }
 
422
 
 
423
        txnCommit(txn);
 
424
        close();
 
425
    }
 
426
 
 
427
    public void testDeleteFromSubIndex()
 
428
        throws DatabaseException {
 
429
 
 
430
        open();
 
431
 
 
432
        PrimaryIndex<Integer, MyEntity> priIndex =
 
433
            store.getPrimaryIndex(Integer.class, MyEntity.class);
 
434
 
 
435
        SecondaryIndex<Integer, Integer, MyEntity> secIndex =
 
436
            store.getSecondaryIndex(priIndex, Integer.class, "secKey");
 
437
 
 
438
        Transaction txn = txnBegin();
 
439
        MyEntity e = new MyEntity();
 
440
        e.secKey = 1;
 
441
        e.priKey = 1;
 
442
        priIndex.put(txn, e);
 
443
        e.priKey = 2;
 
444
        priIndex.put(txn, e);
 
445
        e.priKey = 3;
 
446
        priIndex.put(txn, e);
 
447
        e.priKey = 4;
 
448
        priIndex.put(txn, e);
 
449
        txnCommit(txn);
 
450
 
 
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);
 
463
        assertNull(e);
 
464
 
 
465
        boolean deleted = subIndex.delete(txn, 1);
 
466
        assertTrue(deleted);
 
467
        assertNull(subIndex.get(txn, 1, null));
 
468
        assertNotNull(subIndex.get(txn, 2, null));
 
469
 
 
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) {
 
475
                cursor.delete();
 
476
            }
 
477
            if (e2.priKey == 4) {
 
478
                saw4 = true;
 
479
            }
 
480
        }
 
481
        cursor.close();
 
482
        assertTrue(saw4);
 
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));
 
487
 
 
488
        txnCommit(txn);
 
489
        close();
 
490
    }
 
491
 
 
492
    @Entity
 
493
    static class MyEntity {
 
494
 
 
495
        @PrimaryKey
 
496
        private int priKey;
 
497
 
 
498
        @SecondaryKey(relate=MANY_TO_ONE)
 
499
        private Integer secKey;
 
500
 
 
501
        private MyEntity() {}
 
502
    }
 
503
 
 
504
    public void testSharedSequence()
 
505
        throws DatabaseException {
 
506
 
 
507
        open();
 
508
 
 
509
        PrimaryIndex<Integer, SharedSequenceEntity1> priIndex1 =
 
510
            store.getPrimaryIndex(Integer.class, SharedSequenceEntity1.class);
 
511
 
 
512
        PrimaryIndex<Integer, SharedSequenceEntity2> priIndex2 =
 
513
            store.getPrimaryIndex(Integer.class, SharedSequenceEntity2.class);
 
514
 
 
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);
 
522
        e1.key = 0;
 
523
        priIndex1.putNoOverwrite(txn, e1);
 
524
        assertEquals(3, e1.key);
 
525
        e2.key = null;
 
526
        priIndex2.put(txn, e2);
 
527
        assertEquals(Integer.valueOf(4), e2.key);
 
528
        txnCommit(txn);
 
529
 
 
530
        close();
 
531
    }
 
532
 
 
533
    @Entity
 
534
    static class SharedSequenceEntity1 {
 
535
 
 
536
        @PrimaryKey(sequence="shared")
 
537
        private int key;
 
538
    }
 
539
 
 
540
    @Entity
 
541
    static class SharedSequenceEntity2 {
 
542
 
 
543
        @PrimaryKey(sequence="shared")
 
544
        private Integer key;
 
545
    }
 
546
 
 
547
    public void testSeparateSequence()
 
548
        throws DatabaseException {
 
549
 
 
550
        open();
 
551
 
 
552
        PrimaryIndex<Integer, SeparateSequenceEntity1> priIndex1 =
 
553
            store.getPrimaryIndex
 
554
                (Integer.class, SeparateSequenceEntity1.class);
 
555
 
 
556
        PrimaryIndex<Integer, SeparateSequenceEntity2> priIndex2 =
 
557
            store.getPrimaryIndex
 
558
                (Integer.class, SeparateSequenceEntity2.class);
 
559
 
 
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);
 
567
        e1.key = 0;
 
568
        priIndex1.putNoOverwrite(txn, e1);
 
569
        assertEquals(2, e1.key);
 
570
        e2.key = null;
 
571
        priIndex2.put(txn, e2);
 
572
        assertEquals(Integer.valueOf(2), e2.key);
 
573
        txnCommit(txn);
 
574
 
 
575
        close();
 
576
    }
 
577
 
 
578
    @Entity
 
579
    static class SeparateSequenceEntity1 {
 
580
 
 
581
        @PrimaryKey(sequence="seq1")
 
582
        private int key;
 
583
    }
 
584
 
 
585
    @Entity
 
586
    static class SeparateSequenceEntity2 {
 
587
 
 
588
        @PrimaryKey(sequence="seq2")
 
589
        private Integer key;
 
590
    }
 
591
 
 
592
    public void testCompositeSequence()
 
593
        throws DatabaseException {
 
594
 
 
595
        open();
 
596
 
 
597
        PrimaryIndex<CompositeSequenceEntity1.Key, CompositeSequenceEntity1>
 
598
            priIndex1 =
 
599
            store.getPrimaryIndex
 
600
                (CompositeSequenceEntity1.Key.class,
 
601
                 CompositeSequenceEntity1.class);
 
602
 
 
603
        PrimaryIndex<CompositeSequenceEntity2.Key, CompositeSequenceEntity2>
 
604
            priIndex2 =
 
605
            store.getPrimaryIndex
 
606
                (CompositeSequenceEntity2.Key.class,
 
607
                 CompositeSequenceEntity2.class);
 
608
 
 
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);
 
616
        e1.key = null;
 
617
        priIndex1.putNoOverwrite(txn, e1);
 
618
        assertEquals(2, e1.key.key);
 
619
        e2.key = null;
 
620
        priIndex2.put(txn, e2);
 
621
        assertEquals(Integer.valueOf(2), e2.key.key);
 
622
        txnCommit(txn);
 
623
 
 
624
        txn = txnBeginCursor();
 
625
        EntityCursor<CompositeSequenceEntity1> c1 =
 
626
            priIndex1.entities(txn, null);
 
627
        e1 = c1.next();
 
628
        assertEquals(2, e1.key.key);
 
629
        e1 = c1.next();
 
630
        assertEquals(1, e1.key.key);
 
631
        e1 = c1.next();
 
632
        assertNull(e1);
 
633
        c1.close();
 
634
        txnCommit(txn);
 
635
 
 
636
        txn = txnBeginCursor();
 
637
        EntityCursor<CompositeSequenceEntity2> c2 =
 
638
            priIndex2.entities(txn, null);
 
639
        e2 = c2.next();
 
640
        assertEquals(Integer.valueOf(2), e2.key.key);
 
641
        e2 = c2.next();
 
642
        assertEquals(Integer.valueOf(1), e2.key.key);
 
643
        e2 = c2.next();
 
644
        assertNull(e2);
 
645
        c2.close();
 
646
        txnCommit(txn);
 
647
 
 
648
        close();
 
649
    }
 
650
 
 
651
    @Entity
 
652
    static class CompositeSequenceEntity1 {
 
653
 
 
654
        @Persistent
 
655
        static class Key implements Comparable<Key> {
 
656
 
 
657
            @KeyField(1)
 
658
            private int key;
 
659
 
 
660
            public int compareTo(Key o) {
 
661
                /* Reverse the natural order. */
 
662
                return o.key - key;
 
663
            }
 
664
        }
 
665
 
 
666
        @PrimaryKey(sequence="seq1")
 
667
        private Key key;
 
668
    }
 
669
 
 
670
    /**
 
671
     * Same as CompositeSequenceEntity1 but using Integer rather than int for
 
672
     * the key type.
 
673
     */
 
674
    @Entity
 
675
    static class CompositeSequenceEntity2 {
 
676
 
 
677
        @Persistent
 
678
        static class Key implements Comparable<Key> {
 
679
 
 
680
            @KeyField(1)
 
681
            private Integer key;
 
682
 
 
683
            public int compareTo(Key o) {
 
684
                /* Reverse the natural order. */
 
685
                return o.key - key;
 
686
            }
 
687
        }
 
688
 
 
689
        @PrimaryKey(sequence="seq2")
 
690
        private Key key;
 
691
    }
 
692
 
 
693
    /**
 
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.
 
699
     */
 
700
    public void testOpenRawStoreReadOnly()
 
701
        throws DatabaseException {
 
702
 
 
703
        open();
 
704
        store.getPrimaryIndex(Integer.class, MyEntity.class);
 
705
        close();
 
706
 
 
707
        StoreConfig config = new StoreConfig();
 
708
        config.setReadOnly(true);
 
709
        config.setTransactional(envConfig.getTransactional());
 
710
        RawStore rawStore = new RawStore(env, "test", config);
 
711
 
 
712
        String clsName = MyEntity.class.getName();
 
713
        rawStore.getSecondaryIndex(clsName, "secKey");
 
714
 
 
715
        rawStore.close();
 
716
    }
 
717
 
 
718
    /**
 
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]
 
723
     */
 
724
    public void testToManyKeyClass()
 
725
        throws DatabaseException {
 
726
 
 
727
        open();
 
728
 
 
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");
 
733
 
 
734
        priIndex.put(new ToManyKeyEntity());
 
735
        secIndex.get(new ToManyKey());
 
736
 
 
737
        close();
 
738
    }
 
739
 
 
740
    /**
 
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]
 
744
     */
 
745
    public void testToManyReadOnly()
 
746
        throws DatabaseException {
 
747
 
 
748
        open();
 
749
        PrimaryIndex<Integer, ToManyKeyEntity> priIndex =
 
750
            store.getPrimaryIndex(Integer.class, ToManyKeyEntity.class);
 
751
        priIndex.put(new ToManyKeyEntity());
 
752
        close();
 
753
 
 
754
        openReadOnly();
 
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());
 
759
        close();
 
760
    }
 
761
 
 
762
    @Persistent
 
763
    static class ToManyKey {
 
764
 
 
765
        @KeyField(1)
 
766
        int value = 99;
 
767
    }
 
768
 
 
769
    @Entity
 
770
    static class ToManyKeyEntity {
 
771
 
 
772
        @PrimaryKey
 
773
        int key = 88;
 
774
 
 
775
        @SecondaryKey(relate=ONE_TO_MANY)
 
776
        Set<ToManyKey> key2;
 
777
 
 
778
        ToManyKeyEntity() {
 
779
            key2 = new HashSet<ToManyKey>();
 
780
            key2.add(new ToManyKey());
 
781
        }
 
782
    }
 
783
 
 
784
 
 
785
 
 
786
    /**
 
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]
 
790
     */
 
791
    public void testAutoOpenRelatedEntity()
 
792
        throws DatabaseException {
 
793
 
 
794
        PrimaryIndex<Integer, RelatedY> priY;
 
795
        PrimaryIndex<Integer, RelatedX> priX;
 
796
 
 
797
        /* Opening X should create (and open) Y and enforce constraints. */
 
798
        open();
 
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. */
 
804
            try {
 
805
                priX.put(new RelatedX());
 
806
                fail();
 
807
            } catch (DatabaseException e) {
 
808
                assertTrue
 
809
                    ("" + e.getMessage(), (e.getMessage().indexOf
 
810
                      ("foreign key not allowed: it is not present") >= 0) ||
 
811
                     (e.getMessage().indexOf("DB_FOREIGN_CONFLICT") >= 0));
 
812
            }
 
813
        }
 
814
        priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
 
815
        priY.put(new RelatedY());
 
816
        priX.put(new RelatedX());
 
817
        close();
 
818
 
 
819
        /* Delete should cascade even when X is not opened explicitly. */
 
820
        open();
 
821
        priY = store.getPrimaryIndex(Integer.class, RelatedY.class);
 
822
        assertEquals(1, priY.count());
 
823
        priY.delete(88);
 
824
        assertEquals(0, priY.count());
 
825
        priX = store.getPrimaryIndex(Integer.class, RelatedX.class);
 
826
        assertEquals(0, priX.count()); /* Failed prior to [#15358] fix. */
 
827
        close();
 
828
    }
 
829
 
 
830
    @Entity
 
831
    static class RelatedX {
 
832
 
 
833
        @PrimaryKey
 
834
        int key = 99;
 
835
 
 
836
        @SecondaryKey(relate=ONE_TO_ONE,
 
837
                      relatedEntity=RelatedY.class,
 
838
                      onRelatedEntityDelete=CASCADE)
 
839
        int key2 = 88;
 
840
 
 
841
        RelatedX() {
 
842
        }
 
843
    }
 
844
 
 
845
    @Entity
 
846
    static class RelatedY {
 
847
 
 
848
        @PrimaryKey
 
849
        int key = 88;
 
850
 
 
851
        RelatedY() {
 
852
        }
 
853
    }
 
854
 
 
855
    public void testSecondaryBulkLoad1()
 
856
        throws DatabaseException {
 
857
 
 
858
        doSecondaryBulkLoad(true);
 
859
    }
 
860
 
 
861
    public void testSecondaryBulkLoad2()
 
862
        throws DatabaseException {
 
863
 
 
864
        doSecondaryBulkLoad(false);
 
865
    }
 
866
 
 
867
    private void doSecondaryBulkLoad(boolean closeAndOpenNormally)
 
868
        throws DatabaseException {
 
869
 
 
870
        PrimaryIndex<Integer, RelatedX> priX;
 
871
        PrimaryIndex<Integer, RelatedY> priY;
 
872
        SecondaryIndex<Integer, Integer, RelatedX> secX;
 
873
 
 
874
        /* Open priX with SecondaryBulkLoad=true. */
 
875
        StoreConfig config = new StoreConfig();
 
876
        config.setAllowCreate(true);
 
877
        config.setSecondaryBulkLoad(true);
 
878
        open(config);
 
879
 
 
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");
 
884
 
 
885
        /* We can put records that violate the secondary key constraint. */
 
886
        priX.put(new RelatedX());
 
887
 
 
888
        if (closeAndOpenNormally) {
 
889
            /* Open normally and attempt to populate the secondary. */
 
890
            close();
 
891
            open();
 
892
            if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
 
893
                /* Constraint enforcement requires transactions. */
 
894
                try {
 
895
                    /* Before adding the foreign key, constraint is violated. */
 
896
                    priX = store.getPrimaryIndex(Integer.class,
 
897
                                                 RelatedX.class);
 
898
                    fail();
 
899
                } catch (DatabaseException e) {
 
900
                    assertTrue
 
901
                        (e.toString(),
 
902
                         e.toString().contains("foreign key not allowed"));
 
903
                }
 
904
            }
 
905
            /* Open priX with SecondaryBulkLoad=true. */
 
906
            close();
 
907
            open(config);
 
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. */
 
912
            close();
 
913
            open();
 
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");
 
918
        } else {
 
919
            /* Get secondary index explicitly and it will be populated. */
 
920
            if (isTransactional && DbCompat.POPULATE_ENFORCES_CONSTRAINTS) {
 
921
                /* Constraint enforcement requires transactions. */
 
922
                try {
 
923
                    /* Before adding the foreign key, constraint is violated. */
 
924
                    secX = store.getSecondaryIndex(priX, Integer.class,
 
925
                                                   "key2");
 
926
                    fail();
 
927
                } catch (DatabaseException e) {
 
928
                    assertTrue
 
929
                        (e.toString(),
 
930
                         e.toString().contains("foreign key not allowed"));
 
931
                }
 
932
            }
 
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");
 
939
        }
 
940
 
 
941
        RelatedX x = secX.get(88);
 
942
        assertNotNull(x);
 
943
        close();
 
944
    }
 
945
 
 
946
    public void testPersistentFields()
 
947
        throws DatabaseException {
 
948
 
 
949
        open();
 
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);
 
955
        assertNotNull(o2);
 
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);
 
962
        close();
 
963
    }
 
964
 
 
965
    @Entity
 
966
    static class PersistentFields {
 
967
 
 
968
        @PrimaryKey int key;
 
969
 
 
970
        transient int transient1;
 
971
        @NotPersistent int transient2;
 
972
        @NotPersistent transient int transient3;
 
973
 
 
974
        int persistent1;
 
975
        @NotTransient int persistent2;
 
976
        @NotTransient transient int persistent3;
 
977
 
 
978
        PersistentFields(int k,
 
979
                         int t1,
 
980
                         int t2,
 
981
                         int t3,
 
982
                         int p1,
 
983
                         int p2,
 
984
                         int p3) {
 
985
            key = k;
 
986
            transient1 = t1;
 
987
            transient2 = t2;
 
988
            transient3 = t3;
 
989
            persistent1 = p1;
 
990
            persistent2 = p2;
 
991
            persistent3 = p3;
 
992
        }
 
993
 
 
994
        private PersistentFields() {}
 
995
    }
 
996
 
 
997
    /**
 
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.
 
1003
     * [#16407]
 
1004
     */
 
1005
    public void testKeyClassInitialization()
 
1006
        throws DatabaseException {
 
1007
 
 
1008
        open();
 
1009
        store.getSecondaryConfig(ToManyKeyEntity.class, "key2");
 
1010
        close();
 
1011
    }
 
1012
 
 
1013
    public void testKeyName()
 
1014
        throws DatabaseException {
 
1015
 
 
1016
        open();
 
1017
 
 
1018
        PrimaryIndex<Long, BookEntity> pri1 =
 
1019
            store.getPrimaryIndex(Long.class, BookEntity.class);
 
1020
        PrimaryIndex<Long, AuthorEntity> pri2 =
 
1021
            store.getPrimaryIndex(Long.class, AuthorEntity.class);
 
1022
 
 
1023
        BookEntity book = new BookEntity();
 
1024
        pri1.put(book);
 
1025
        AuthorEntity author = new AuthorEntity();
 
1026
        author.bookIds.add(book.bookId);
 
1027
        pri2.put(author);
 
1028
 
 
1029
        close();
 
1030
 
 
1031
        open();
 
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);
 
1038
        close();
 
1039
    }
 
1040
 
 
1041
    @Entity
 
1042
    static class AuthorEntity {
 
1043
 
 
1044
        @PrimaryKey(sequence="authorSeq")
 
1045
        long authorId;
 
1046
 
 
1047
        @SecondaryKey(relate=MANY_TO_MANY, relatedEntity=BookEntity.class,
 
1048
                      name="bookId", onRelatedEntityDelete=NULLIFY)
 
1049
        Set<Long> bookIds = new HashSet<Long>();
 
1050
    }
 
1051
 
 
1052
    @Entity
 
1053
    static class BookEntity {
 
1054
 
 
1055
        @PrimaryKey(sequence="bookSeq")
 
1056
        long bookId;
 
1057
    }
 
1058
 
 
1059
    /**
 
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]
 
1063
     */
 
1064
    public void testPutEntitySubclassWithoutRegisterClass()
 
1065
        throws DatabaseException {
 
1066
 
 
1067
        open();
 
1068
 
 
1069
        final PrimaryIndex<Long, Statement> pri =
 
1070
            store.getPrimaryIndex(Long.class, Statement.class);
 
1071
 
 
1072
        final Transaction txn = txnBegin();
 
1073
        pri.put(txn, new Statement(1));
 
1074
        try {
 
1075
            pri.put(txn, new ExtendedStatement(2, null));
 
1076
            fail();
 
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()));
 
1083
        }
 
1084
        txnAbort(txn);
 
1085
 
 
1086
        close();
 
1087
    }
 
1088
 
 
1089
    /**
 
1090
     * Checks that registerClass avoids an exception when storing an entity
 
1091
     * subclass instance, which defines a secondary key. [#16399]
 
1092
     */
 
1093
    public void testPutEntitySubclassWithRegisterClass()
 
1094
        throws DatabaseException {
 
1095
 
 
1096
        open(ExtendedStatement.class);
 
1097
 
 
1098
        final PrimaryIndex<Long, Statement> pri =
 
1099
            store.getPrimaryIndex(Long.class, Statement.class);
 
1100
 
 
1101
        final Transaction txn = txnBegin();
 
1102
        pri.put(txn, new Statement(1));
 
1103
        pri.put(txn, new ExtendedStatement(2, "abc"));
 
1104
        txnCommit(txn);
 
1105
 
 
1106
        final SecondaryIndex<String, Long, ExtendedStatement> sec =
 
1107
            store.getSubclassIndex(pri, ExtendedStatement.class,
 
1108
                                   String.class, "name");
 
1109
 
 
1110
        ExtendedStatement o = sec.get("abc");
 
1111
        assertNotNull(o);
 
1112
        assertEquals(2, o.id);
 
1113
 
 
1114
        close();
 
1115
    }
 
1116
 
 
1117
    /**
 
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]
 
1123
     */
 
1124
    public void testPutEntitySubclassWithRegisterClass2()
 
1125
        throws DatabaseException {
 
1126
 
 
1127
        open(ExtendedStatement.class);
 
1128
 
 
1129
        PrimaryIndex<Long, Statement> pri =
 
1130
            store.getPrimaryIndex(Long.class, Statement.class);
 
1131
 
 
1132
        Transaction txn = txnBegin();
 
1133
        pri.put(txn, new Statement(1));
 
1134
        txnCommit(txn);
 
1135
 
 
1136
        close();
 
1137
        open();
 
1138
 
 
1139
        pri = store.getPrimaryIndex(Long.class, Statement.class);
 
1140
 
 
1141
        txn = txnBegin();
 
1142
        pri.put(txn, new ExtendedStatement(2, "abc"));
 
1143
        txnCommit(txn);
 
1144
 
 
1145
        final SecondaryIndex<String, Long, ExtendedStatement> sec =
 
1146
            store.getSubclassIndex(pri, ExtendedStatement.class,
 
1147
                                   String.class, "name");
 
1148
 
 
1149
        ExtendedStatement o = sec.get("abc");
 
1150
        assertNotNull(o);
 
1151
        assertEquals(2, o.id);
 
1152
 
 
1153
        close();
 
1154
    }
 
1155
 
 
1156
    /**
 
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]
 
1160
     */
 
1161
    public void testPutEntitySubclassWithGetSubclassIndex()
 
1162
        throws DatabaseException {
 
1163
 
 
1164
        open();
 
1165
 
 
1166
        final PrimaryIndex<Long, Statement> pri =
 
1167
            store.getPrimaryIndex(Long.class, Statement.class);
 
1168
 
 
1169
        final SecondaryIndex<String, Long, ExtendedStatement> sec =
 
1170
            store.getSubclassIndex(pri, ExtendedStatement.class,
 
1171
                                   String.class, "name");
 
1172
 
 
1173
        final Transaction txn = txnBegin();
 
1174
        pri.put(txn, new Statement(1));
 
1175
        pri.put(txn, new ExtendedStatement(2, "abc"));
 
1176
        txnCommit(txn);
 
1177
 
 
1178
        ExtendedStatement o = sec.get("abc");
 
1179
        assertNotNull(o);
 
1180
        assertEquals(2, o.id);
 
1181
 
 
1182
        close();
 
1183
    }
 
1184
 
 
1185
    /**
 
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]
 
1191
     */
 
1192
    public void testPutEntitySubclassWithGetSubclassIndex2()
 
1193
        throws DatabaseException {
 
1194
 
 
1195
        open();
 
1196
 
 
1197
        PrimaryIndex<Long, Statement> pri =
 
1198
            store.getPrimaryIndex(Long.class, Statement.class);
 
1199
 
 
1200
        SecondaryIndex<String, Long, ExtendedStatement> sec =
 
1201
            store.getSubclassIndex(pri, ExtendedStatement.class,
 
1202
                                   String.class, "name");
 
1203
 
 
1204
        Transaction txn = txnBegin();
 
1205
        pri.put(txn, new Statement(1));
 
1206
        txnCommit(txn);
 
1207
 
 
1208
        close();
 
1209
        open();
 
1210
 
 
1211
        pri = store.getPrimaryIndex(Long.class, Statement.class);
 
1212
 
 
1213
        txn = txnBegin();
 
1214
        pri.put(txn, new ExtendedStatement(2, "abc"));
 
1215
        txnCommit(txn);
 
1216
 
 
1217
        sec = store.getSubclassIndex(pri, ExtendedStatement.class,
 
1218
                                     String.class, "name");
 
1219
 
 
1220
        ExtendedStatement o = sec.get("abc");
 
1221
        assertNotNull(o);
 
1222
        assertEquals(2, o.id);
 
1223
 
 
1224
        close();
 
1225
    }
 
1226
 
 
1227
    /**
 
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]
 
1232
     */
 
1233
 
 
1234
    @Entity
 
1235
    static class Statement {
 
1236
        
 
1237
        @PrimaryKey
 
1238
        long id;
 
1239
 
 
1240
        Statement(long id) {
 
1241
            this.id = id;
 
1242
        }
 
1243
 
 
1244
        private Statement() {}
 
1245
    }
 
1246
 
 
1247
    @Persistent
 
1248
    static class ExtendedStatement extends Statement {
 
1249
        
 
1250
        @SecondaryKey(relate=MANY_TO_ONE)
 
1251
        String name;
 
1252
 
 
1253
        ExtendedStatement(long id, String name) {
 
1254
            super(id);
 
1255
            this.name = name;
 
1256
        }
 
1257
 
 
1258
        private ExtendedStatement() {}
 
1259
    } 
 
1260
 
 
1261
    public void testCustomCompare()
 
1262
        throws DatabaseException {
 
1263
 
 
1264
        open();
 
1265
 
 
1266
        PrimaryIndex<ReverseIntKey, CustomCompareEntity>
 
1267
            priIndex = store.getPrimaryIndex
 
1268
                (ReverseIntKey.class, CustomCompareEntity.class);
 
1269
 
 
1270
        SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
 
1271
            secIndex1 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
 
1272
                                                "secKey1");
 
1273
 
 
1274
        SecondaryIndex<ReverseIntKey, ReverseIntKey, CustomCompareEntity>
 
1275
            secIndex2 = store.getSecondaryIndex(priIndex, ReverseIntKey.class,
 
1276
                                                "secKey2");
 
1277
 
 
1278
        Transaction txn = txnBegin();
 
1279
        for (int i = 1; i <= 5; i += 1) {
 
1280
            assertTrue(priIndex.putNoOverwrite(txn,
 
1281
                                               new CustomCompareEntity(i)));
 
1282
        }
 
1283
        txnCommit(txn);
 
1284
 
 
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();
 
1289
            assertNotNull(e);
 
1290
            assertEquals(new ReverseIntKey(i), e.key);
 
1291
        }
 
1292
        c.close();
 
1293
        txnCommit(txn);
 
1294
 
 
1295
        txn = txnBeginCursor();
 
1296
        c = secIndex1.entities(txn, null);
 
1297
        for (int i = -1; i >= -5; i -= 1) {
 
1298
            CustomCompareEntity e = c.next();
 
1299
            assertNotNull(e);
 
1300
            assertEquals(new ReverseIntKey(-i), e.key);
 
1301
            assertEquals(new ReverseIntKey(i), e.secKey1);
 
1302
        }
 
1303
        c.close();
 
1304
        txnCommit(txn);
 
1305
 
 
1306
        txn = txnBeginCursor();
 
1307
        c = secIndex2.entities(txn, null);
 
1308
        for (int i = -1; i >= -5; i -= 1) {
 
1309
            CustomCompareEntity e = c.next();
 
1310
            assertNotNull(e);
 
1311
            assertEquals(new ReverseIntKey(-i), e.key);
 
1312
            assertTrue(e.secKey2.contains(new ReverseIntKey(i)));
 
1313
        }
 
1314
        c.close();
 
1315
        txnCommit(txn);
 
1316
 
 
1317
        close();
 
1318
    }
 
1319
 
 
1320
    @Entity
 
1321
    static class CustomCompareEntity {
 
1322
 
 
1323
        @PrimaryKey
 
1324
        private ReverseIntKey key;
 
1325
 
 
1326
        @SecondaryKey(relate=MANY_TO_ONE)
 
1327
        private ReverseIntKey secKey1;
 
1328
 
 
1329
        @SecondaryKey(relate=ONE_TO_MANY)
 
1330
        private Set<ReverseIntKey> secKey2 = new HashSet<ReverseIntKey>();
 
1331
 
 
1332
        private CustomCompareEntity() {}
 
1333
 
 
1334
        CustomCompareEntity(int i) {
 
1335
            key = new ReverseIntKey(i);
 
1336
            secKey1 = new ReverseIntKey(-i);
 
1337
            secKey2.add(new ReverseIntKey(-i));
 
1338
        }
 
1339
    }
 
1340
 
 
1341
    @Persistent
 
1342
    static class ReverseIntKey implements Comparable<ReverseIntKey> {
 
1343
 
 
1344
        @KeyField(1)
 
1345
        private int key;
 
1346
 
 
1347
        public int compareTo(ReverseIntKey o) {
 
1348
            /* Reverse the natural order. */
 
1349
            return o.key - key;
 
1350
        }
 
1351
 
 
1352
        private ReverseIntKey() {}
 
1353
 
 
1354
        ReverseIntKey(int key) {
 
1355
            this.key = key;
 
1356
        }
 
1357
 
 
1358
        @Override
 
1359
        public boolean equals(Object o) {
 
1360
            return key == ((ReverseIntKey) o).key;
 
1361
        }
 
1362
 
 
1363
        @Override
 
1364
        public int hashCode() {
 
1365
            return key;
 
1366
        }
 
1367
 
 
1368
        @Override
 
1369
        public String toString() {
 
1370
            return "Key = " + key;
 
1371
        }
 
1372
    }
 
1373
 
 
1374
    /**
 
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.
 
1380
     *
 
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).
 
1383
     * [#17140]
 
1384
     *
 
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.
 
1390
     */
 
1391
    public void testStoredComparators()
 
1392
        throws DatabaseException {
 
1393
 
 
1394
        open();
 
1395
 
 
1396
        PrimaryIndex<StoredComparatorEntity.Key,
 
1397
                     StoredComparatorEntity> priIndex =
 
1398
            store.getPrimaryIndex(StoredComparatorEntity.Key.class,
 
1399
                                  StoredComparatorEntity.class);
 
1400
 
 
1401
        SecondaryIndex<StoredComparatorEntity.MyEnum,
 
1402
                       StoredComparatorEntity.Key,
 
1403
                       StoredComparatorEntity> secIndex =
 
1404
            store.getSecondaryIndex
 
1405
                (priIndex, StoredComparatorEntity.MyEnum.class, "secKey");
 
1406
 
 
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),
 
1430
            };
 
1431
 
 
1432
        final StoredComparatorEntity.MyEnum[] secKeys =
 
1433
            new StoredComparatorEntity.MyEnum[] {
 
1434
                StoredComparatorEntity.MyEnum.C,
 
1435
                StoredComparatorEntity.MyEnum.B,
 
1436
                StoredComparatorEntity.MyEnum.A,
 
1437
                null,
 
1438
                StoredComparatorEntity.MyEnum.A,
 
1439
                StoredComparatorEntity.MyEnum.B,
 
1440
                StoredComparatorEntity.MyEnum.C,
 
1441
            };
 
1442
 
 
1443
        assertEquals(priKeys.length, secKeys.length);
 
1444
        final int nEntities = priKeys.length;
 
1445
 
 
1446
        Transaction txn = txnBegin();
 
1447
        for (int i = 0; i < nEntities; i += 1) {
 
1448
            priIndex.put(txn,
 
1449
                         new StoredComparatorEntity(priKeys[i], secKeys[i]));
 
1450
        }
 
1451
        txnCommit(txn);
 
1452
 
 
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();
 
1458
            assertNotNull(e);
 
1459
            assertEquals(priKeys[i], e.key);
 
1460
            assertEquals(secKeys[i], e.secKey);
 
1461
        }
 
1462
        assertNull(entities.next());
 
1463
        entities.close();
 
1464
        txnCommit(txn);
 
1465
 
 
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();
 
1473
                    assertNotNull(e);
 
1474
                    assertEquals(priKeys[i], e.key);
 
1475
                    assertEquals(secKeys[i], e.secKey);
 
1476
                }
 
1477
            }
 
1478
        }
 
1479
        assertNull(entities.next());
 
1480
        entities.close();
 
1481
        txnCommit(txn);
 
1482
 
 
1483
        close();
 
1484
    }
 
1485
 
 
1486
    @Entity
 
1487
    static class StoredComparatorEntity {
 
1488
 
 
1489
        enum MyEnum { A, B, C };
 
1490
 
 
1491
        @Persistent
 
1492
        static class Key implements Comparable<Key> {
 
1493
 
 
1494
            @KeyField(1)
 
1495
            MyEnum f1;
 
1496
 
 
1497
            @KeyField(2)
 
1498
            Integer f2;
 
1499
 
 
1500
            @KeyField(3)
 
1501
            MyEnum f3;
 
1502
 
 
1503
            private Key() {}
 
1504
 
 
1505
            Key(MyEnum f1, Integer f2, MyEnum f3) {
 
1506
                this.f1 = f1;
 
1507
                this.f2 = f2;
 
1508
                this.f3 = f3;
 
1509
            }
 
1510
 
 
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;
 
1519
                return 0;
 
1520
            }
 
1521
 
 
1522
            @Override
 
1523
            public boolean equals(Object other) {
 
1524
                if (!(other instanceof Key)) {
 
1525
                    return false;
 
1526
                }
 
1527
                Key o = (Key) other;
 
1528
                return f1 == o.f1 &&
 
1529
                       f2.equals(o.f2) &&
 
1530
                       f3 == o.f3;
 
1531
            }
 
1532
 
 
1533
            @Override
 
1534
            public int hashCode() {
 
1535
                return f1.ordinal() + f2 + f3.ordinal();
 
1536
            }
 
1537
 
 
1538
            @Override
 
1539
            public String toString() {
 
1540
                return "[Key " + f1 + ' ' + f2 + ' ' + f3 + ']';
 
1541
            }
 
1542
        }
 
1543
 
 
1544
        @PrimaryKey
 
1545
        Key key;
 
1546
 
 
1547
        @SecondaryKey(relate=MANY_TO_ONE)
 
1548
        private MyEnum secKey;
 
1549
 
 
1550
        private StoredComparatorEntity() {}
 
1551
 
 
1552
        StoredComparatorEntity(Key key, MyEnum secKey) {
 
1553
            this.key = key;
 
1554
            this.secKey = secKey;
 
1555
        }
 
1556
 
 
1557
        @Override
 
1558
        public String toString() {
 
1559
            return "[pri = " + key + " sec = " + secKey + ']';
 
1560
        }
 
1561
    }
 
1562
}