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