/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002,2008 Oracle. All rights reserved. * * $Id: RangeCursor.java,v 1.8 2008/02/07 17:12:29 mark Exp $ */ 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. * *

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.

* * @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) throws DatabaseException { 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. * *

If the returned cursor == the cursor field, the cursor is * uninitialized and was not duped; this case is handled correctly by * endOperation.

*/ 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 succeded, 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) { KeyRange.copy(range.beginKey, privKey); if (pkRange.singleKey) { KeyRange.copy(pkRange.beginKey, privPKey); status = doGetSearchBoth(lockMode); endOperation(null, status, key, pKey, data); } else { 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; } 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) { KeyRange.copy(range.beginKey, privKey); boolean doLast = false; if (!sortedDups) { status = doGetSearchKey(lockMode); } else if (pkRange.endKey == null) { doLast = true; } else { KeyRange.copy(pkRange.endKey, privPKey); status = doGetSearchBothRange(lockMode); if (status == OperationStatus.SUCCESS) { if (!pkRange.endInclusive || pkRange.compare(pkRange.endKey, privPKey) != 0) { status = doGetPrevDup(lockMode); } } else { KeyRange.copy(range.beginKey, privKey); doLast = true; } } if (doLast) { status = doGetSearchKey(lockMode); if (status == OperationStatus.SUCCESS) { status = doGetNextNoDup(lockMode); if (status == OperationStatus.SUCCESS) { status = doGetPrev(lockMode); } else { status = doGetLast(lockMode); } } } if (status == OperationStatus.SUCCESS && !pkRange.check(privPKey)) { status = OperationStatus.NOTFOUND; } } else if (range.endKey == null) { status = doGetLast(lockMode); } else { KeyRange.copy(range.endKey, privKey); status = doGetSearchKeyRange(lockMode); if (status == OperationStatus.SUCCESS) { if (range.endInclusive && range.compare(range.endKey, privKey) == 0) { /* Skip this step if dups are not configured? */ status = doGetNextNoDup(lockMode); if (status == OperationStatus.SUCCESS) { status = doGetPrev(lockMode); } else { status = doGetLast(lockMode); } } else { status = doGetPrev(lockMode); } } else { status = doGetLast(lockMode); } } if (status == OperationStatus.SUCCESS && !range.checkBegin(privKey, true)) { status = OperationStatus.NOTFOUND; } } finally { endOperation(oldCursor, status, key, pKey, data); } return status; } 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 DatabaseException("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 DatabaseException("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 DatabaseException("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; } }