package com.sleepycat.je;
import java.util.Arrays;
import java.util.Comparator;
import java.util.logging.Level;
import com.sleepycat.je.dbi.GetMode;
import com.sleepycat.je.dbi.CursorImpl.SearchMode;
import com.sleepycat.je.txn.Locker;
import de.ovgu.cide.jakutil.*;
/**
* Javadoc for this public class is generated
* via the doc templates in the doc_src directory.
*/
public class JoinCursor {
private JoinConfig config;
private Database priDb;
private Cursor priCursor;
private Cursor[] secCursors;
private DatabaseEntry[] cursorScratchEntries;
private DatabaseEntry scratchEntry;
/**
* Creates a join cursor without parameter checking.
*/
JoinCursor( Locker locker, Database primaryDb, final Cursor[] cursors, JoinConfig configParam) throws DatabaseException {
priDb=primaryDb;
config=(configParam != null) ? configParam.cloneConfig() : JoinConfig.DEFAULT;
scratchEntry=new DatabaseEntry();
cursorScratchEntries=new DatabaseEntry[cursors.length];
Cursor[] sortedCursors=new Cursor[cursors.length];
System.arraycopy(cursors,0,sortedCursors,0,cursors.length);
if (!config.getNoSort()) {
final int[] counts=new int[cursors.length];
for (int i=0; i < cursors.length; i+=1) {
counts[i]=cursors[i].countInternal(LockMode.READ_UNCOMMITTED);
assert counts[i] >= 0;
}
Arrays.sort(sortedCursors,new Comparator(){
public int compare( Object o1, Object o2){
int count1=-1;
int count2=-1;
for (int i=0; i < cursors.length && (count1 < 0 || count2 < 0); i+=1) {
if (cursors[i] == o1) {
count1=counts[i];
}
else if (cursors[i] == o2) {
count2=counts[i];
}
}
assert count1 >= 0 && count2 >= 0;
return (count1 - count2);
}
}
);
}
try {
priCursor=new Cursor(priDb,locker,null);
secCursors=new Cursor[cursors.length];
for (int i=0; i < cursors.length; i+=1) {
secCursors[i]=new Cursor(sortedCursors[i],true);
}
}
catch ( DatabaseException e) {
close(e);
}
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public void close() throws DatabaseException {
if (priCursor == null) {
throw new DatabaseException("Already closed");
}
close(null);
}
/**
* Close all cursors we own, throwing only the first exception that occurs.
* @param firstException an exception that has already occured, or null.
*/
private void close( DatabaseException firstException) throws DatabaseException {
if (priCursor != null) {
try {
priCursor.close();
}
catch ( DatabaseException e) {
if (firstException == null) {
firstException=e;
}
}
priCursor=null;
}
for (int i=0; i < secCursors.length; i+=1) {
if (secCursors[i] != null) {
try {
secCursors[i].close();
}
catch ( DatabaseException e) {
if (firstException == null) {
firstException=e;
}
}
secCursors[i]=null;
}
}
if (firstException != null) {
throw firstException;
}
}
/**
* For unit testing.
*/
Cursor[] getSortedCursors(){
return secCursors;
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public Database getDatabase(){
return priDb;
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public JoinConfig getConfig(){
return config.cloneConfig();
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public OperationStatus getNext( DatabaseEntry key, LockMode lockMode) throws DatabaseException {
priCursor.checkEnv();
DatabaseUtil.checkForNullDbt(key,"key",false);
this.hook62(lockMode);
return retrieveNext(key,null,lockMode);
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public OperationStatus getNext( DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException {
priCursor.checkEnv();
DatabaseUtil.checkForNullDbt(key,"key",false);
DatabaseUtil.checkForNullDbt(data,"data",false);
this.hook63(lockMode);
return retrieveNext(key,data,lockMode);
}
/**
* Internal version of getNext(), with an optional data param.
* <p>
* Since duplicates are always sorted and duplicate-duplicates are not
* allowed, a natural join can be implemented by simply traversing through
* the duplicates of the first cursor to find candidate keys, and then
* looking for each candidate key in the duplicate set of the other
* cursors, without ever reseting a cursor to the beginning of the
* duplicate set.
* <p>
* This only works when the same duplicate comparison method is used for
* all cursors. We don't check for that, we just assume the user won't
* violate that rule.
* <p>
* A future optimization would be to add a SearchMode.BOTH_DUPS operation
* and use it instead of using SearchMode.BOTH. This would be the
* equivalent of the undocumented DB_GET_BOTHC operation used by DB core's
* join() implementation.
*/
private OperationStatus retrieveNext( DatabaseEntry keyParam, DatabaseEntry dataParam, LockMode lockMode) throws DatabaseException {
outerLoop: while (true) {
Cursor secCursor=secCursors[0];
DatabaseEntry candidateKey=cursorScratchEntries[0];
OperationStatus status;
if (candidateKey == null) {
candidateKey=new DatabaseEntry();
cursorScratchEntries[0]=candidateKey;
status=secCursor.getCurrentInternal(scratchEntry,candidateKey,lockMode);
}
else {
status=secCursor.retrieveNext(scratchEntry,candidateKey,lockMode,GetMode.NEXT_DUP);
}
if (status != OperationStatus.SUCCESS) {
return status;
}
for (int i=1; i < secCursors.length; i+=1) {
secCursor=secCursors[i];
DatabaseEntry secKey=cursorScratchEntries[i];
if (secKey == null) {
secKey=new DatabaseEntry();
cursorScratchEntries[i]=secKey;
status=secCursor.getCurrentInternal(secKey,scratchEntry,lockMode);
assert status == OperationStatus.SUCCESS;
}
scratchEntry.setData(secKey.getData(),secKey.getOffset(),secKey.getSize());
status=secCursor.search(scratchEntry,candidateKey,lockMode,SearchMode.BOTH);
if (status != OperationStatus.SUCCESS) {
continue outerLoop;
}
}
if (dataParam != null) {
status=priCursor.search(candidateKey,dataParam,lockMode,SearchMode.SET);
if (status != OperationStatus.SUCCESS) {
throw new DatabaseException("Secondary corrupt");
}
}
keyParam.setData(candidateKey.getData(),candidateKey.getOffset(),candidateKey.getSize());
return OperationStatus.SUCCESS;
}
}
protected void hook62( LockMode lockMode) throws DatabaseException {
}
protected void hook63( LockMode lockMode) throws DatabaseException {
}
}