/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2015 Oracle and/or its affiliates. All rights reserved.
*
*/
package com.sleepycat.util.keyrange;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.db.Cursor;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.LockMode;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.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,
OperationStatus status,
DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data)
throws DatabaseException {
if (status == OperationStatus.SUCCESS) {
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 OperationStatus getFirst(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetFirst(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (pkRange != null && pkRange.isSingleKey()) {
KeyRange.copy(range.beginKey, privKey);
KeyRange.copy(pkRange.beginKey, privPKey);
status = doGetSearchBoth(lockMode);
endOperation(null, status, key, pKey, data);
return status;
}
if (pkRange != null) {
KeyRange.copy(range.beginKey, privKey);
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
if (pkRange.beginKey == null || !sortedDups) {
status = doGetSearchKey(lockMode);
} else {
KeyRange.copy(pkRange.beginKey, privPKey);
status = doGetSearchBothRange(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.beginInclusive &&
pkRange.compare(privPKey, pkRange.beginKey) == 0) {
status = doGetNextDup(lockMode);
}
}
if (status == OperationStatus.SUCCESS &&
!pkRange.check(privPKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
} else if (range.singleKey) {
KeyRange.copy(range.beginKey, privKey);
status = doGetSearchKey(lockMode);
endOperation(null, status, key, pKey, data);
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
if (range.beginKey == null) {
status = doGetFirst(lockMode);
} else {
KeyRange.copy(range.beginKey, privKey);
status = doGetSearchKeyRange(lockMode);
if (status == OperationStatus.SUCCESS &&
!range.beginInclusive &&
range.compare(privKey, range.beginKey) == 0) {
status = doGetNextNoDup(lockMode);
}
}
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
return status;
}
/**
* 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 OperationStatus getLast(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status = OperationStatus.NOTFOUND;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetLast(lockMode);
endOperation(null, status, null, null, null);
return status;
}
Cursor oldCursor = beginOperation();
try {
if (pkRange != null) {
status = getLastInPKeyRange(lockMode);
/* Final check on candidate key and pKey value. */
if (status == OperationStatus.SUCCESS &&
!(range.check(privKey) && pkRange.check(privPKey))) {
status = OperationStatus.NOTFOUND;
}
} else {
status = getLastInKeyRange(lockMode);
/* Final check on candidate key value. */
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
}
return status;
} finally {
endOperation(oldCursor, status, 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 OperationStatus getLastInKeyRange(LockMode lockMode)
throws DatabaseException {
/* Without an endKey, getLast returns the candidate record. */
if (range.endKey == null) {
return doGetLast(lockMode);
}
/*
* K stands for the main key at the cursor position in the comments
* below.
*/
while (true) {
KeyRange.copy(range.endKey, privKey);
OperationStatus status = status = doGetSearchKeyRange(lockMode);
if (status == OperationStatus.SUCCESS) {
/* 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 OperationStatus.SUCCESS;
}
/*
* 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.
*/
status = doGetNextNoDup(lockMode);
if (status == OperationStatus.SUCCESS) {
/*
* K > endKey. Move backward to the last dup for
* endKey.
*/
status = doGetPrev(lockMode);
} else {
/*
* endKey is the last main key in the DB. Its last dup
* is the last key in the DB.
*/
status = doGetLast(lockMode);
}
} 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.
*/
status = doGetPrev(lockMode);
}
} else {
/*
* There are no keys >= endKey in the DB. The last key in the
* range is the last key in the DB.
*/
status = doGetLast(lockMode);
}
if (status != OperationStatus.SUCCESS) {
return status;
}
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 status;
}
}
/**
* 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 OperationStatus getLastInPKeyRange(LockMode lockMode)
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(lockMode);
}
/*
* 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(lockMode);
}
/*
* 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);
OperationStatus status = doGetSearchBothRange(lockMode);
if (status == OperationStatus.SUCCESS) {
/* 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.
*/
status = doGetPrevDup(lockMode);
if (status != OperationStatus.SUCCESS) {
return status;
}
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 OperationStatus.SUCCESS;
}
/* 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);
OperationStatus status = doGetSearchKey(lockMode);
if (status != OperationStatus.SUCCESS) {
return status;
}
/*
* 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.
*/
status = doGetNextNoDup(lockMode);
if (status == OperationStatus.SUCCESS) {
/*
* K > main key and D is its first dup. Move to the prev record
* which should be the last dup for the main key.
*/
status = doGetPrev(lockMode);
} else {
/*
* The main key specified is the last main key in the DB. Its
* last dup is the last record in the DB.
*/
status = doGetLast(lockMode);
}
if (status != OperationStatus.SUCCESS) {
return status;
}
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 OperationStatus.SUCCESS;
}
}
public OperationStatus getNext(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!initialized) {
return getFirst(key, pKey, data, lockMode);
}
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetNext(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (pkRange != null) {
if (pkRange.endKey == null) {
status = doGetNextDup(lockMode);
endOperation(null, status, key, pKey, data);
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetNextDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.checkEnd(privPKey, true)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
} else if (range.singleKey) {
status = doGetNextDup(lockMode);
endOperation(null, status, key, pKey, data);
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetNext(lockMode);
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
return status;
}
public OperationStatus getNextNoDup(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!initialized) {
return getFirst(key, pKey, data, lockMode);
}
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetNextNoDup(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (range.singleKey) {
status = OperationStatus.NOTFOUND;
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetNextNoDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
return status;
}
public OperationStatus getPrev(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!initialized) {
return getLast(key, pKey, data, lockMode);
}
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetPrev(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (pkRange != null) {
if (pkRange.beginKey == null) {
status = doGetPrevDup(lockMode);
endOperation(null, status, key, pKey, data);
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetPrevDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.checkBegin(privPKey, true)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
} else if (range.singleKey) {
status = doGetPrevDup(lockMode);
endOperation(null, status, key, pKey, data);
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetPrev(lockMode);
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
return status;
}
public OperationStatus getPrevNoDup(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!initialized) {
return getLast(key, pKey, data, lockMode);
}
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetPrevNoDup(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (range.singleKey) {
status = OperationStatus.NOTFOUND;
} else {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetPrevNoDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!range.check(privKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
}
return status;
}
public OperationStatus getSearchKey(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetSearchKey(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (!range.check(key)) {
status = OperationStatus.NOTFOUND;
} else if (pkRange != null) {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
shareData(key, privKey);
status = doGetSearchKey(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.check(privPKey)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
} else {
shareData(key, privKey);
status = doGetSearchKey(lockMode);
endOperation(null, status, key, pKey, data);
}
return status;
}
public OperationStatus getSearchBoth(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetSearchBoth(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (!range.check(key) ||
(pkRange != null && !pkRange.check(pKey))) {
status = OperationStatus.NOTFOUND;
} else {
shareData(key, privKey);
if (secCursor != null) {
shareData(pKey, privPKey);
} else {
shareData(data, privData);
}
status = doGetSearchBoth(lockMode);
endOperation(null, status, key, pKey, data);
}
return status;
}
public OperationStatus getSearchKeyRange(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status = OperationStatus.NOTFOUND;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetSearchKeyRange(lockMode);
endOperation(null, status, null, null, null);
return status;
}
Cursor oldCursor = beginOperation();
try {
shareData(key, privKey);
status = doGetSearchKeyRange(lockMode);
if (status == OperationStatus.SUCCESS &&
(!range.check(privKey) ||
(pkRange != null && !pkRange.check(pKey)))) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
return status;
}
public OperationStatus getSearchBothRange(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status = OperationStatus.NOTFOUND;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetSearchBothRange(lockMode);
endOperation(null, status, null, null, null);
return status;
}
Cursor oldCursor = beginOperation();
try {
shareData(key, privKey);
if (secCursor != null) {
shareData(pKey, privPKey);
} else {
shareData(data, privData);
}
status = doGetSearchBothRange(lockMode);
if (status == OperationStatus.SUCCESS &&
(!range.check(privKey) ||
(pkRange != null && !pkRange.check(pKey)))) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
return status;
}
public OperationStatus getSearchRecordNumber(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetSearchRecordNumber(lockMode);
endOperation(null, status, null, null, null);
return status;
}
if (!range.check(key)) {
status = OperationStatus.NOTFOUND;
} else {
shareData(key, privKey);
status = doGetSearchRecordNumber(lockMode);
endOperation(null, status, key, pKey, data);
}
return status;
}
public OperationStatus getNextDup(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
if (!initialized) {
throw new IllegalStateException("Cursor not initialized");
}
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetNextDup(lockMode);
endOperation(null, status, null, null, null);
} else if (pkRange != null && pkRange.endKey != null) {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetNextDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.checkEnd(privPKey, true)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
} else {
status = doGetNextDup(lockMode);
endOperation(null, status, key, pKey, data);
}
return status;
}
public OperationStatus getPrevDup(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
if (!initialized) {
throw new IllegalStateException("Cursor not initialized");
}
OperationStatus status;
if (!range.hasBound()) {
setParams(key, pKey, data);
status = doGetPrevDup(lockMode);
endOperation(null, status, null, null, null);
} else if (pkRange != null && pkRange.beginKey != null) {
status = OperationStatus.NOTFOUND;
Cursor oldCursor = beginOperation();
try {
status = doGetPrevDup(lockMode);
if (status == OperationStatus.SUCCESS &&
!pkRange.checkBegin(privPKey, true)) {
status = OperationStatus.NOTFOUND;
}
} finally {
endOperation(oldCursor, status, key, pKey, data);
}
} else {
status = doGetPrevDup(lockMode);
endOperation(null, status, key, pKey, data);
}
return status;
}
public OperationStatus getCurrent(DatabaseEntry key,
DatabaseEntry pKey,
DatabaseEntry data,
LockMode lockMode)
throws DatabaseException {
if (!initialized) {
throw new IllegalStateException("Cursor not initialized");
}
if (secCursor != null && pKey != null) {
return secCursor.getCurrent(key, pKey, data, lockMode);
} else {
return cursor.getCurrent(key, data, lockMode);
}
}
/*
* 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 OperationStatus doGetFirst(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getFirst(privKey, privPKey, privData, lockMode);
} else {
return cursor.getFirst(privKey, privData, lockMode);
}
}
private OperationStatus doGetLast(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getLast(privKey, privPKey, privData, lockMode);
} else {
return cursor.getLast(privKey, privData, lockMode);
}
}
private OperationStatus doGetNext(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getNext(privKey, privPKey, privData, lockMode);
} else {
return cursor.getNext(privKey, privData, lockMode);
}
}
private OperationStatus doGetNextDup(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getNextDup(privKey, privPKey, privData, lockMode);
} else {
return cursor.getNextDup(privKey, privData, lockMode);
}
}
private OperationStatus doGetNextNoDup(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getNextNoDup(privKey, privPKey, privData,
lockMode);
} else {
return cursor.getNextNoDup(privKey, privData, lockMode);
}
}
private OperationStatus doGetPrev(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getPrev(privKey, privPKey, privData, lockMode);
} else {
return cursor.getPrev(privKey, privData, lockMode);
}
}
private OperationStatus doGetPrevDup(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getPrevDup(privKey, privPKey, privData, lockMode);
} else {
return cursor.getPrevDup(privKey, privData, lockMode);
}
}
private OperationStatus doGetPrevNoDup(LockMode lockMode)
throws DatabaseException {
if (secCursor != null && privPKey != null) {
return secCursor.getPrevNoDup(privKey, privPKey, privData,
lockMode);
} else {
return cursor.getPrevNoDup(privKey, privData, lockMode);
}
}
private OperationStatus doGetSearchKey(LockMode lockMode)
throws DatabaseException {
if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
return OperationStatus.NOTFOUND;
}
if (secCursor != null && privPKey != null) {
return secCursor.getSearchKey(privKey, privPKey, privData,
lockMode);
} else {
return cursor.getSearchKey(privKey, privData, lockMode);
}
}
private OperationStatus doGetSearchKeyRange(LockMode lockMode)
throws DatabaseException {
if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
return OperationStatus.NOTFOUND;
}
if (secCursor != null && privPKey != null) {
return secCursor.getSearchKeyRange(privKey, privPKey, privData,
lockMode);
} else {
return cursor.getSearchKeyRange(privKey, privData, lockMode);
}
}
private OperationStatus doGetSearchBoth(LockMode lockMode)
throws DatabaseException {
if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
return OperationStatus.NOTFOUND;
}
if (secCursor != null && privPKey != null) {
return secCursor.getSearchBoth(privKey, privPKey, privData,
lockMode);
} else {
return cursor.getSearchBoth(privKey, privData, lockMode);
}
}
private OperationStatus doGetSearchBothRange(LockMode lockMode)
throws DatabaseException {
if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) {
return OperationStatus.NOTFOUND;
}
if (secCursor != null && privPKey != null) {
return secCursor.getSearchBothRange(privKey, privPKey,
privData, lockMode);
} else {
return cursor.getSearchBothRange(privKey, privData, lockMode);
}
}
private OperationStatus doGetSearchRecordNumber(LockMode lockMode)
throws DatabaseException {
if (DbCompat.getRecordNumber(privKey) <= 0) {
return OperationStatus.NOTFOUND;
}
if (secCursor != null && privPKey != null) {
return DbCompat.getSearchRecordNumber(secCursor, privKey, privPKey,
privData, lockMode);
} else {
return DbCompat.getSearchRecordNumber(cursor, privKey, privData,
lockMode);
}
}
/*
* 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;
}
}