/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2000-2003
* Sleepycat Software. All rights reserved.
*
* $Id: DataView.java,v 1.24 2003/10/24 15:14:58 gburd Exp $
*/
package com.sleepycat.bdb;
import com.sleepycat.bdb.bind.DataBinding;
import com.sleepycat.bdb.bind.DataFormat;
import com.sleepycat.bdb.bind.EntityBinding;
import com.sleepycat.bdb.bind.KeyExtractor;
import com.sleepycat.db.Db;
import com.sleepycat.db.Dbc;
import com.sleepycat.db.DbEnv;
import com.sleepycat.db.DbException;
import java.io.IOException;
import java.util.Collection;
/**
* (<em>internal</em>) Represents a Berkeley DB database and adds support
* for indices, bindings and key ranges.
*
* <p><b>NOTE:</b> This classes is internal and may be changed incompatibly or
* deleted in the future. It is public only so it may be used by
* subpackages.</p>
*
* <p>This class defines a view and takes care of reading and updating indices,
* calling bindings, constraining access to a key range, etc.</p>
*
* @author Mark Hayes
*/
public final class DataView implements Cloneable {
private static final KeyRange NULL_RANGE = new KeyRange();
DataDb db;
DataStore store;
DataIndex index;
KeyRange range;
boolean writeAllowed;
boolean dirtyRead;
boolean transactional;
boolean dirtyReadAllowed;
boolean autoCommit;
DataBinding keyBinding;
DataBinding valueBinding;
EntityBinding entityBinding;
boolean recNumAccess;
boolean btreeRecNumAccess;
/**
* Creates a view for a given store/index and bindings. The key range of
* the view will be open.
*
* @param store is the store or is ignored if the index parameter is given.
*
* @param index is the index or null if no index is used.
*
* @param keyBinding is the key binding or null if keys will not be used.
*
* @param valueBinding is the value binding or null if an entityBinding is
* given or if values will not be used.
*
* @param entityBinding is the entity binding or null if an valueBinding
* is given or if values will not be used.
*
* @param writeAllowed is whether writing through this view is allowed.
*
* @throws IllegalArgumentException if formats are not consistently
* defined or a parameter is invalid.
*/
public DataView(DataStore store, DataIndex index,
DataBinding keyBinding, DataBinding valueBinding,
EntityBinding entityBinding, boolean writeAllowed)
throws IllegalArgumentException {
if (index != null) {
this.db = index.db;
this.index = index;
this.store = index.store;
} else {
if (store == null)
throw new IllegalArgumentException(
"both store and index are null");
this.db = store.db;
this.store = store;
}
this.writeAllowed = writeAllowed;
this.range = NULL_RANGE;
this.keyBinding = keyBinding;
this.valueBinding = valueBinding;
this.entityBinding = entityBinding;
this.transactional = db.isTransactional();
this.dirtyReadAllowed = transactional &&
(store == null || store.db.isDirtyReadAllowed()) &&
(index == null || index.db.isDirtyReadAllowed());
if (valueBinding != null && entityBinding != null)
throw new IllegalArgumentException(
"both valueBinding and entityBinding are non-null");
if (keyBinding != null &&
keyBinding.getDataFormat() instanceof RecordNumberFormat) {
if (!db.hasRecNumAccess()) {
throw new IllegalArgumentException(
"RecordNumberFormat requires DB_BTREE/DB_RECNUM, " +
"DB_RECNO, or DB_QUEUE");
}
recNumAccess = true;
if (db.type == Db.DB_BTREE) {
btreeRecNumAccess = true;
}
}
checkBindingFormats();
}
/**
* Clone the view.
*/
private DataView cloneView() {
try {
return (DataView) super.clone();
}
catch (CloneNotSupportedException willNeverOccur) { return null; }
}
/**
* Return a new key-set view derived from this view by setting the
* entity and value binding to null.
*
* @return the derived view.
*/
public DataView keySetView() {
if (keyBinding == null) {
throw new UnsupportedOperationException("must have keyBinding");
}
DataView view = cloneView();
view.valueBinding = null;
view.entityBinding = null;
return view;
}
/**
* Return a new value-set view derived from this view by setting the
* key binding to null.
*
* @return the derived view.
*/
public DataView valueSetView() {
if (valueBinding == null && entityBinding == null) {
throw new UnsupportedOperationException(
"must have valueBinding or entityBinding");
}
DataView view = cloneView();
view.keyBinding = null;
return view;
}
/**
* Return a new value-set view for single key range.
*
* @param singleKey the single key value.
*
* @return the derived view.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*
* @throws KeyRangeException if the specified range is not within the
* current range.
*/
public DataView valueSetView(Object singleKey)
throws DbException, IOException, KeyRangeException {
// must do subRange before valueSetView since the latter clears the
// key binding needed for the former
KeyRange singleKeyRange = subRange(singleKey);
DataView view = valueSetView();
view.range = singleKeyRange;
return view;
}
/**
* Return a new value-set view for key range, optionally changing
* the key binding.
*
* @param beginKey the lower bound.
*
* @param beginInclusive whether the lower bound is inclusive.
*
* @param endKey the upper bound.
*
* @param endInclusive whether the upper bound is inclusive.
*
* @param keyBinding a key binding to use, or null to retain the base
* view's key binding.
*
* @return the derived view.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*
* @throws KeyRangeException if the specified range is not within the
* current range.
*/
public DataView subView(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive,
DataBinding keyBinding)
throws DbException, IOException, KeyRangeException {
DataView view = cloneView();
view.setRange(beginKey, beginInclusive, endKey, endInclusive);
if (keyBinding != null) view.keyBinding = keyBinding;
return view;
}
/**
* Returns a new view with a specified dirtyRead setting.
*
* @param enable whether to enable or disable dirty-read.
*
* @return the derived view.
*/
public DataView dirtyReadView(boolean enable) {
if (!isDirtyReadAllowed())
return this;
DataView view = cloneView();
view.dirtyRead = enable;
return view;
}
/**
* Returns a new view with a specified autoCommit setting.
* Note that auto-commit is not implemented by the view, the view only
* holds the auto-commit property.
*
* @param enable whether to enable or disable auto-commit.
*
* @return the derived view.
*/
public DataView autoCommitView(boolean enable) {
if (!isTransactional())
return this;
DataView view = cloneView();
view.autoCommit = enable;
return view;
}
/**
* Returns the current transaction for the view or null if the environment
* is non-transactional.
*
* @return the current transaction.
*/
public CurrentTransaction getCurrentTxn() {
return isTransactional() ? db.env : null;
}
private void setRange(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DbException, IOException, KeyRangeException {
range = subRange(beginKey, beginInclusive, endKey, endInclusive);
}
/**
* Returns the key thang for a single key range, or null if a single key
* range is not used.
*
* @return the key thang or null.
*/
public DataThang getSingleKeyThang() {
return range.getSingleKey();
}
/**
* Returns the database for the index, if one is used, or store, if no
* index is used.
*
* @return the database of the index or, if none, the store.
*/
public DataDb getDb() {
return db;
}
/**
* Returns the environment for the store and index.
*
* @return the environment.
*/
public final DbEnv getEnv() {
return db.env.getEnv();
}
/**
* Returns whether auto-commit is set for this view or for the
* transactional environment of the store and index.
* Note that auto-commit is not implemented by the view, the view only
* holds the auto-commit property.
*
* @return the auto-commit setting.
*/
public final boolean isAutoCommit() {
return autoCommit || db.env.isAutoCommit();
}
/**
* Returns the store, as specified to the constructor.
*
* @return the store.
*/
public final DataStore getStore() {
return store;
}
/**
* Returns the index, as specified to the constructor.
*
* @return the index or null.
*/
public final DataIndex getIndex() {
return index;
}
/**
* Returns the key binding that is used.
*
* @return the key binding or null.
*/
public final DataBinding getKeyBinding() {
return keyBinding;
}
/**
* Returns the value binding that is used.
*
* @return the value binding or null.
*/
public final DataBinding getValueBinding() {
return valueBinding;
}
/**
* Returns the entity binding that is used.
*
* @return the entity binding or null.
*/
public final EntityBinding getValueEntityBinding() {
return entityBinding;
}
/**
* Returns whether duplicates are allowed for the index or store.
*
* @return whether duplicates are allowed.
*/
public final boolean areDuplicatesAllowed() {
return db.areDuplicatesAllowed();
}
/**
* Returns whether duplicates are ordered for the index or store.
*
* @return whether duplicates are ordered.
*/
public final boolean areDuplicatesOrdered() {
return db.areDuplicatesOrdered();
}
/**
* Returns whether keys (record numbers) are renumbered for the index or
* store.
*
* @return whether keys are renumbered.
*/
public final boolean areKeysRenumbered() {
return btreeRecNumAccess || db.areKeysRenumbered();
}
/**
* Returns whether keys are ordered for the index or store.
*
* @return whether keys are ordered.
*/
public final boolean isOrdered() {
return db.isOrdered();
}
/**
* Returns whether write operations are allowed.
*
* @return whether write operations are allowed.
*/
public final boolean isWriteAllowed() {
return writeAllowed;
}
/**
* Returns whether DIRTY_READ was specified for both the Store and Index.
*
* @return whether dirty-read is allowed.
*/
public final boolean isDirtyReadAllowed() {
return dirtyReadAllowed;
}
/**
* Returns whether DIRTY_READ will be used for all read operations.
*
* @return whether dirty-read is enabled.
*/
public final boolean isDirtyReadEnabled() {
return dirtyRead;
}
/**
* Returns whether the store and index are transactional.
*
* @return whether the store and index are transactional.
*/
public final boolean isTransactional() {
return transactional;
}
/**
* Returns whether no records are present in the view.
*
* @return whether the view is empty.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public boolean isEmpty()
throws DbException, IOException {
Dbc cursor = db.openCursor(false);
try {
// val is always unused (discarded) but key is needed for bounds
// checking if the range has a bound
DataThang val = DataThang.getDiscardDataThang();
DataThang key = (range.hasBound()) ? (new DataThang()) : val;
int flags = Db.DB_FIRST;
if (dirtyRead) flags |= Db.DB_DIRTY_READ;
int err = range.get(db, cursor, key, val, flags);
return (err != 0 && err != DataDb.ENOMEM);
} finally {
db.closeCursor(cursor);
}
}
/**
* Performs a general database 'get' operation.
*
* @param key used to find the value
*
* @param value used to find the value
*
* @param flags all flags except DB_SET and DB_GET_BOTH
* are legal, {@link com.sleepycat.db.Db#get(DbTxn,Dbt,Dbt,int)}.
*
* @param lockForWrite if true locks the cursor during the get.
*
* @param retValue used to store the result of the query
*
* @return 0 if mathing values are found, Db.DB_NOTFOUND if not.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public int get(Object key, Object value, int flags, boolean lockForWrite,
Object[] retValue)
throws DbException, IOException {
// Only 0/SET/GET_BOTH flags are allowed.
if (flags == 0) {
flags = Db.DB_SET;
} else if (flags != Db.DB_SET && flags != Db.DB_GET_BOTH) {
throw new IllegalArgumentException("flag not allowed");
}
DataCursor cursor = null;
try {
cursor = new DataCursor(this, false);
int err = cursor.get(key, value, flags, lockForWrite);
if (err == 0 && retValue != null) {
retValue[0] = cursor.getCurrentValue();
}
return err;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Performs a database 'get and consume' operation.
*
* @param flags must be CONSUME or CONSUME_WAIT.
*
* @param retPrimaryKey used to store the resulting key.
*
* @param retValue used to store the resulting value.
*
* @return an error or zero for success.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public int consume(int flags, Object[] retPrimaryKey, Object[] retValue)
throws DbException, IOException {
// Does not respect the range.
// Requires: write allowed
// Requires: if retPrimaryKey, primary key binding (no index).
// Requires: if retValue, value or entity binding
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (flags != Db.DB_CONSUME && flags != Db.DB_CONSUME_WAIT) {
throw new IllegalArgumentException("flag not allowed");
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
int err = store.db.get(keyThang, valueThang, flags);
if (err == 0) {
store.applyChange(keyThang, valueThang, null);
returnPrimaryKeyAndValue(keyThang, valueThang,
retPrimaryKey, retValue);
}
return err;
}
/**
* Performs a database 'put' operation, optionally returning the old value.
*
* @param primaryKey key of new record.
*
* @param value value of new record.
*
* @param flags must be 0, NODUPDATA or NOOVERWRITE.
*
* @param oldValue used to store the old value, or null if none should be
* returned.
*
* @return an error or zero for success.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public int put(Object primaryKey, Object value, int flags,
Object[] oldValue)
throws DbException, IOException {
// Returns old value if key already exists and no duplicates.
// Requires: write allowed
// Requires: if no index, key or entity binding with key or value
// Requires: if index, entity binding with value param and null key
// Requires: if value param, value or entity binding
// Requires: if oldValue param, value or entity binding
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (flags != 0 && flags != Db.DB_NOOVERWRITE &&
flags != Db.DB_NODUPDATA) {
throw new IllegalArgumentException("flags not allowed: " + flags);
}
if (index != null) {
throw new UnsupportedOperationException("cannot put() with index");
}
if (oldValue != null) {
oldValue[0] = null;
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
DataThang oldValueThang = null;
int err = useKey(primaryKey, value, keyThang, range);
if (err != 0) {
throw new IllegalArgumentException("primaryKey out of range " +
keyThang + range);
}
if (flags == 0 && !areDuplicatesAllowed()) {
oldValueThang = new DataThang();
err = store.db.get(keyThang, oldValueThang,
db.env.getWriteLockFlag());
if (err == 0) {
if (oldValue != null) {
oldValue[0] = makeValue(keyThang, oldValueThang);
}
} else {
oldValueThang = null;
}
}
useValue(value, valueThang, null);
err = store.db.put(keyThang, valueThang, flags);
if (err == 0) {
store.applyChange(keyThang, oldValueThang, valueThang);
}
return err;
}
/**
* Adds a duplicate value for a specified key.
*
* @param primaryKeyThang key of new record.
*
* @param value value of new record.
*
* @param flags must be 0 or NODUPDATA or KEYFIRST or KEYLAST.
*
* @return an error or zero for success.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public int addValue(DataThang primaryKeyThang, Object value, int flags)
throws DbException, IOException {
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (!areDuplicatesAllowed()) {
throw new UnsupportedOperationException("duplicates required");
}
if (flags != 0 && flags != Db.DB_NODUPDATA &&
flags != Db.DB_KEYFIRST && flags != Db.DB_KEYLAST) {
throw new IllegalArgumentException("flags not allowed: " + flags);
}
DataThang valueThang = new DataThang();
if (!range.check(primaryKeyThang)) {
throw new IllegalArgumentException("primaryKey out of range");
}
useValue(value, valueThang, null);
int err = store.db.put(primaryKeyThang, valueThang, flags);
if (err == 0) {
store.applyChange(primaryKeyThang, null, valueThang);
}
return err;
}
/**
* Appends a value and returns the new key. If a key assigner is used
* it assigns the key, otherwise a QUEUE or RECNO database is required.
*
* @param value is the value to append.
*
* @param retPrimaryKey used to store the assigned key.
*
* @param retValue used to store the resulting entity, or null if none
* should be returned.
*
* @return an error or zero for success.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public int append(Object value, Object[] retPrimaryKey, Object[] retValue)
throws DbException, IOException {
// Flags will be NOOVERWRITE if used with assigner, or APPEND
// otherwise.
// Requires: if value param, value or entity binding
// Requires: if retPrimaryKey, primary key binding (no index).
// Requires: if retValue, value or entity binding
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
int flags;
if (store.keyAssigner != null) {
store.keyAssigner.assignKey(keyThang);
if (!range.check(keyThang)) {
throw new IllegalArgumentException(
"assigned key out of range");
}
flags = Db.DB_NOOVERWRITE;
} else {
if (db.type != Db.DB_QUEUE && db.type != Db.DB_RECNO) {
throw new UnsupportedOperationException(
"DB_QUEUE or DB_RECNO type is required");
}
flags = Db.DB_APPEND; // assume RECNO access method
}
useValue(value, valueThang, null);
int err = store.db.put(keyThang, valueThang, flags);
if (err == 0) {
if (store.keyAssigner == null && !range.check(keyThang)) {
store.db.delete(keyThang, 0);
throw new IllegalArgumentException(
"appended record number out of range");
}
store.applyChange(keyThang, null, valueThang);
returnPrimaryKeyAndValue(keyThang, valueThang,
retPrimaryKey, retValue);
}
return err;
}
/**
* Deletes all records in the current range, optionally returning the
* values for the deleted records.
*
* @param oldValues is used to store the values that are cleared, or null
* if the old values should not be returned.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public void clear(Collection oldValues)
throws DbException, IOException {
DataCursor cursor = null;
try {
cursor = new DataCursor(this, true);
int op = areKeysRenumbered() ? Db.DB_FIRST : Db.DB_NEXT;
int err = 0;
while (err == 0) {
err = cursor.get(null, null, op, true);
if (err == 0) {
if (oldValues != null) {
oldValues.add(cursor.getCurrentValue());
}
err = cursor.delete();
if (err != 0)
throw new DbException(
"Unexpected error on delete", err);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Returns a cursor for this view that reads only records having the
* specified index key values.
*
* @param indexViews are the views to be joined.
*
* @param indexKeys are the keys to join on for each view.
*
* @param presorted is whether the given views are presorted or should be
* sorted by number of values per key.
*
* @return the join cursor.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public DataCursor join(DataView[] indexViews, Object[] indexKeys,
boolean presorted)
throws DbException, IOException {
DataCursor joinCursor = null;
DataCursor[] indexCursors = new DataCursor[indexViews.length];
try {
for (int i = 0; i < indexViews.length; i += 1) {
indexCursors[i] = new DataCursor(indexViews[i], false);
indexCursors[i].get(indexKeys[i], null, Db.DB_SET, false);
}
joinCursor = new DataCursor(this, indexCursors, presorted, true);
return joinCursor;
} finally {
if (joinCursor == null) {
// An exception is being thrown, so close cursors we opened.
for (int i = 0; i < indexCursors.length; i += 1) {
if (indexCursors[i] != null) {
try { indexCursors[i].close(); }
catch (Exception e) {}
}
}
}
}
}
/**
* Returns a cursor for this view that reads only records having the
* index key values at the specified cursors.
*
* @param indexCursor are the cursors to be joined.
*
* @param presorted is whether the given cursors are presorted or should be
* sorted by number of values per key.
*
* @return the join cursor.
*
* @throws DbException if a database problem occurs.
*
* @throws IOException if an IO problem occurs.
*/
public DataCursor join(DataCursor[] indexCursors, boolean presorted)
throws DbException, IOException {
return new DataCursor(this, indexCursors, presorted, false);
}
private void returnPrimaryKeyAndValue(DataThang keyThang,
DataThang valueThang,
Object[] retPrimaryKey,
Object[] retValue)
throws DbException, IOException {
// Requires: if retPrimaryKey, primary key binding (no index).
// Requires: if retValue, value or entity binding
if (retPrimaryKey != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"returning key requires primary key binding");
} else if (index != null) {
throw new IllegalArgumentException(
"returning key requires unindexed view");
} else {
retPrimaryKey[0] = keyBinding.dataToObject(keyThang);
}
}
if (retValue != null) {
retValue[0] = makeValue(keyThang, valueThang);
}
}
int useKey(Object key, Object value, DataThang keyThang,
KeyRange checkRange)
throws DbException, IOException {
if (key != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"non-null key with null key binding");
}
keyBinding.objectToData(key, keyThang);
} else if (value == null) {
throw new IllegalArgumentException(
"null key and null value");
} else if (index == null) {
if (entityBinding == null) {
throw new UnsupportedOperationException(
"null key, null index, and null entity binding");
}
entityBinding.objectToKey(value, keyThang);
} else {
KeyExtractor extractor = index.getKeyExtractor();
DataThang primaryKeyThang = null;
DataThang valueThang = null;
if (entityBinding != null) {
if (extractor.getPrimaryKeyFormat() != null) {
primaryKeyThang = new DataThang();
entityBinding.objectToKey(value, primaryKeyThang);
}
if (extractor.getValueFormat() != null) {
valueThang = new DataThang();
entityBinding.objectToValue(value, valueThang);
}
} else {
if (extractor.getPrimaryKeyFormat() != null) {
throw new IllegalStateException(
"primary key needed by index extractor");
}
if (extractor.getValueFormat() != null) {
valueThang = new DataThang();
valueBinding.objectToData(value, valueThang);
}
}
extractor.extractIndexKey(primaryKeyThang, valueThang, keyThang);
}
if (checkRange != null) {
return checkRange.check(keyThang) ? 0 : Db.DB_NOTFOUND;
} else {
return 0;
}
}
/**
* Returns whether data keys can be derived from the value/entity binding
* of this view, which determines whether a value/entity object alone is
* sufficient for operations that require keys.
*
* @return whether data keys can be derived.
*/
public boolean canDeriveKeyFromValue() {
if (index == null) {
return (entityBinding != null);
} else {
KeyExtractor extractor = index.getKeyExtractor();
if (extractor.getPrimaryKeyFormat() != null &&
entityBinding == null) {
return false;
} else if (extractor.getValueFormat() != null &&
entityBinding == null && valueBinding == null) {
return false;
} else {
return true;
}
}
}
void useValue(Object value, DataThang valueThang, DataThang checkKeyThang)
throws DbException, IOException {
if (value != null) {
if (valueBinding != null) {
valueBinding.objectToData(value, valueThang);
} else if (entityBinding != null) {
entityBinding.objectToValue(value, valueThang);
if (checkKeyThang != null) {
DataThang thang = new DataThang();
entityBinding.objectToKey(value, thang);
if (!thang.equals(checkKeyThang)) {
throw new IllegalArgumentException(
"cannot change primary key");
}
}
} else {
throw new IllegalArgumentException(
"non-null value with null value/entity binding");
}
} else {
valueThang.set_data(new byte[0]);
valueThang.set_offset(0);
valueThang.set_size(0);
}
}
Object makeKey(DataThang keyThang)
throws DbException, IOException {
if (keyThang.get_size() == 0) return null;
return keyBinding.dataToObject(keyThang);
}
Object makeValue(DataThang primaryKeyThang, DataThang valueThang)
throws DbException, IOException {
Object value;
if (valueBinding != null) {
value = valueBinding.dataToObject(valueThang);
} else if (entityBinding != null) {
value = entityBinding.dataToObject(primaryKeyThang,
valueThang);
} else {
throw new UnsupportedOperationException(
"requires valueBinding or entityBinding");
}
return value;
}
KeyRange subRange(Object singleKey)
throws DbException, IOException, KeyRangeException {
return range.subRange(makeRangeKey(singleKey));
}
KeyRange subRange(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DbException, IOException, KeyRangeException {
if (beginKey == endKey && beginInclusive && endInclusive) {
return subRange(beginKey);
}
if (!isOrdered()) {
throw new UnsupportedOperationException(
"Cannot use key ranges on an unsorted database");
}
DataThang beginThang =
(beginKey != null) ? makeRangeKey(beginKey) : null;
DataThang endThang =
(endKey != null) ? makeRangeKey(endKey) : null;
return range.subRange(beginThang, beginInclusive,
endThang, endInclusive);
}
private void checkBindingFormats() {
if (keyBinding != null && !recNumAccess) {
DataFormat keyFormat = (index != null) ? index.keyFormat
: store.keyFormat;
if (!keyFormat.equals(keyBinding.getDataFormat())) {
throw new IllegalArgumentException(
db.toString() + " key binding format mismatch");
}
}
if (valueBinding != null) {
if (!store.valueFormat.equals(valueBinding.getDataFormat())) {
throw new IllegalArgumentException(
store.toString() + " value binding format mismatch");
}
}
if (entityBinding != null) {
if (!store.keyFormat.equals(entityBinding.getKeyFormat())) {
throw new IllegalArgumentException(store.toString() +
" value entity binding keyFormat mismatch");
}
if (!store.valueFormat.equals(entityBinding.getValueFormat())) {
throw new IllegalArgumentException(store.toString() +
" value entity binding valueFormat mismatch");
}
}
}
private DataThang makeRangeKey(Object key)
throws DbException, IOException {
DataThang thang = new DataThang();
if (keyBinding != null) {
useKey(key, null, thang, null);
} else {
useKey(null, key, thang, null);
}
return thang;
}
}