1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2000, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.collections;
9 
10 import com.sleepycat.compat.DbCompat;
11 import com.sleepycat.bind.EntityBinding;
12 import com.sleepycat.bind.EntryBinding;
13 import com.sleepycat.db.CursorConfig;
14 import com.sleepycat.db.Database;
15 import com.sleepycat.db.DatabaseConfig;
16 import com.sleepycat.db.DatabaseEntry;
17 import com.sleepycat.db.DatabaseException;
18 import com.sleepycat.db.Environment;
19 import com.sleepycat.db.JoinConfig;
20 import com.sleepycat.db.OperationStatus;
21 import com.sleepycat.db.SecondaryConfig;
22 import com.sleepycat.db.SecondaryDatabase;
23 import com.sleepycat.db.SecondaryKeyCreator;
24 import com.sleepycat.db.Transaction;
25 import com.sleepycat.util.RuntimeExceptionWrapper;
26 import com.sleepycat.util.keyrange.KeyRange;
27 import com.sleepycat.util.keyrange.KeyRangeException;
28 
29 /**
30  * Represents a Berkeley DB database and adds support for indices, bindings and
31  * key ranges.
32  *
33  * <p>This class defines a view and takes care of reading and updating indices,
34  * calling bindings, constraining access to a key range, etc.</p>
35  *
36  * @author Mark Hayes
37  */
38 final class DataView implements Cloneable {
39 
40     Database db;
41     SecondaryDatabase secDb;
42     CurrentTransaction currentTxn;
43     KeyRange range;
44     EntryBinding keyBinding;
45     EntryBinding valueBinding;
46     EntityBinding entityBinding;
47     PrimaryKeyAssigner keyAssigner;
48     SecondaryKeyCreator secKeyCreator;
49     CursorConfig cursorConfig;      // Used for all operations via this view
50     boolean writeAllowed;           // Read-write view
51     boolean ordered;                // Not a HASH Db
52     boolean keyRangesAllowed;       // BTREE only
53     boolean recNumAllowed;          // QUEUE, RECNO, or BTREE-RECNUM Db
54     boolean recNumAccess;           // recNumAllowed && using a rec num binding
55     boolean btreeRecNumDb;          // BTREE-RECNUM Db
56     boolean btreeRecNumAccess;      // recNumAccess && BTREE-RECNUM Db
57     boolean recNumRenumber;         // RECNO-RENUM Db
58     boolean keysRenumbered;         // recNumRenumber || btreeRecNumAccess
59     boolean dupsAllowed;            // Dups configured
60     boolean dupsOrdered;            // Sorted dups configured
61     boolean transactional;          // Db is transactional
62     boolean readUncommittedAllowed; // Read-uncommited is optional in DB-CORE
63 
64     /*
65      * If duplicatesView is called, dupsView will be true and dupsKey will be
66      * the secondary key used as the "single key" range.  dupRange will be set
67      * as the range of the primary key values if subRange is subsequently
68      * called, to further narrow the view.
69      */
70     DatabaseEntry dupsKey;
71     boolean dupsView;
72     KeyRange dupsRange;
73 
74     /**
75      * Creates a view for a given database and bindings.  The initial key range
76      * of the view will be open.
77      */
DataView(Database database, EntryBinding keyBinding, EntryBinding valueBinding, EntityBinding entityBinding, boolean writeAllowed, PrimaryKeyAssigner keyAssigner)78     DataView(Database database, EntryBinding keyBinding,
79              EntryBinding valueBinding, EntityBinding entityBinding,
80              boolean writeAllowed, PrimaryKeyAssigner keyAssigner)
81         throws IllegalArgumentException {
82 
83         if (database == null) {
84             throw new IllegalArgumentException("database is null");
85         }
86         db = database;
87         try {
88             currentTxn =
89                 CurrentTransaction.getInstanceInternal(db.getEnvironment());
90             DatabaseConfig dbConfig;
91             if (db instanceof SecondaryDatabase) {
92                 secDb = (SecondaryDatabase) database;
93                 SecondaryConfig secConfig = secDb.getSecondaryConfig();
94                 secKeyCreator = secConfig.getKeyCreator();
95                 dbConfig = secConfig;
96             } else {
97                 dbConfig = db.getConfig();
98             }
99             ordered = !DbCompat.isTypeHash(dbConfig);
100             keyRangesAllowed = DbCompat.isTypeBtree(dbConfig);
101             recNumAllowed = DbCompat.isTypeQueue(dbConfig) ||
102                             DbCompat.isTypeRecno(dbConfig) ||
103                             DbCompat.getBtreeRecordNumbers(dbConfig);
104             recNumRenumber = DbCompat.getRenumbering(dbConfig);
105             dupsAllowed = DbCompat.getSortedDuplicates(dbConfig) ||
106                           DbCompat.getUnsortedDuplicates(dbConfig);
107             dupsOrdered = DbCompat.getSortedDuplicates(dbConfig);
108             transactional = currentTxn.isTxnMode() &&
109                             dbConfig.getTransactional();
110             readUncommittedAllowed = DbCompat.getReadUncommitted(dbConfig);
111             btreeRecNumDb = recNumAllowed && DbCompat.isTypeBtree(dbConfig);
112             range = new KeyRange(dbConfig.getBtreeComparator());
113         } catch (DatabaseException e) {
114             throw RuntimeExceptionWrapper.wrapIfNeeded(e);
115         }
116         this.writeAllowed = writeAllowed;
117         this.keyBinding = keyBinding;
118         this.valueBinding = valueBinding;
119         this.entityBinding = entityBinding;
120         this.keyAssigner = keyAssigner;
121         cursorConfig = CursorConfig.DEFAULT;
122 
123         if (valueBinding != null && entityBinding != null)
124             throw new IllegalArgumentException
125                 ("both valueBinding and entityBinding are non-null");
126 
127         if (keyBinding instanceof com.sleepycat.bind.RecordNumberBinding) {
128             if (!recNumAllowed) {
129                 throw new IllegalArgumentException
130                     ("RecordNumberBinding requires DB_BTREE/DB_RECNUM, " +
131                      "DB_RECNO, or DB_QUEUE");
132             }
133             recNumAccess = true;
134             if (btreeRecNumDb) {
135                 btreeRecNumAccess = true;
136             }
137         }
138         keysRenumbered = recNumRenumber || btreeRecNumAccess;
139     }
140 
141     /**
142      * Clones the view.
143      */
cloneView()144     private DataView cloneView() {
145 
146         try {
147             return (DataView) super.clone();
148         } catch (CloneNotSupportedException willNeverOccur) {
149             throw DbCompat.unexpectedState();
150         }
151     }
152 
153     /**
154      * Return a new key-set view derived from this view by setting the
155      * entity and value binding to null.
156      *
157      * @return the derived view.
158      */
keySetView()159     DataView keySetView() {
160 
161         if (keyBinding == null) {
162             throw new UnsupportedOperationException("Must have keyBinding");
163         }
164         DataView view = cloneView();
165         view.valueBinding = null;
166         view.entityBinding = null;
167         return view;
168     }
169 
170     /**
171      * Return a new value-set view derived from this view by setting the
172      * key binding to null.
173      *
174      * @return the derived view.
175      */
valueSetView()176     DataView valueSetView() {
177 
178         if (valueBinding == null && entityBinding == null) {
179             throw new UnsupportedOperationException
180                 ("Must have valueBinding or entityBinding");
181         }
182         DataView view = cloneView();
183         view.keyBinding = null;
184         return view;
185     }
186 
187     /**
188      * Return a new value-set view for single key range.
189      *
190      * @param singleKey the single key value.
191      *
192      * @return the derived view.
193      *
194      * @throws DatabaseException if a database problem occurs.
195      *
196      * @throws KeyRangeException if the specified range is not within the
197      * current range.
198      */
valueSetView(Object singleKey)199     DataView valueSetView(Object singleKey)
200         throws DatabaseException, KeyRangeException {
201 
202         /*
203          * Must do subRange before valueSetView since the latter clears the
204          * key binding needed for the former.
205          */
206         KeyRange singleKeyRange = subRange(range, singleKey);
207         DataView view = valueSetView();
208         view.range = singleKeyRange;
209         return view;
210     }
211 
212     /**
213      * Return a new value-set view for key range, optionally changing
214      * the key binding.
215      */
subView(Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive, EntryBinding keyBinding)216     DataView subView(Object beginKey, boolean beginInclusive,
217                      Object endKey, boolean endInclusive,
218                      EntryBinding keyBinding)
219         throws DatabaseException, KeyRangeException {
220 
221         DataView view = cloneView();
222         view.setRange(beginKey, beginInclusive, endKey, endInclusive);
223         if (keyBinding != null) view.keyBinding = keyBinding;
224         return view;
225     }
226 
227     /**
228      * Return a new duplicates view for a given secondary key.
229      */
duplicatesView(Object secondaryKey, EntryBinding primaryKeyBinding)230     DataView duplicatesView(Object secondaryKey,
231                             EntryBinding primaryKeyBinding)
232         throws DatabaseException, KeyRangeException {
233 
234         if (!isSecondary()) {
235             throw new UnsupportedOperationException
236                 ("Only allowed for maps on secondary databases");
237         }
238         if (dupsView) {
239             throw DbCompat.unexpectedState();
240         }
241         DataView view = cloneView();
242         view.range = subRange(view.range, secondaryKey);
243         view.dupsKey = view.range.getSingleKey();
244         view.dupsView = true;
245         view.keyBinding = primaryKeyBinding;
246         return view;
247     }
248 
249     /**
250      * Returns a new view with a specified cursor configuration.
251      */
configuredView(CursorConfig config)252     DataView configuredView(CursorConfig config) {
253 
254         DataView view = cloneView();
255         view.cursorConfig = (config != null) ?
256             DbCompat.cloneCursorConfig(config) : CursorConfig.DEFAULT;
257         return view;
258     }
259 
260     /**
261      * Returns the current transaction for the view or null if the environment
262      * is non-transactional.
263      */
getCurrentTxn()264     CurrentTransaction getCurrentTxn() {
265 
266         return transactional ? currentTxn : null;
267     }
268 
269     /**
270      * Sets this view's range to a subrange with the given parameters.
271      */
setRange(Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive)272     private void setRange(Object beginKey, boolean beginInclusive,
273                           Object endKey, boolean endInclusive)
274         throws DatabaseException, KeyRangeException {
275 
276         if ((beginKey != null || endKey != null) && !keyRangesAllowed) {
277             throw new UnsupportedOperationException
278                 ("Key ranges allowed only for BTREE databases");
279         }
280         KeyRange useRange = useSubRange();
281         useRange = subRange
282             (useRange, beginKey, beginInclusive, endKey, endInclusive);
283         if (dupsView) {
284             dupsRange = useRange;
285         } else {
286             range = useRange;
287         }
288     }
289 
290     /**
291      * Returns the key thang for a single key range, or null if a single key
292      * range is not used.
293      */
getSingleKeyThang()294     DatabaseEntry getSingleKeyThang() {
295 
296         return range.getSingleKey();
297     }
298 
299     /**
300      * Returns the environment for the database.
301      */
getEnv()302     final Environment getEnv() {
303 
304         return currentTxn.getEnvironment();
305     }
306 
307     /**
308      * Returns whether this is a view on a secondary database rather
309      * than directly on a primary database.
310      */
isSecondary()311     final boolean isSecondary() {
312 
313         return (secDb != null);
314     }
315 
316     /**
317      * Returns whether no records are present in the view.
318      *
319      * Auto-commit must be performed by the caller.
320      */
isEmpty()321     boolean isEmpty()
322         throws DatabaseException {
323 
324         DataCursor cursor = new DataCursor(this, false);
325         try {
326             return cursor.getFirst(false) != OperationStatus.SUCCESS;
327         } finally {
328             cursor.close();
329         }
330     }
331 
332     /**
333      * Appends a value and returns the new key.  If a key assigner is used
334      * it assigns the key, otherwise a QUEUE or RECNO database is required.
335      *
336      * Auto-commit must be performed by the caller.
337      */
append(Object value, Object[] retPrimaryKey, Object[] retValue)338     OperationStatus append(Object value,
339                            Object[] retPrimaryKey,
340                            Object[] retValue)
341         throws DatabaseException {
342 
343         /*
344          * Flags will be NOOVERWRITE if used with assigner, or APPEND
345          * otherwise.
346          * Requires: if value param, value or entity binding
347          * Requires: if retPrimaryKey, primary key binding (no index).
348          * Requires: if retValue, value or entity binding
349          */
350         DatabaseEntry keyThang = new DatabaseEntry();
351         DatabaseEntry valueThang = new DatabaseEntry();
352         useValue(value, valueThang, null);
353         OperationStatus status;
354         if (keyAssigner != null) {
355             keyAssigner.assignKey(keyThang);
356             if (!range.check(keyThang)) {
357                 throw new IllegalArgumentException
358                     ("assigned key out of range");
359             }
360             DataCursor cursor = new DataCursor(this, true);
361             try {
362                 status = cursor.getCursor().putNoOverwrite(keyThang,
363                                                            valueThang);
364             } finally {
365                 cursor.close();
366             }
367         } else {
368             /* Assume QUEUE/RECNO access method. */
369             if (currentTxn.isCDBCursorOpen(db)) {
370                 throw new IllegalStateException
371                     ("cannot open CDB write cursor when read cursor is open");
372             }
373             status = DbCompat.append(db, useTransaction(),
374                                      keyThang, valueThang);
375             if (status == OperationStatus.SUCCESS && !range.check(keyThang)) {
376                 db.delete(useTransaction(), keyThang);
377                 throw new IllegalArgumentException
378                     ("appended record number out of range");
379             }
380         }
381         if (status == OperationStatus.SUCCESS) {
382             returnPrimaryKeyAndValue(keyThang, valueThang,
383                                      retPrimaryKey, retValue);
384         }
385         return status;
386     }
387 
388     /**
389      * Returns the current transaction if the database is transaction, or null
390      * if the database is not transactional or there is no current transaction.
391      */
useTransaction()392     Transaction useTransaction() {
393         return transactional ?  currentTxn.getTransaction() : null;
394     }
395 
396     /**
397      * Deletes all records in the current range.
398      *
399      * Auto-commit must be performed by the caller.
400      */
clear()401     void clear()
402         throws DatabaseException {
403 
404         DataCursor cursor = new DataCursor(this, true);
405         try {
406             OperationStatus status = OperationStatus.SUCCESS;
407             while (status == OperationStatus.SUCCESS) {
408                 if (keysRenumbered) {
409                     status = cursor.getFirst(true);
410                 } else {
411                     status = cursor.getNext(true);
412                 }
413                 if (status == OperationStatus.SUCCESS) {
414                     cursor.delete();
415                 }
416             }
417         } finally {
418             cursor.close();
419         }
420     }
421 
422     /**
423      * Returns a cursor for this view that reads only records having the
424      * specified index key values.
425      */
join(DataView[] indexViews, Object[] indexKeys, JoinConfig joinConfig)426     DataCursor join(DataView[] indexViews, Object[] indexKeys,
427                     JoinConfig joinConfig)
428         throws DatabaseException {
429 
430         DataCursor joinCursor = null;
431         DataCursor[] indexCursors = new DataCursor[indexViews.length];
432         try {
433             for (int i = 0; i < indexViews.length; i += 1) {
434                 indexCursors[i] = new DataCursor(indexViews[i], false);
435                 indexCursors[i].getSearchKey(indexKeys[i], null, false);
436             }
437             joinCursor = new DataCursor(this, indexCursors, joinConfig, true);
438             return joinCursor;
439         } finally {
440             if (joinCursor == null) {
441                 // An exception is being thrown, so close cursors we opened.
442                 for (int i = 0; i < indexCursors.length; i += 1) {
443                     if (indexCursors[i] != null) {
444                         try { indexCursors[i].close(); }
445                         catch (Exception e) {
446                             /* FindBugs, this is ok. */
447                         }
448                     }
449                 }
450             }
451         }
452     }
453 
454     /**
455      * Returns a cursor for this view that reads only records having the
456      * index key values at the specified cursors.
457      */
join(DataCursor[] indexCursors, JoinConfig joinConfig)458     DataCursor join(DataCursor[] indexCursors, JoinConfig joinConfig)
459         throws DatabaseException {
460 
461         return new DataCursor(this, indexCursors, joinConfig, false);
462     }
463 
464     /**
465      * Returns primary key and value if return parameters are non-null.
466      */
returnPrimaryKeyAndValue(DatabaseEntry keyThang, DatabaseEntry valueThang, Object[] retPrimaryKey, Object[] retValue)467     private void returnPrimaryKeyAndValue(DatabaseEntry keyThang,
468                                           DatabaseEntry valueThang,
469                                           Object[] retPrimaryKey,
470                                           Object[] retValue) {
471         // Requires: if retPrimaryKey, primary key binding (no index).
472         // Requires: if retValue, value or entity binding
473 
474         if (retPrimaryKey != null) {
475             if (keyBinding == null) {
476                 throw new IllegalArgumentException
477                     ("returning key requires primary key binding");
478             } else if (isSecondary()) {
479                 throw new IllegalArgumentException
480                     ("returning key requires unindexed view");
481             } else {
482                 retPrimaryKey[0] = keyBinding.entryToObject(keyThang);
483             }
484         }
485         if (retValue != null) {
486             retValue[0] = makeValue(keyThang, valueThang);
487         }
488     }
489 
490     /**
491      * Populates the key entry and returns whether the key is within range.
492      */
useKey(Object key, Object value, DatabaseEntry keyThang, KeyRange checkRange)493     boolean useKey(Object key, Object value, DatabaseEntry keyThang,
494                    KeyRange checkRange)
495         throws DatabaseException {
496 
497         if (key != null) {
498             if (keyBinding == null) {
499                 throw new IllegalArgumentException
500                     ("non-null key with null key binding");
501             }
502             keyBinding.objectToEntry(key, keyThang);
503         } else {
504             if (value == null) {
505                 throw new IllegalArgumentException("null key and null value");
506             }
507             if (entityBinding == null) {
508                 throw new IllegalStateException
509                     ("EntityBinding required to derive key from value");
510             }
511             if (!dupsView && isSecondary()) {
512                 DatabaseEntry primaryKeyThang = new DatabaseEntry();
513                 entityBinding.objectToKey(value, primaryKeyThang);
514                 DatabaseEntry valueThang = new DatabaseEntry();
515                 entityBinding.objectToData(value, valueThang);
516                 secKeyCreator.createSecondaryKey(secDb, primaryKeyThang,
517                                                  valueThang, keyThang);
518             } else {
519                 entityBinding.objectToKey(value, keyThang);
520             }
521         }
522         if (recNumAccess && DbCompat.getRecordNumber(keyThang) <= 0) {
523             return false;
524         }
525         if (checkRange != null && !checkRange.check(keyThang)) {
526             return false;
527         }
528         return true;
529     }
530 
531     /**
532      * Returns whether data keys can be derived from the value/entity binding
533      * of this view, which determines whether a value/entity object alone is
534      * sufficient for operations that require keys.
535      */
canDeriveKeyFromValue()536     final boolean canDeriveKeyFromValue() {
537 
538         return (entityBinding != null);
539     }
540 
541     /**
542      * Populates the value entry and throws an exception if the primary key
543      * would be changed via an entity binding.
544      */
useValue(Object value, DatabaseEntry valueThang, DatabaseEntry checkKeyThang)545     void useValue(Object value, DatabaseEntry valueThang,
546                   DatabaseEntry checkKeyThang) {
547         if (valueBinding != null) {
548             /* Allow binding to handle null value. */
549             valueBinding.objectToEntry(value, valueThang);
550         } else if (entityBinding != null) {
551             if (value == null) {
552                 throw new IllegalArgumentException
553                     ("null value with entity binding");
554             }
555             entityBinding.objectToData(value, valueThang);
556             if (checkKeyThang != null) {
557                 DatabaseEntry thang = new DatabaseEntry();
558                 entityBinding.objectToKey(value, thang);
559                 if (!KeyRange.equalBytes(thang, checkKeyThang)) {
560                     throw new IllegalArgumentException
561                         ("cannot change primary key");
562                 }
563             }
564         } else {
565             if (value != null) {
566                 throw new IllegalArgumentException
567                     ("non-null value with null value/entity binding");
568             }
569             valueThang.setData(KeyRange.ZERO_LENGTH_BYTE_ARRAY);
570             valueThang.setOffset(0);
571             valueThang.setSize(0);
572         }
573     }
574 
575     /**
576      * Converts a key entry to a key object.
577      */
makeKey(DatabaseEntry keyThang, DatabaseEntry priKeyThang)578     Object makeKey(DatabaseEntry keyThang, DatabaseEntry priKeyThang) {
579 
580         if (keyBinding == null) {
581             throw new UnsupportedOperationException();
582         } else {
583             DatabaseEntry thang = dupsView ? priKeyThang : keyThang;
584             if (thang.getSize() == 0) {
585                 return null;
586             } else {
587                 return keyBinding.entryToObject(thang);
588             }
589         }
590     }
591 
592     /**
593      * Converts a key-value entry pair to a value object.
594      */
makeValue(DatabaseEntry primaryKeyThang, DatabaseEntry valueThang)595     Object makeValue(DatabaseEntry primaryKeyThang, DatabaseEntry valueThang) {
596 
597         Object value;
598         if (valueBinding != null) {
599             value = valueBinding.entryToObject(valueThang);
600         } else if (entityBinding != null) {
601             value = entityBinding.entryToObject(primaryKeyThang,
602                                                     valueThang);
603         } else {
604             throw new UnsupportedOperationException
605                 ("Requires valueBinding or entityBinding");
606         }
607         return value;
608     }
609 
610     /**
611      * Intersects the given key and the current range.
612      */
subRange(KeyRange useRange, Object singleKey)613     KeyRange subRange(KeyRange useRange, Object singleKey)
614         throws DatabaseException, KeyRangeException {
615 
616         return useRange.subRange(makeRangeKey(singleKey));
617     }
618 
619     /**
620      * Intersects the given range and the current range.
621      */
subRange(KeyRange useRange, Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive)622     KeyRange subRange(KeyRange useRange,
623                       Object beginKey, boolean beginInclusive,
624                       Object endKey, boolean endInclusive)
625         throws DatabaseException, KeyRangeException {
626 
627         if (beginKey == endKey && beginInclusive && endInclusive) {
628             return subRange(useRange, beginKey);
629         }
630         if (!ordered) {
631             throw new UnsupportedOperationException
632                 ("Cannot use key ranges on an unsorted database");
633         }
634         DatabaseEntry beginThang =
635             (beginKey != null) ? makeRangeKey(beginKey) : null;
636         DatabaseEntry endThang =
637             (endKey != null) ? makeRangeKey(endKey) : null;
638 
639         return useRange.subRange(beginThang, beginInclusive,
640                                  endThang, endInclusive);
641     }
642 
643     /**
644      * Returns the range to use for sub-ranges.  Returns range if this is not a
645      * dupsView, or the dupsRange if this is a dupsView, creating dupsRange if
646      * necessary.
647      */
useSubRange()648     KeyRange useSubRange()
649         throws DatabaseException {
650 
651         if (dupsView) {
652             synchronized (this) {
653                 if (dupsRange == null) {
654                     DatabaseConfig config =
655                         secDb.getPrimaryDatabase().getConfig();
656                     dupsRange = new KeyRange(config.getBtreeComparator());
657                 }
658             }
659             return dupsRange;
660         } else {
661             return range;
662         }
663     }
664 
665     /**
666      * Given a key object, make a key entry that can be used in a range.
667      */
makeRangeKey(Object key)668     private DatabaseEntry makeRangeKey(Object key)
669         throws DatabaseException {
670 
671         DatabaseEntry thang = new DatabaseEntry();
672         if (keyBinding != null) {
673             useKey(key, null, thang, null);
674         } else {
675             useKey(null, key, thang, null);
676         }
677         return thang;
678     }
679 }
680