package org.reldb.rel.v0.storage.temporary;
import java.util.NoSuchElementException;
import org.reldb.rel.exceptions.ExceptionFatal;
import org.reldb.rel.v0.storage.RelDatabase;
import org.reldb.rel.v0.values.TupleIterator;
import org.reldb.rel.v0.values.ValueTuple;
import com.sleepycat.bind.tuple.LongBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
// As of Oracle Berkeley Java DB version 5.0.34, use of cursor.getSearchKey() on a 'database' with
// a defined Comparator (of type RelDatabase.ComparisonHandler) resulted in
// 'StreamCorruptedException: invalid type code: AC' in RelDatabase.ComparisonHandler. This appears to
// have something to do with using setSortedDuplicates(true) in the DatabaseConfig along with
// a user-defined Comparator.
//
// To get around this, a Database called keys holds <key: key, value: id> pairs, whilst a
// Database called values holds <key: id, value: valueTuple> pairs. The former identifies
// key values, the latter holds associated tuple values.
//
// storageKeys requires unique keys and the custom Comparator RelDatabase.ComparisonHandler
// storageValues requires non-unique keys and no custom Comparator
//
class TempIndexDisk implements TempIndex {
private Database keys;
private Database values;
private RelDatabase database;
private Long id = Long.valueOf(0);
public TempIndexDisk(RelDatabase db) {
database = db;
keys = database.createTempStorage();
values = database.createTempStorageWithDuplicatesNoComparator();
}
public void close() {
database.destroyTempStorage(keys);
database.destroyTempStorage(values);
}
public void put(ValueTuple keyTuple, ValueTuple valueTuple) throws DatabaseException {
DatabaseEntry theKey = new DatabaseEntry();
database.getTupleBinding().objectToEntry(keyTuple, theKey);
DatabaseEntry theID = new DatabaseEntry();
OperationStatus status = keys.get(null, theKey, theID, LockMode.DEFAULT);
if (status == OperationStatus.NOTFOUND) {
LongBinding.longToEntry(id++, theID);
keys.put(null, theKey, theID);
} else if (status != OperationStatus.SUCCESS)
throw new ExceptionFatal("RS0318: Insertion failure in put().");
DatabaseEntry theData = new DatabaseEntry();
database.getTupleBinding().objectToEntry(valueTuple, theData);
values.put(null, theID, theData);
}
// Get a TupleIterator on values which iterates all values for a given Key
public TupleIterator keySearch(ValueTuple keyTuple) {
DatabaseEntry theKey = new DatabaseEntry();
database.getTupleBinding().objectToEntry(keyTuple, theKey);
final DatabaseEntry theID = new DatabaseEntry();
OperationStatus status = keys.get(null, theKey, theID, LockMode.DEFAULT);
if (status == OperationStatus.SUCCESS) {
return new TupleIterator() {
protected Cursor cursor = null;
protected ValueTuple current = null;
public ValueTuple next() {
if (hasNext())
try {
return current;
} finally {
current = null;
}
throw new NoSuchElementException();
}
public void close() {
try {
if (cursor != null) {
cursor.close();
}
} catch (DatabaseException exp) {
throw new ExceptionFatal("RS0319: Unable to close cursor: " + exp.getMessage());
}
}
public boolean hasNext() {
if (current != null)
return true;
try {
final DatabaseEntry foundData = new DatabaseEntry();
if (cursor == null) {
cursor = values.openCursor(null, null);
if (cursor.getSearchKey(theID, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
current = (ValueTuple)database.getTupleBinding().entryToObject(foundData);
return true;
}
return false;
} else if (cursor.getNextDup(theID, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
current = (ValueTuple)database.getTupleBinding().entryToObject(foundData);
return true;
}
} catch (DatabaseException exp) {
throw new ExceptionFatal("RS0320: Unable to get next tuple: " + exp.getMessage());
}
return false;
}
};
} else {
return new TupleIterator() {
public boolean hasNext() {
return false;
}
public ValueTuple next() {
return null;
}
public void close() {}
};
}
}
}