blob: 4c748db2da75e6c75d1d1757cc57a1a6481ec4a3 [file] [log] [blame]
/*-
* 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.collections;
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.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.JoinConfig;
import com.sleepycat.je.JoinCursor;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.util.keyrange.KeyRange;
import com.sleepycat.util.keyrange.RangeCursor;
/**
* Represents a Berkeley DB cursor and adds support for indices, bindings and
* key ranges.
*
* <p>This class operates on a view and takes care of reading and updating
* indices, calling bindings, constraining access to a key range, etc.</p>
*
* @author Mark Hayes
*/
final class DataCursor implements Cloneable {
/** Repositioned exactly to the key/data pair given. */
static final int REPOS_EXACT = 0;
/** Repositioned on a record following the key/data pair given. */
static final int REPOS_NEXT = 1;
/** Repositioned failed, no records on or after the key/data pair given. */
static final int REPOS_EOF = 2;
private RangeCursor cursor;
private JoinCursor joinCursor;
private DataView view;
private KeyRange range;
private boolean writeAllowed;
private boolean readUncommitted;
private DatabaseEntry keyThang;
private DatabaseEntry valueThang;
private DatabaseEntry primaryKeyThang;
private DatabaseEntry otherThang;
private DataCursor[] indexCursorsToClose;
/**
* Creates a cursor for a given view.
*/
DataCursor(DataView view, boolean writeAllowed)
throws DatabaseException {
init(view, writeAllowed, null, null);
}
/**
* Creates a cursor for a given view.
*/
DataCursor(DataView view, boolean writeAllowed, CursorConfig config)
throws DatabaseException {
init(view, writeAllowed, config, null);
}
/**
* Creates a cursor for a given view and single key range.
* Used by unit tests.
*/
DataCursor(DataView view, boolean writeAllowed, Object singleKey)
throws DatabaseException {
init(view, writeAllowed, null, view.subRange(view.range, singleKey));
}
/**
* Creates a cursor for a given view and key range.
* Used by unit tests.
*/
DataCursor(DataView view, boolean writeAllowed,
Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DatabaseException {
init(view, writeAllowed, null,
view.subRange
(view.range, beginKey, beginInclusive, endKey, endInclusive));
}
/**
* Creates a join cursor.
*/
DataCursor(DataView view, DataCursor[] indexCursors,
JoinConfig joinConfig, boolean closeIndexCursors)
throws DatabaseException {
if (view.isSecondary()) {
throw new IllegalArgumentException(
"The primary collection in a join must not be a secondary " +
"database");
}
Cursor[] cursors = new Cursor[indexCursors.length];
for (int i = 0; i < cursors.length; i += 1) {
cursors[i] = indexCursors[i].cursor.getCursor();
}
joinCursor = view.db.join(cursors, joinConfig);
init(view, false, null, null);
if (closeIndexCursors) {
indexCursorsToClose = indexCursors;
}
}
/**
* Clones a cursor preserving the current position.
*/
DataCursor cloneCursor()
throws DatabaseException {
checkNoJoinCursor();
DataCursor o;
try {
o = (DataCursor) super.clone();
} catch (CloneNotSupportedException neverHappens) {
return null;
}
o.initThangs();
KeyRange.copy(keyThang, o.keyThang);
KeyRange.copy(valueThang, o.valueThang);
if (primaryKeyThang != keyThang) {
KeyRange.copy(primaryKeyThang, o.primaryKeyThang);
}
o.cursor = cursor.dup(true);
return o;
}
/**
* Returns the internal range cursor.
*/
RangeCursor getCursor() {
return cursor;
}
/**
* Constructor helper.
*/
private void init(DataView view,
boolean writeAllowed,
CursorConfig config,
KeyRange range)
throws DatabaseException {
if (config == null) {
config = view.cursorConfig;
}
this.view = view;
this.writeAllowed = writeAllowed && view.writeAllowed;
this.range = (range != null) ? range : view.range;
readUncommitted = config.getReadUncommitted() ||
view.currentTxn.isReadUncommitted();
initThangs();
if (joinCursor == null) {
cursor = new MyRangeCursor
(this.range, config, view, this.writeAllowed);
}
}
/**
* Constructor helper.
*/
private void initThangs() {
keyThang = new DatabaseEntry();
primaryKeyThang = view.isSecondary() ? (new DatabaseEntry())
: keyThang;
valueThang = new DatabaseEntry();
}
/**
* Set entries from given byte arrays.
*/
private void setThangs(byte[] keyBytes,
byte[] priKeyBytes,
byte[] valueBytes) {
keyThang.setData(KeyRange.copyBytes(keyBytes));
if (keyThang != primaryKeyThang) {
primaryKeyThang.setData(KeyRange.copyBytes(priKeyBytes));
}
valueThang.setData(KeyRange.copyBytes(valueBytes));
}
/**
* Closes the associated cursor.
*/
void close()
throws DatabaseException {
if (joinCursor != null) {
JoinCursor toClose = joinCursor;
joinCursor = null;
toClose.close();
}
if (cursor != null) {
Cursor toClose = cursor.getCursor();
cursor = null;
view.currentTxn.closeCursor(toClose);
}
if (indexCursorsToClose != null) {
DataCursor[] toClose = indexCursorsToClose;
indexCursorsToClose = null;
for (int i = 0; i < toClose.length; i += 1) {
toClose[i].close();
}
}
}
/**
* Repositions to a given raw key/data pair, or just past it if that record
* has been deleted.
*
* @return REPOS_EXACT, REPOS_NEXT or REPOS_EOF.
*/
int repositionRange(byte[] lastKeyBytes,
byte[] lastPriKeyBytes,
byte[] lastValueBytes,
boolean lockForWrite)
throws DatabaseException {
OpReadOptions options = OpReadOptions.make(getLockMode(lockForWrite));
OpResult result;
/* Use the given key/data byte arrays. */
setThangs(lastKeyBytes, lastPriKeyBytes, lastValueBytes);
/* Position on or after the given key/data pair. */
if (!view.dupsAllowed) {
/*
* No-dups is the simple case. Search for key >= lastKey, and then
* compare to see if we found lastKey again.
*/
result = cursor.getSearchKeyRange(
keyThang, primaryKeyThang, valueThang, options);
if (!result.isSuccess()) {
return REPOS_EOF;
}
return KeyRange.equalBytes(
lastKeyBytes, 0, lastKeyBytes.length,
keyThang.getData(), keyThang.getOffset(), keyThang.getSize()) ?
REPOS_EXACT : REPOS_NEXT;
}
/*
* Duplicates are more complex.
*
* Search for key == lastKey && data >= lastData, and then compare to
* see if we found lastData again.
*/
result = cursor.getSearchBothRange(
keyThang, primaryKeyThang, valueThang, options);
if (result.isSuccess()) {
DatabaseEntry thang =
view.isSecondary() ? primaryKeyThang : valueThang;
byte[] bytes =
view.isSecondary() ? lastPriKeyBytes : lastValueBytes;
return KeyRange.equalBytes(
bytes, 0, bytes.length,
thang.getData(), thang.getOffset(), thang.getSize()) ?
REPOS_EXACT : REPOS_NEXT;
}
/*
* The record with lastKey/lastData must have been deleted and there
* are no records with key == lastkey && data > lastData.
*
* Search for key >= lastKey, but keep in mind that we will probably
* land on the first record with lastKey.
*/
result = cursor.getSearchKeyRange(
keyThang, primaryKeyThang, valueThang, options);
if (!result.isSuccess()) {
return REPOS_EOF;
}
/*
* If we are positioned on the first dup of lastKey, skip over its
* records to the first record with the next key.
*/
if (KeyRange.equalBytes(lastKeyBytes, 0, lastKeyBytes.length,
keyThang.getData(), keyThang.getOffset(), keyThang.getSize())) {
result = cursor.getNextNoDup(keyThang, primaryKeyThang,
valueThang, options);
if (!result.isSuccess()) {
return REPOS_EOF;
}
}
return REPOS_NEXT;
}
/**
* Repositions to a given raw key/data pair.
*
* @throws IllegalStateException when the database has unordered keys or
* unordered duplicates.
*
* @return whether the search succeeded.
*/
boolean repositionExact(byte[] keyBytes,
byte[] priKeyBytes,
byte[] valueBytes,
boolean lockForWrite)
throws DatabaseException {
OpReadOptions options = OpReadOptions.make(getLockMode(lockForWrite));
OpResult result;
/* Use the given key/data byte arrays. */
setThangs(keyBytes, priKeyBytes, valueBytes);
/* Position on the given key/data pair. */
if (view.recNumRenumber) {
/* getSearchBoth doesn't work with recno-renumber databases. */
result = cursor.getSearchKey(keyThang, primaryKeyThang,
valueThang, options);
} else {
result = cursor.getSearchBoth(keyThang, primaryKeyThang,
valueThang, options);
}
return result.isSuccess();
}
/**
* Returns the view for this cursor.
*/
DataView getView() {
return view;
}
/**
* Returns the range for this cursor.
*/
KeyRange getRange() {
return range;
}
/**
* Returns whether write is allowed for this cursor, as specified to the
* constructor.
*/
boolean isWriteAllowed() {
return writeAllowed;
}
/**
* Returns the key object for the last record read.
*/
Object getCurrentKey() {
return view.makeKey(keyThang, primaryKeyThang);
}
/**
* Returns the value object for the last record read.
*/
Object getCurrentValue() {
return view.makeValue(primaryKeyThang, valueThang);
}
/**
* Returns the internal key entry.
*/
DatabaseEntry getKeyThang() {
return keyThang;
}
/**
* Returns the internal primary key entry, which is the same object as the
* key entry if the cursor is not for a secondary database.
*/
DatabaseEntry getPrimaryKeyThang() {
return primaryKeyThang;
}
/**
* Returns the internal value entry.
*/
DatabaseEntry getValueThang() {
return valueThang;
}
/**
* Returns whether record number access is allowed.
*/
boolean hasRecNumAccess() {
return view.recNumAccess;
}
/**
* Returns the record number for the last record read.
*/
int getCurrentRecordNumber()
throws DatabaseException {
if (view.btreeRecNumDb) {
/* BTREE-RECNO access. */
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
DbCompat.getCurrentRecordNumber(cursor.getCursor(), otherThang,
getLockMode(false));
return DbCompat.getRecordNumber(otherThang);
} else {
/* QUEUE or RECNO database. */
return DbCompat.getRecordNumber(keyThang);
}
}
/**
* Binding version of Cursor.getCurrent(), no join cursor allowed.
*/
OperationStatus getCurrent(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getCurrent(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
/**
* Binding version of Cursor.getFirst(), join cursor is allowed.
*/
OperationStatus getFirst(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else {
return cursor.getFirst(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(lockMode)).status();
}
}
/**
* Binding version of Cursor.getNext(), join cursor is allowed.
*/
OperationStatus getNext(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else {
return cursor.getNext(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(lockMode)).status();
}
}
/**
* Binding version of Cursor.getNext(), join cursor is allowed.
*/
OperationStatus getNextNoDup(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
OpReadOptions options = OpReadOptions.make(lockMode);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else if (view.dupsView) {
return cursor.getNext
(keyThang, primaryKeyThang, valueThang, options).status();
} else {
return cursor.getNextNoDup
(keyThang, primaryKeyThang, valueThang, options).status();
}
}
/**
* Binding version of Cursor.getNextDup(), no join cursor allowed.
*/
OperationStatus getNextDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return null;
} else {
return cursor.getNextDup(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
}
/**
* Binding version of Cursor.getLast(), no join cursor allowed.
*/
OperationStatus getLast(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getLast(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
/**
* Binding version of Cursor.getPrev(), no join cursor allowed.
*/
OperationStatus getPrev(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getPrev(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
/**
* Binding version of Cursor.getPrevNoDup(), no join cursor allowed.
*/
OperationStatus getPrevNoDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
LockMode lockMode = getLockMode(lockForWrite);
OpReadOptions options = OpReadOptions.make(lockMode);
if (view.dupsView) {
return null;
} else {
return cursor.getPrevNoDup(
keyThang, primaryKeyThang, valueThang, options).status();
}
}
/**
* Binding version of Cursor.getPrevDup(), no join cursor allowed.
*/
OperationStatus getPrevDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return null;
} else {
return cursor.getPrevDup(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
}
/**
* Binding version of Cursor.getSearchKey(), no join cursor allowed.
* Searches by record number in a BTREE-RECNO db with RECNO access.
*/
OperationStatus getSearchKey(Object key, Object value,
boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
return cursor.getSearchBoth(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(lockForWrite))).status();
}
} else {
if (view.useKey(key, value, keyThang, range)) {
return doGetSearchKey(lockForWrite);
}
}
return OperationStatus.NOTFOUND;
}
/**
* Pass-thru version of Cursor.getSearchKey().
* Searches by record number in a BTREE-RECNO db with RECNO access.
*/
private OperationStatus doGetSearchKey(boolean lockForWrite)
throws DatabaseException {
OpReadOptions options = OpReadOptions.make(getLockMode(lockForWrite));
if (view.btreeRecNumAccess) {
return cursor.getSearchRecordNumber(
keyThang, primaryKeyThang, valueThang, options).status();
} else {
return cursor.getSearchKey(
keyThang, primaryKeyThang, valueThang, options).status();
}
}
/**
* Binding version of Cursor.getSearchKeyRange(), no join cursor allowed.
*/
OperationStatus getSearchKeyRange(Object key, Object value,
boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
OpReadOptions options = OpReadOptions.make(getLockMode(lockForWrite));
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
return cursor.getSearchBothRange(
keyThang, primaryKeyThang, valueThang, options).status();
}
} else {
if (view.useKey(key, value, keyThang, range)) {
return cursor.getSearchKeyRange(
keyThang, primaryKeyThang, valueThang, options).status();
}
}
return OperationStatus.NOTFOUND;
}
/**
* Find the given key and value using getSearchBoth if possible or a
* sequential scan otherwise, no join cursor allowed.
*/
OperationStatus findBoth(Object key, Object value, boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
OpReadOptions options = OpReadOptions.make(getLockMode(lockForWrite));
view.useValue(value, valueThang, null);
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
OperationStatus status = cursor.getSearchBoth(
keyThang, primaryKeyThang, otherThang, options).status();
if (status == OperationStatus.SUCCESS &&
KeyRange.equalBytes(otherThang, valueThang)) {
return status;
}
}
} else if (view.useKey(key, value, keyThang, range)) {
if (view.isSecondary()) {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
OperationStatus status = cursor.getSearchKey(
keyThang, primaryKeyThang, otherThang, options).status();
while (status == OperationStatus.SUCCESS) {
if (KeyRange.equalBytes(otherThang, valueThang)) {
return status;
}
status = cursor.getNextDup(
keyThang, primaryKeyThang, otherThang,
options).status();
}
/* if status != SUCCESS set range cursor to invalid? */
} else {
return cursor.getSearchBoth(
keyThang, null, valueThang, options).status();
}
}
return OperationStatus.NOTFOUND;
}
/**
* Find the given value using getSearchBoth if possible or a sequential
* scan otherwise, no join cursor allowed.
*/
OperationStatus findValue(Object value, boolean findFirst)
throws DatabaseException {
checkNoJoinCursor();
if (view.entityBinding != null && !view.isSecondary() &&
(findFirst || !view.dupsAllowed)) {
return findBoth(null, value, false);
} else {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
view.useValue(value, otherThang, null);
OperationStatus status = findFirst ? getFirst(false)
: getLast(false);
while (status == OperationStatus.SUCCESS) {
if (KeyRange.equalBytes(valueThang, otherThang)) {
break;
}
status = findFirst ? getNext(false) : getPrev(false);
}
return status;
}
}
/**
* Calls Cursor.count(), no join cursor allowed.
*/
int count()
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return 1;
} else {
return cursor.count();
}
}
/**
* Binding version of Cursor.putCurrent().
*/
OperationStatus putCurrent(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, keyThang);
/*
* Workaround for a DB core problem: With HASH type a put() with
* different data is allowed.
*/
boolean hashWorkaround = (view.dupsOrdered && !view.ordered);
if (hashWorkaround) {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
cursor.getCurrent(
keyThang, primaryKeyThang, otherThang, OpReadOptions.EMPTY);
if (KeyRange.equalBytes(valueThang, otherThang)) {
return OperationStatus.SUCCESS;
} else {
throw new IllegalArgumentException(
"Current data differs from put data with sorted duplicates");
}
}
return cursor.putCurrent(valueThang);
}
/**
* Binding version of Cursor.putAfter().
*/
OperationStatus putAfter(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, null); /* why no key check? */
return cursor.putAfter(keyThang, valueThang);
}
/**
* Binding version of Cursor.putBefore().
*/
OperationStatus putBefore(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, keyThang);
return cursor.putBefore(keyThang, valueThang);
}
/**
* Binding version of Cursor.put(), optionally returning the old value and
* optionally using the current key instead of the key parameter.
*/
OperationStatus put(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, oldValue, useCurrentKey);
return cursor.put(keyThang, valueThang);
}
/**
* Binding version of Cursor.putNoOverwrite(), optionally using the current
* key instead of the key parameter.
*/
OperationStatus putNoOverwrite(Object key, Object value,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, null, useCurrentKey);
return cursor.putNoOverwrite(keyThang, valueThang);
}
/**
* Binding version of Cursor.putNoDupData(), optionally returning the old
* value and optionally using the current key instead of the key parameter.
*/
OperationStatus putNoDupData(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, oldValue, useCurrentKey);
if (view.dupsOrdered) {
return cursor.putNoDupData(keyThang, valueThang);
} else {
if (view.dupsAllowed) {
/* Unordered duplicates. */
OperationStatus status = cursor.getSearchBoth(
keyThang, primaryKeyThang, valueThang,
OpReadOptions.make(getLockMode(false))).status();
if (status == OperationStatus.SUCCESS) {
return OperationStatus.KEYEXIST;
} else {
return cursor.put(keyThang, valueThang);
}
} else {
/* No duplicates. */
return cursor.putNoOverwrite(keyThang, valueThang);
}
}
}
/**
* Do setup for a put() operation.
*/
private void initForPut(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
checkWriteAllowed(false);
if (!useCurrentKey && !view.useKey(key, value, keyThang, range)) {
throw new IllegalArgumentException("key out of range");
}
if (oldValue != null) {
oldValue[0] = null;
if (!view.dupsAllowed) {
OperationStatus status = doGetSearchKey(true);
if (status == OperationStatus.SUCCESS) {
oldValue[0] = getCurrentValue();
}
}
}
view.useValue(value, valueThang, keyThang);
}
/**
* Sets the key entry to the begin key of a single key range, so the next
* time a putXxx() method is called that key will be used.
*/
void useRangeKey() {
if (!range.isSingleKey()) {
throw DbCompat.unexpectedState();
}
KeyRange.copy(range.getSingleKey(), keyThang);
}
/**
* Perform an arbitrary database 'delete' operation.
*/
OperationStatus delete()
throws DatabaseException {
checkWriteAllowed(true);
return cursor.delete();
}
/**
* Returns the lock mode to use for a getXxx() operation.
*/
LockMode getLockMode(boolean lockForWrite) {
/* Read-uncommmitted takes precedence over write-locking. */
if (readUncommitted) {
return LockMode.READ_UNCOMMITTED;
} else if (lockForWrite) {
return view.currentTxn.getWriteLockMode();
} else {
return LockMode.DEFAULT;
}
}
/**
* Throws an exception if a join cursor is in use.
*/
private void checkNoJoinCursor() {
if (joinCursor != null) {
throw new UnsupportedOperationException
("Not allowed with a join cursor");
}
}
/**
* Throws an exception if write is not allowed or if a join cursor is in
* use.
*/
private void checkWriteAllowed(boolean allowSecondary) {
checkNoJoinCursor();
if (!writeAllowed || (!allowSecondary && view.isSecondary())) {
throw new UnsupportedOperationException
("Writing is not allowed");
}
}
}