| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.util.keyrange; |
| |
| import com.sleepycat.compat.DbCompat; |
| import com.sleepycat.compat.DbCompat.OpReadOptions; |
| import com.sleepycat.compat.DbCompat.OpResult; |
| import com.sleepycat.je.Cursor; |
| import com.sleepycat.je.DatabaseEntry; |
| import com.sleepycat.je.DatabaseException; |
| /* <!-- begin JE only --> */ |
| import com.sleepycat.je.Get; |
| /* <!-- end JE only --> */ |
| import com.sleepycat.je.OperationStatus; |
| import com.sleepycat.je.SecondaryCursor; |
| |
| /** |
| * A cursor-like interface that enforces a key range. The method signatures |
| * are actually those of SecondaryCursor, but the pKey parameter may be null. |
| * It was done this way to avoid doubling the number of methods. |
| * |
| * <p>This is not a fully general implementation of a range cursor and should |
| * not be used directly by applications; however, it may evolve into a |
| * generally useful range cursor some day.</p> |
| * |
| * @author Mark Hayes |
| */ |
| public class RangeCursor implements Cloneable { |
| |
| /** |
| * The cursor and secondary cursor are the same object. The secCursor is |
| * null if the database is not a secondary database. |
| */ |
| private Cursor cursor; |
| private SecondaryCursor secCursor; |
| |
| /** |
| * The range is always non-null, but may be unbounded meaning that it is |
| * open and not used. |
| */ |
| private KeyRange range; |
| |
| /** |
| * The pkRange may be non-null only if the range is a single-key range |
| * and the cursor is a secondary cursor. It further restricts the range of |
| * primary keys in a secondary database. |
| */ |
| private KeyRange pkRange; |
| |
| /** |
| * If the DB supported sorted duplicates, then calling |
| * Cursor.getSearchBothRange is allowed. |
| */ |
| private boolean sortedDups; |
| |
| /** |
| * The privXxx entries are used only when the range is bounded. We read |
| * into these private entries to avoid modifying the caller's entry |
| * parameters in the case where we read successfully but the key is out of |
| * range. In that case we return NOTFOUND and we want to leave the entry |
| * parameters unchanged. |
| */ |
| private DatabaseEntry privKey; |
| private DatabaseEntry privPKey; |
| private DatabaseEntry privData; |
| |
| /** |
| * The initialized flag is set to true whenever we successfully position |
| * the cursor. It is used to implement the getNext/Prev logic for doing a |
| * getFirst/Last when the cursor is not initialized. We can't rely on |
| * Cursor to do that for us, since if we position the underlying cursor |
| * successfully but the key is out of range, we have no way to set the |
| * underlying cursor to uninitialized. A range cursor always starts in the |
| * uninitialized state. |
| */ |
| private boolean initialized; |
| |
| /** |
| * Creates a range cursor with a duplicate range. |
| */ |
| public RangeCursor(KeyRange range, |
| KeyRange pkRange, |
| boolean sortedDups, |
| Cursor cursor) { |
| if (pkRange != null && !range.singleKey) { |
| throw new IllegalArgumentException(); |
| } |
| this.range = range; |
| this.pkRange = pkRange; |
| this.sortedDups = sortedDups; |
| this.cursor = cursor; |
| init(); |
| if (pkRange != null && secCursor == null) { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| /** |
| * Create a cloned range cursor. The caller must clone the underlying |
| * cursor before using this constructor, because cursor open/close is |
| * handled specially for CDS cursors outside this class. |
| */ |
| public RangeCursor dup(boolean samePosition) |
| throws DatabaseException { |
| |
| try { |
| RangeCursor c = (RangeCursor) super.clone(); |
| c.cursor = dupCursor(cursor, samePosition); |
| c.init(); |
| return c; |
| } catch (CloneNotSupportedException neverHappens) { |
| return null; |
| } |
| } |
| |
| /** |
| * Used for opening and duping (cloning). |
| */ |
| private void init() { |
| |
| if (cursor instanceof SecondaryCursor) { |
| secCursor = (SecondaryCursor) cursor; |
| } else { |
| secCursor = null; |
| } |
| |
| if (range.hasBound()) { |
| privKey = new DatabaseEntry(); |
| privPKey = new DatabaseEntry(); |
| privData = new DatabaseEntry(); |
| } else { |
| privKey = null; |
| privPKey = null; |
| privData = null; |
| } |
| } |
| |
| /** |
| * Returns whether the cursor is initialized at a valid position. |
| */ |
| public boolean isInitialized() { |
| return initialized; |
| } |
| |
| /** |
| * Returns the underlying cursor. Used for cloning. |
| */ |
| public Cursor getCursor() { |
| return cursor; |
| } |
| |
| /** |
| * When an unbounded range is used, this method is called to use the |
| * callers entry parameters directly, to avoid the extra step of copying |
| * between the private entries and the caller's entries. |
| */ |
| private void setParams(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data) { |
| privKey = key; |
| privPKey = pKey; |
| privData = data; |
| } |
| |
| /** |
| * Dups the cursor, sets the cursor and secCursor fields to the duped |
| * cursor, and returns the old cursor. Always call endOperation in a |
| * finally clause after calling beginOperation. |
| * |
| * <p>If the returned cursor == the cursor field, the cursor is |
| * uninitialized and was not duped; this case is handled correctly by |
| * endOperation.</p> |
| */ |
| private Cursor beginOperation() |
| throws DatabaseException { |
| |
| Cursor oldCursor = cursor; |
| if (initialized) { |
| cursor = dupCursor(cursor, true); |
| if (secCursor != null) { |
| secCursor = (SecondaryCursor) cursor; |
| } |
| } else { |
| return cursor; |
| } |
| return oldCursor; |
| } |
| |
| /** |
| * If the operation succeeded, leaves the duped cursor in place and closes |
| * the oldCursor. If the operation failed, moves the oldCursor back in |
| * place and closes the duped cursor. oldCursor may be null if |
| * beginOperation was not called, in cases where we don't need to dup |
| * the cursor. Always call endOperation when a successful operation ends, |
| * in order to set the initialized field. |
| */ |
| private void endOperation(Cursor oldCursor, |
| OpResult result, |
| DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data) |
| throws DatabaseException { |
| |
| if (result.isSuccess()) { |
| if (oldCursor != null && oldCursor != cursor) { |
| closeCursor(oldCursor); |
| } |
| if (key != null) { |
| swapData(key, privKey); |
| } |
| if (pKey != null && secCursor != null) { |
| swapData(pKey, privPKey); |
| } |
| if (data != null) { |
| swapData(data, privData); |
| } |
| initialized = true; |
| } else { |
| if (oldCursor != null && oldCursor != cursor) { |
| closeCursor(cursor); |
| cursor = oldCursor; |
| if (secCursor != null) { |
| secCursor = (SecondaryCursor) cursor; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Swaps the contents of the two entries. Used to return entry data to |
| * the caller when the operation was successful. |
| */ |
| private static void swapData(DatabaseEntry e1, DatabaseEntry e2) { |
| |
| byte[] d1 = e1.getData(); |
| int o1 = e1.getOffset(); |
| int s1 = e1.getSize(); |
| |
| e1.setData(e2.getData(), e2.getOffset(), e2.getSize()); |
| e2.setData(d1, o1, s1); |
| } |
| |
| /** |
| * Shares the same byte array, offset and size between two entries. |
| * Used when copying the entry data is not necessary because it is known |
| * that the underlying operation will not modify the entry, for example, |
| * with getSearchKey. |
| */ |
| private static void shareData(DatabaseEntry from, DatabaseEntry to) { |
| |
| if (from != null) { |
| to.setData(from.getData(), from.getOffset(), from.getSize()); |
| } |
| } |
| |
| public OpResult getFirst(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetFirst(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (pkRange != null && pkRange.isSingleKey()) { |
| KeyRange.copy(range.beginKey, privKey); |
| KeyRange.copy(pkRange.beginKey, privPKey); |
| result = doGetSearchBoth(options); |
| endOperation(null, result, key, pKey, data); |
| return result; |
| } |
| if (pkRange != null) { |
| KeyRange.copy(range.beginKey, privKey); |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| if (pkRange.beginKey == null || !sortedDups) { |
| result = doGetSearchKey(options); |
| } else { |
| KeyRange.copy(pkRange.beginKey, privPKey); |
| result = doGetSearchBothRange(options); |
| if (result.isSuccess() && |
| !pkRange.beginInclusive && |
| pkRange.compare(privPKey, pkRange.beginKey) == 0) { |
| result = doGetNextDup(options); |
| } |
| } |
| if (result.isSuccess() && |
| !pkRange.check(privPKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } else if (range.singleKey) { |
| KeyRange.copy(range.beginKey, privKey); |
| result = doGetSearchKey(options); |
| endOperation(null, result, key, pKey, data); |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| if (range.beginKey == null) { |
| result = doGetFirst(options); |
| } else { |
| KeyRange.copy(range.beginKey, privKey); |
| result = doGetSearchKeyRange(options); |
| if (result.isSuccess() && |
| !range.beginInclusive && |
| range.compare(privKey, range.beginKey) == 0) { |
| result = doGetNextNoDup(options); |
| } |
| } |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * This method will restart the operation when a key range is used and an |
| * insertion at the end of the key range is performed in another thread. |
| * The restarts are needed because a sequence of cursor movements is |
| * performed, and serializable isolation cannot be relied on to prevent |
| * insertions in other threads. Without the restarts, getLast could return |
| * NOTFOUND when keys in the range exist. This may only be an issue for JE |
| * since it uses record locking, while DB core uses page locking. |
| */ |
| public OpResult getLast(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result = OpResult.FAILURE; |
| |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetLast(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| |
| Cursor oldCursor = beginOperation(); |
| try { |
| if (pkRange != null) { |
| result = getLastInPKeyRange(options); |
| |
| /* Final check on candidate key and pKey value. */ |
| if (result.isSuccess() && |
| !(range.check(privKey) && pkRange.check(privPKey))) { |
| result = OpResult.FAILURE; |
| } |
| } else { |
| result = getLastInKeyRange(options); |
| |
| /* Final check on candidate key value. */ |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } |
| |
| return result; |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| |
| /** |
| * Performs getLast operation when a main key range is specified but |
| * pkRange is null. Does everything but the final checks for key in range, |
| * i.e., when SUCCESS is returned the caller should do the final check. |
| */ |
| private OpResult getLastInKeyRange(OpReadOptions options) |
| throws DatabaseException { |
| |
| /* Without an endKey, getLast returns the candidate record. */ |
| if (range.endKey == null) { |
| return doGetLast(options); |
| } |
| |
| /* |
| * K stands for the main key at the cursor position in the comments |
| * below. |
| */ |
| while (true) { |
| KeyRange.copy(range.endKey, privKey); |
| OpResult result = doGetSearchKeyRange(options); |
| |
| if (result.isSuccess()) { |
| |
| /* Found K >= endKey. */ |
| if (range.endInclusive && |
| range.compare(range.endKey, privKey) == 0) { |
| |
| /* K == endKey and endKey is inclusive. */ |
| |
| if (!sortedDups) { |
| /* If dups are not configured, we're done. */ |
| return result; |
| } |
| |
| /* |
| * If there are dups, we're positioned at endKey's first |
| * dup and we want to move to its last dup. Move to the |
| * first dup for the next main key (getNextNoDup) and then |
| * the prev record. In the absence of insertions by other |
| * threads, the prev record is the last dup for endKey. |
| */ |
| result = doGetNextNoDup(options); |
| if (result.isSuccess()) { |
| |
| /* |
| * K > endKey. Move backward to the last dup for |
| * endKey. |
| */ |
| result = doGetPrev(options); |
| } else { |
| |
| /* |
| * endKey is the last main key in the DB. Its last dup |
| * is the last key in the DB. |
| */ |
| result = doGetLast(options); |
| } |
| } else { |
| |
| /* |
| * K > endKey or endKey is exclusive (and K >= endKey). In |
| * both cases, moving to the prev key finds the last key in |
| * the range, whether or not there are dups. |
| */ |
| result = doGetPrev(options); |
| } |
| } else { |
| |
| /* |
| * There are no keys >= endKey in the DB. The last key in the |
| * range is the last key in the DB. |
| */ |
| result = doGetLast(options); |
| } |
| |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| if (!range.checkEnd(privKey, true)) { |
| |
| /* |
| * The last call above (getPrev or getLast) returned a key |
| * outside the endKey range. Another thread must have inserted |
| * this key. Start over. |
| */ |
| continue; |
| } |
| |
| return result; |
| } |
| } |
| |
| /** |
| * Performs getLast operation when both a main key range (which must be a |
| * single key range) and a pkRange are specified. Does everything but the |
| * final checks for key and pKey in range, i.e., when SUCCESS is returned |
| * the caller should do the final two checks. |
| */ |
| private OpResult getLastInPKeyRange(OpReadOptions options) |
| throws DatabaseException { |
| |
| /* We can do an exact search when range and pkRange are single keys. */ |
| if (pkRange.isSingleKey()) { |
| KeyRange.copy(range.beginKey, privKey); |
| KeyRange.copy(pkRange.beginKey, privPKey); |
| return doGetSearchBoth(options); |
| } |
| |
| /* |
| * When dups are not configured, getSearchKey for the main key returns |
| * the only possible candidate record. |
| */ |
| if (!sortedDups) { |
| KeyRange.copy(range.beginKey, privKey); |
| return doGetSearchKey(options); |
| } |
| |
| /* |
| * K stands for the main key and D for the duplicate (data item) at the |
| * cursor position in the comments below |
| */ |
| while (true) { |
| |
| if (pkRange.endKey != null) { |
| |
| KeyRange.copy(range.beginKey, privKey); |
| KeyRange.copy(pkRange.endKey, privPKey); |
| OpResult result = doGetSearchBothRange(options); |
| |
| if (result.isSuccess()) { |
| |
| /* Found D >= endKey. */ |
| if (!pkRange.endInclusive || |
| pkRange.compare(pkRange.endKey, privPKey) != 0) { |
| |
| /* |
| * D > endKey or endKey is exclusive (and D >= endKey). |
| * In both cases, moving to the prev dup finds the last |
| * key in the range. |
| */ |
| result = doGetPrevDup(options); |
| |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| if (!pkRange.checkEnd(privPKey, true)) { |
| |
| /* |
| * getPrevDup returned a key outside the endKey |
| * range. Another thread must have inserted this |
| * key. Start over. |
| */ |
| continue; |
| } |
| } |
| /* Else D == endKey and endKey is inclusive. */ |
| |
| return result; |
| } |
| /* Else there are no dups >= endKey. Fall through. */ |
| } |
| |
| /* |
| * We're here for one of two reasons: |
| * 1. pkRange.endKey == null. |
| * 2. There are no dups >= endKey for the main key (status |
| * returned by getSearchBothRange above was not SUCCESS). |
| * In both cases, the last dup in the range is the last dup for the |
| * main key. |
| */ |
| KeyRange.copy(range.beginKey, privKey); |
| OpResult result = doGetSearchKey(options); |
| |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| /* |
| * K == the main key and D is its first dup. We want to move to its |
| * last dup. Move to the first dup for the next main key; |
| * (getNextNoDup) and then the prev record. In the absence of |
| * insertions by other threads, the prev record is the last dup for |
| * the main key. |
| */ |
| result = doGetNextNoDup(options); |
| |
| if (result.isSuccess()) { |
| |
| /* |
| * K > main key and D is its first dup. Move to the prev record |
| * which should be the last dup for the main key. |
| */ |
| result = doGetPrev(options); |
| } else { |
| |
| /* |
| * The main key specified is the last main key in the DB. Its |
| * last dup is the last record in the DB. |
| */ |
| result = doGetLast(options); |
| } |
| |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| if (!range.checkEnd(privKey, true)) { |
| |
| /* |
| * The last call above (getPrev or getLast) returned a key |
| * outside the endKey range. Another thread must have inserted |
| * this key. Start over. |
| */ |
| continue; |
| } |
| |
| return result; |
| } |
| } |
| |
| public OpResult getNext(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!initialized) { |
| return getFirst(key, pKey, data, options); |
| } |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetNext(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (pkRange != null) { |
| if (pkRange.endKey == null) { |
| result = doGetNextDup(options); |
| endOperation(null, result, key, pKey, data); |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetNextDup(options); |
| if (result.isSuccess() && |
| !pkRange.checkEnd(privPKey, true)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| } else if (range.singleKey) { |
| result = doGetNextDup(options); |
| endOperation(null, result, key, pKey, data); |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetNext(options); |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| return result; |
| } |
| |
| public OpResult getNextNoDup(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!initialized) { |
| return getFirst(key, pKey, data, options); |
| } |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetNextNoDup(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (range.singleKey) { |
| result = OpResult.FAILURE; |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetNextNoDup(options); |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| return result; |
| } |
| |
| public OpResult getPrev(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!initialized) { |
| return getLast(key, pKey, data, options); |
| } |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetPrev(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (pkRange != null) { |
| if (pkRange.beginKey == null) { |
| result = doGetPrevDup(options); |
| endOperation(null, result, key, pKey, data); |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetPrevDup(options); |
| if (result.isSuccess() && |
| !pkRange.checkBegin(privPKey, true)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| } else if (range.singleKey) { |
| result = doGetPrevDup(options); |
| endOperation(null, result, key, pKey, data); |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetPrev(options); |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| return result; |
| } |
| |
| public OpResult getPrevNoDup(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!initialized) { |
| return getLast(key, pKey, data, options); |
| } |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetPrevNoDup(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (range.singleKey) { |
| result = OpResult.FAILURE; |
| } else { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetPrevNoDup(options); |
| if (result.isSuccess() && |
| !range.check(privKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } |
| return result; |
| } |
| |
| public OpResult getSearchKey(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetSearchKey(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (!range.check(key)) { |
| result = OpResult.FAILURE; |
| } else if (pkRange != null) { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| shareData(key, privKey); |
| result = doGetSearchKey(options); |
| if (result.isSuccess() && |
| !pkRange.check(privPKey)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } else { |
| shareData(key, privKey); |
| result = doGetSearchKey(options); |
| endOperation(null, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getSearchBoth(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetSearchBoth(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (!range.check(key) || |
| (pkRange != null && !pkRange.check(pKey))) { |
| result = OpResult.FAILURE; |
| } else { |
| shareData(key, privKey); |
| if (secCursor != null) { |
| shareData(pKey, privPKey); |
| } else { |
| shareData(data, privData); |
| } |
| result = doGetSearchBoth(options); |
| endOperation(null, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getSearchKeyRange(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result = OpResult.FAILURE; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetSearchKeyRange(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| Cursor oldCursor = beginOperation(); |
| try { |
| shareData(key, privKey); |
| result = doGetSearchKeyRange(options); |
| if (result.isSuccess() && |
| (!range.check(privKey) || |
| (pkRange != null && !pkRange.check(pKey)))) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getSearchBothRange(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result = OpResult.FAILURE; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetSearchBothRange(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| Cursor oldCursor = beginOperation(); |
| try { |
| shareData(key, privKey); |
| if (secCursor != null) { |
| shareData(pKey, privPKey); |
| } else { |
| shareData(data, privData); |
| } |
| result = doGetSearchBothRange(options); |
| if (result.isSuccess() && |
| (!range.check(privKey) || |
| (pkRange != null && !pkRange.check(pKey)))) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getSearchRecordNumber(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetSearchRecordNumber(options); |
| endOperation(null, result, null, null, null); |
| return result; |
| } |
| if (!range.check(key)) { |
| result = OpResult.FAILURE; |
| } else { |
| shareData(key, privKey); |
| result = doGetSearchRecordNumber(options); |
| endOperation(null, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getNextDup(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| if (!initialized) { |
| throw new IllegalStateException("Cursor not initialized"); |
| } |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetNextDup(options); |
| endOperation(null, result, null, null, null); |
| } else if (pkRange != null && pkRange.endKey != null) { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetNextDup(options); |
| if (result.isSuccess() && |
| !pkRange.checkEnd(privPKey, true)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } else { |
| result = doGetNextDup(options); |
| endOperation(null, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getPrevDup(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| if (!initialized) { |
| throw new IllegalStateException("Cursor not initialized"); |
| } |
| OpResult result; |
| if (!range.hasBound()) { |
| setParams(key, pKey, data); |
| result = doGetPrevDup(options); |
| endOperation(null, result, null, null, null); |
| } else if (pkRange != null && pkRange.beginKey != null) { |
| result = OpResult.FAILURE; |
| Cursor oldCursor = beginOperation(); |
| try { |
| result = doGetPrevDup(options); |
| if (result.isSuccess() && |
| !pkRange.checkBegin(privPKey, true)) { |
| result = OpResult.FAILURE; |
| } |
| } finally { |
| endOperation(oldCursor, result, key, pKey, data); |
| } |
| } else { |
| result = doGetPrevDup(options); |
| endOperation(null, result, key, pKey, data); |
| } |
| return result; |
| } |
| |
| public OpResult getCurrent(DatabaseEntry key, |
| DatabaseEntry pKey, |
| DatabaseEntry data, |
| OpReadOptions options) |
| throws DatabaseException { |
| |
| if (!initialized) { |
| throw new IllegalStateException("Cursor not initialized"); |
| } |
| if (secCursor != null && pKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| key, pKey, data, Get.CURRENT, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getCurrent(key, pKey, data, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get(key, data, Get.CURRENT, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getCurrent(key, data, options.getLockMode())); |
| } |
| } |
| |
| /* |
| * Pass-thru methods. |
| */ |
| |
| public void close() |
| throws DatabaseException { |
| |
| closeCursor(cursor); |
| } |
| |
| public int count() |
| throws DatabaseException { |
| |
| return cursor.count(); |
| } |
| |
| public OperationStatus delete() |
| throws DatabaseException { |
| |
| return cursor.delete(); |
| } |
| |
| public OperationStatus put(DatabaseEntry key, DatabaseEntry data) |
| throws DatabaseException { |
| |
| return cursor.put(key, data); |
| } |
| |
| public OperationStatus putNoOverwrite(DatabaseEntry key, |
| DatabaseEntry data) |
| throws DatabaseException { |
| |
| return cursor.putNoOverwrite(key, data); |
| } |
| |
| public OperationStatus putNoDupData(DatabaseEntry key, DatabaseEntry data) |
| throws DatabaseException { |
| |
| return cursor.putNoDupData(key, data); |
| } |
| |
| public OperationStatus putCurrent(DatabaseEntry data) |
| throws DatabaseException { |
| |
| return cursor.putCurrent(data); |
| } |
| |
| public OperationStatus putAfter(DatabaseEntry key, DatabaseEntry data) |
| throws DatabaseException { |
| |
| return DbCompat.putAfter(cursor, key, data); |
| } |
| |
| public OperationStatus putBefore(DatabaseEntry key, DatabaseEntry data) |
| throws DatabaseException { |
| |
| return DbCompat.putBefore(cursor, key, data); |
| } |
| |
| private OpResult doGetFirst(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.FIRST, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getFirst( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.FIRST, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getFirst(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetLast(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.LAST, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getLast( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.LAST, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getLast(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetNext(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.NEXT, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getNext( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.NEXT, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getNext(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetNextDup(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.NEXT_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getNextDup( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.NEXT_DUP, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getNextDup(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetNextNoDup(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.NEXT_NO_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getNextNoDup( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.NEXT_NO_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getNextNoDup(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetPrev(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.PREV, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getPrev( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.PREV, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getPrev(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetPrevDup(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.PREV_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getPrevDup( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.PREV_DUP, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getPrevDup(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetPrevNoDup(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.PREV_NO_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getPrevNoDup( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.PREV_NO_DUP, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getPrevNoDup(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetSearchKey(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { |
| return OpResult.FAILURE; |
| } |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.SEARCH, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getSearchKey( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.SEARCH, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getSearchKey(privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetSearchKeyRange(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { |
| return OpResult.FAILURE; |
| } |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.SEARCH_GTE, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getSearchKeyRange( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.SEARCH_GTE, options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getSearchKeyRange( |
| privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetSearchBoth(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { |
| return OpResult.FAILURE; |
| } |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.SEARCH_BOTH, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getSearchBoth( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.SEARCH_BOTH, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getSearchBoth( |
| privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetSearchBothRange(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { |
| return OpResult.FAILURE; |
| } |
| if (secCursor != null && privPKey != null) { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| secCursor.get( |
| privKey, privPKey, privData, Get.SEARCH_BOTH_GTE, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| secCursor.getSearchBothRange( |
| privKey, privPKey, privData, options.getLockMode())); |
| } else { |
| /* <!-- begin JE only --> */ |
| if (DbCompat.IS_JE) { |
| return OpResult.make( |
| cursor.get( |
| privKey, privData, Get.SEARCH_BOTH_GTE, |
| options.jeOptions)); |
| } |
| /* <!-- end JE only --> */ |
| return OpResult.make( |
| cursor.getSearchBothRange( |
| privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| private OpResult doGetSearchRecordNumber(OpReadOptions options) |
| throws DatabaseException { |
| |
| if (DbCompat.getRecordNumber(privKey) <= 0) { |
| return OpResult.FAILURE; |
| } |
| if (secCursor != null && privPKey != null) { |
| return OpResult.make( |
| DbCompat.getSearchRecordNumber( |
| secCursor, privKey, privPKey, privData, |
| options.getLockMode())); |
| } else { |
| return OpResult.make( |
| DbCompat.getSearchRecordNumber( |
| cursor, privKey, privData, options.getLockMode())); |
| } |
| } |
| |
| /* |
| * Protected methods for duping and closing cursors. These are overridden |
| * by the collections API to implement cursor pooling for CDS. |
| */ |
| |
| /** |
| * Dups the given cursor. |
| */ |
| protected Cursor dupCursor(Cursor cursor, boolean samePosition) |
| throws DatabaseException { |
| |
| return cursor.dup(samePosition); |
| } |
| |
| /** |
| * Closes the given cursor. |
| */ |
| protected void closeCursor(Cursor cursor) |
| throws DatabaseException { |
| |
| cursor.close(); |
| } |
| |
| /** |
| * If the database is a RECNO or QUEUE database, we know its keys are |
| * record numbers. We treat a non-positive record number as out of bounds, |
| * that is, we return NOTFOUND rather than throwing |
| * IllegalArgumentException as would happen if we passed a non-positive |
| * record number into the DB cursor. This behavior is required by the |
| * collections interface. |
| */ |
| protected boolean checkRecordNumber() { |
| return false; |
| } |
| } |