All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.collections.DataCursor Maven / Gradle / Ivy

The newest version!
/*-
 * 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.
 *
 * 

This class operates on a view and takes care of reading and updating * indices, calling bindings, constraining access to a key range, etc.

* * @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"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy