1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: SubIndex.java,v 1.1 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.persist;
10
11import java.util.Map;
12import java.util.SortedMap;
13
14import com.sleepycat.bind.EntityBinding;
15import com.sleepycat.bind.EntryBinding;
16import com.sleepycat.collections.StoredSortedMap;
17import com.sleepycat.compat.DbCompat;
18import com.sleepycat.db.Cursor;
19import com.sleepycat.db.CursorConfig;
20import com.sleepycat.db.DatabaseEntry;
21import com.sleepycat.db.DatabaseException;
22import com.sleepycat.db.Environment;
23import com.sleepycat.db.LockMode;
24import com.sleepycat.db.OperationStatus;
25import com.sleepycat.db.SecondaryCursor;
26import com.sleepycat.db.SecondaryDatabase;
27import com.sleepycat.db.Transaction;
28import com.sleepycat.util.keyrange.KeyRange;
29import com.sleepycat.util.keyrange.RangeCursor;
30
31/**
32 * The EntityIndex returned by SecondaryIndex.subIndex.  A SubIndex, in JE
33 * internal terms, is a duplicates btree for a single key in the main btree.
34 * From the user's viewpoint, the keys are primary keys.  This class implements
35 * that viewpoint.  In general, getSearchBoth and getSearchBothRange are used
36 * where in a normal index getSearchKey and getSearchRange would be used.  The
37 * main tree key is always implied, not passed as a parameter.
38 *
39 * @author Mark Hayes
40 */
41class SubIndex<PK,E> implements EntityIndex<PK,E> {
42
43    private SecondaryIndex<?,PK,E> secIndex;
44    private SecondaryDatabase db;
45    private boolean transactional;
46    private boolean sortedDups;
47    private boolean locking;
48    private DatabaseEntry keyEntry;
49    private Object keyObject;
50    private KeyRange singleKeyRange;
51    private EntryBinding pkeyBinding;
52    private KeyRange emptyPKeyRange;
53    private EntityBinding entityBinding;
54    private ValueAdapter<PK> keyAdapter;
55    private ValueAdapter<E> entityAdapter;
56    private SortedMap<PK,E> map;
57
58    <SK> SubIndex(SecondaryIndex<SK,PK,E> secIndex,
59                  EntityBinding entityBinding,
60                  SK key)
61        throws DatabaseException {
62
63        this.secIndex = secIndex;
64        db = secIndex.getDatabase();
65        transactional = secIndex.transactional;
66        sortedDups = secIndex.sortedDups;
67        locking =
68            DbCompat.getInitializeLocking(db.getEnvironment().getConfig());
69
70        keyObject = key;
71        keyEntry = new DatabaseEntry();
72        secIndex.keyBinding.objectToEntry(key, keyEntry);
73        singleKeyRange = secIndex.emptyRange.subRange(keyEntry);
74
75        PrimaryIndex<PK,E> priIndex = secIndex.getPrimaryIndex();
76        pkeyBinding = priIndex.keyBinding;
77        emptyPKeyRange = priIndex.emptyRange;
78        this.entityBinding = entityBinding;
79
80        keyAdapter = new PrimaryKeyValueAdapter<PK>
81            (priIndex.keyClass, priIndex.keyBinding);
82        entityAdapter = secIndex.entityAdapter;
83    }
84
85    public boolean contains(PK key)
86        throws DatabaseException {
87
88        return contains(null, key, null);
89    }
90
91    public boolean contains(Transaction txn, PK key, LockMode lockMode)
92        throws DatabaseException {
93
94        DatabaseEntry pkeyEntry = new DatabaseEntry();
95        DatabaseEntry dataEntry = BasicIndex.NO_RETURN_ENTRY;
96        pkeyBinding.objectToEntry(key, pkeyEntry);
97
98        OperationStatus status =
99            db.getSearchBoth(txn, keyEntry, pkeyEntry, dataEntry, lockMode);
100        return (status == OperationStatus.SUCCESS);
101    }
102
103    public E get(PK key)
104        throws DatabaseException {
105
106        return get(null, key, null);
107    }
108
109    public E get(Transaction txn, PK key, LockMode lockMode)
110        throws DatabaseException {
111
112        DatabaseEntry pkeyEntry = new DatabaseEntry();
113        DatabaseEntry dataEntry = new DatabaseEntry();
114        pkeyBinding.objectToEntry(key, pkeyEntry);
115
116        OperationStatus status =
117            db.getSearchBoth(txn, keyEntry, pkeyEntry, dataEntry, lockMode);
118
119        if (status == OperationStatus.SUCCESS) {
120            return (E) entityBinding.entryToObject(pkeyEntry, dataEntry);
121        } else {
122            return null;
123        }
124    }
125
126    public long count()
127        throws DatabaseException {
128
129        CursorConfig cursorConfig = locking ?
130            CursorConfig.READ_UNCOMMITTED : null;
131        EntityCursor<PK> cursor = keys(null, cursorConfig);
132        try {
133            if (cursor.next() != null) {
134                return cursor.count();
135            } else {
136                return 0;
137            }
138        } finally {
139            cursor.close();
140        }
141    }
142
143    public boolean delete(PK key)
144        throws DatabaseException {
145
146        return delete(null, key);
147    }
148
149    public boolean delete(Transaction txn, PK key)
150        throws DatabaseException {
151
152        DatabaseEntry pkeyEntry = new DatabaseEntry();
153        DatabaseEntry dataEntry = BasicIndex.NO_RETURN_ENTRY;
154        pkeyBinding.objectToEntry(key, pkeyEntry);
155
156        boolean autoCommit = false;
157	Environment env = db.getEnvironment();
158        if (transactional &&
159	    txn == null &&
160	    DbCompat.getThreadTransaction(env) == null) {
161            txn = env.beginTransaction(null, null);
162            autoCommit = true;
163        }
164
165        boolean failed = true;
166        OperationStatus status;
167        SecondaryCursor cursor = db.openSecondaryCursor(txn, null);
168        try {
169            status = cursor.getSearchBoth
170                (keyEntry, pkeyEntry, dataEntry,
171                 locking ? LockMode.RMW : null);
172            if (status == OperationStatus.SUCCESS) {
173                status = cursor.delete();
174            }
175            failed = false;
176        } finally {
177            cursor.close();
178            if (autoCommit) {
179                if (failed) {
180                    txn.abort();
181                } else {
182                    txn.commit();
183                }
184            }
185        }
186
187        return (status == OperationStatus.SUCCESS);
188    }
189
190    public EntityCursor<PK> keys()
191        throws DatabaseException {
192
193        return keys(null, null);
194    }
195
196    public EntityCursor<PK> keys(Transaction txn, CursorConfig config)
197        throws DatabaseException {
198
199        return cursor(txn, null, keyAdapter, config);
200    }
201
202    public EntityCursor<E> entities()
203        throws DatabaseException {
204
205        return cursor(null, null, entityAdapter, null);
206    }
207
208    public EntityCursor<E> entities(Transaction txn,
209                                    CursorConfig config)
210        throws DatabaseException {
211
212        return cursor(txn, null, entityAdapter, config);
213    }
214
215    public EntityCursor<PK> keys(PK fromKey,
216                                 boolean fromInclusive,
217                                 PK toKey,
218                                 boolean toInclusive)
219        throws DatabaseException {
220
221        return cursor(null, fromKey, fromInclusive, toKey, toInclusive,
222                      keyAdapter, null);
223    }
224
225    public EntityCursor<PK> keys(Transaction txn,
226                                 PK fromKey,
227                                 boolean fromInclusive,
228                                 PK toKey,
229                                 boolean toInclusive,
230                                 CursorConfig config)
231        throws DatabaseException {
232
233        return cursor(txn, fromKey, fromInclusive, toKey, toInclusive,
234                      keyAdapter, config);
235    }
236
237    public EntityCursor<E> entities(PK fromKey,
238                                    boolean fromInclusive,
239                                    PK toKey,
240                                    boolean toInclusive)
241        throws DatabaseException {
242
243        return cursor(null, fromKey, fromInclusive, toKey, toInclusive,
244                      entityAdapter, null);
245    }
246
247    public EntityCursor<E> entities(Transaction txn,
248                                    PK fromKey,
249                                    boolean fromInclusive,
250                                    PK toKey,
251                                    boolean toInclusive,
252                                    CursorConfig config)
253        throws DatabaseException {
254
255        return cursor(txn, fromKey, fromInclusive, toKey, toInclusive,
256                      entityAdapter, config);
257    }
258
259    /*
260    public ForwardCursor<PK> unsortedKeys(KeySelector<PK> selector)
261        throws DatabaseException {
262
263        return unsortedKeys(null, selector, null);
264    }
265
266    public ForwardCursor<PK> unsortedKeys(Transaction txn,
267                                          KeySelector<PK> selector,
268                                          CursorConfig config)
269        throws DatabaseException {
270
271        throw new UnsupportedOperationException();
272    }
273
274    public ForwardCursor<E> unsortedEntities(KeySelector<PK> selector)
275        throws DatabaseException {
276
277        return unsortedEntities(null, selector, null);
278    }
279
280    public ForwardCursor<E> unsortedEntities(Transaction txn,
281                                             KeySelector<PK> selector,
282                                             CursorConfig config)
283        throws DatabaseException {
284
285        throw new UnsupportedOperationException();
286    }
287    */
288
289    private <V> EntityCursor<V> cursor(Transaction txn,
290                                       PK fromKey,
291                                       boolean fromInclusive,
292                                       PK toKey,
293                                       boolean toInclusive,
294                                       ValueAdapter<V> adapter,
295                                       CursorConfig config)
296        throws DatabaseException {
297
298        DatabaseEntry fromEntry = null;
299        if (fromKey != null) {
300            fromEntry = new DatabaseEntry();
301            pkeyBinding.objectToEntry(fromKey, fromEntry);
302        }
303        DatabaseEntry toEntry = null;
304        if (toKey != null) {
305            toEntry = new DatabaseEntry();
306            pkeyBinding.objectToEntry(toKey, toEntry);
307        }
308        KeyRange pkeyRange = emptyPKeyRange.subRange
309            (fromEntry, fromInclusive, toEntry, toInclusive);
310        return cursor(txn, pkeyRange, adapter, config);
311    }
312
313    private <V> EntityCursor<V> cursor(Transaction txn,
314                                       KeyRange pkeyRange,
315                                       ValueAdapter<V> adapter,
316                                       CursorConfig config)
317        throws DatabaseException {
318
319        Cursor cursor = db.openCursor(txn, config);
320        RangeCursor rangeCursor =
321            new RangeCursor(singleKeyRange, pkeyRange, sortedDups, cursor);
322        return new SubIndexCursor<V>(rangeCursor, adapter);
323    }
324
325    public Map<PK,E> map() {
326        return sortedMap();
327    }
328
329    public synchronized SortedMap<PK,E> sortedMap() {
330        if (map == null) {
331            map = (SortedMap) ((StoredSortedMap) secIndex.sortedMap()).
332                duplicatesMap(keyObject, pkeyBinding);
333        }
334        return map;
335    }
336}
337