/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 9, 2008
*/
package com.bigdata.btree.view;
import java.util.NoSuchElementException;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.IRangeQuery;
import com.bigdata.btree.ITuple;
import com.bigdata.btree.ITupleCursor;
import com.bigdata.btree.ITupleCursor2;
import com.bigdata.btree.ITupleIterator;
import com.bigdata.btree.keys.KeyBuilder;
import com.bigdata.util.BytesUtil;
/**
* Layers on the additional methods from the {@link ITupleCursor} interface.
* <p>
* Note: Both the public methods and the internal fields are strongly typed as
* {@link ITupleCursor}s rather than {@link ITupleIterator}s.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class FusedTupleCursor<E> extends FusedTupleIterator<ITupleCursor<E>, E>
implements ITupleCursor<E> {
/**
* The source view. This is required by {@link #getIndex()} and also to
* implement {@link #remove()}.
*/
private final IIndex ndx;
/**
* <code>true</code> iff the current direction of iterator progress is
* "forward", meaning that {@link #hasNext()} was called and that the
* {@link #next()} {@link ITuple}s are currently buffered by
* {@link #sourceTuple}.
* <p>
* This is field always <code>true</code> for this class, but the
* direction can be changed in the {@link FusedTupleCursor} subclass by
* calling {@link ITupleCursor#hasPrior()}. If the direction is changed
* then {@link #sourceTuple}s MUST be cleared before testing whether or not
* the next/prior tuple exists. Likewise, {@link ITupleCursor#seek(byte[])}
* must clear {@link #sourceTuple}s but can leave this flag alone.
*/
private boolean forward = true;
/**
* @param flags
* @param deleted
* @param srcs
* The source iterators.
* @param ndx
* The {@link FusedView} from which the source iterators were
* drawn (required). Note that the semantics of {@link #remove()}
* on a fused view require that the tuple is overwritten by a
* delete marker in the 1st index of the view.
*/
public FusedTupleCursor(final int flags, final boolean deleted,
final ITupleCursor<E>[] srcs, final IIndex ndx) {
super(flags, deleted, srcs);
if (ndx == null)
throw new IllegalArgumentException();
this.ndx = ndx;
}
final public IIndex getIndex() {
return ndx;
}
/**
* <p>
* Set the direction of iterator progress. Clears {@link #sourceTuple} iff
* the current direction is different from the new direction and is
* otherwise a NOP.
* </p>
* <p>
* Note: Care is required for sequences such as
* </p>
*
* <pre>
* ITuple t1 = next();
*
* ITuple t2 = prior();
* </pre>
*
* <p>
* to visit the same tuple for {@link #next()} and {@link #prior()}.
* </p>
*
* @param forward
* <code>true</code> iff the new direction of iterator progress
* is forward using {@link #hasNext()} and {@link #next()}.
*/
private void setForwardDirection(boolean forward) {
if (this.forward != forward) {
if (INFO)
log.info("Changing direction: forward=" + forward);
/*
* This is the last key visited -or- null iff nothing has been
* visited.
*/
final byte[] lastKeyVisited;
if (lastVisited == -1) {
lastKeyVisited = null;
} else {
// lastKeyVisited = ((ITupleCursor2<E>) sourceIterator[lastVisited])
// .tuple().getKey();
lastKeyVisited = lastKeyBuffer.getKey();
if (INFO)
log.info("key for last tuple visited="
+ BytesUtil.toString(lastKeyVisited));
}
for (int i = 0; i < n; i++) {
/*
* Recover the _current_ tuple for each source iterator.
*/
// current tuple for the source iterator.
ITuple<E> tuple = ((ITupleCursor2<E>) sourceIterator[i])
.tuple();
if(INFO)
log.info("sourceIterator[" + i + "]=" + tuple);
if (lastKeyVisited != null) {
/*
* When we are changing to [forward == true] (visiting the
* next tuples in the index order), then we advance the
* source iterator zero or more tuples until it is
* positioned GT the lastVisitedKey.
*
* When we are changing to [forward == false] (visiting the
* prior tuples in the index order), then we backup the
* source iterator zero or more tuples until it is
* positioned LT the lastVisitedKey.
*/
while (tuple != null) {
final int ret = BytesUtil.compareBytes(//
tuple.getKey(), //
lastKeyVisited //
);
final boolean ok = forward ? ret > 0 : ret < 0;
if(ok) break;
/*
* If the source iterator is currently positioned on the
* same key as the last tuple that we visited then we
* need to move it off of that key - either to the
* previous or the next visitable tuple depending on the
* new direction for the iterator.
*/
if (forward) {
if (sourceIterator[i].hasNext()) {
// next tuple
tuple = sourceIterator[i].next();
} else {
// exhausted in this direction.
tuple = null;
}
} else {
if (sourceIterator[i].hasPrior()) {
// prior tuple
tuple = sourceIterator[i].prior();
} else {
// exhausted in this direction.
tuple = null;
}
}
if (INFO)
log.info("skipping tuple: source=" + i
+ ", direction="
+ (forward ? "next" : "prior")
+ ", newTuple=" + tuple);
}
}
sourceTuple[i] = tuple;
// as assigned to source[i].
if (INFO)
log.info("sourceTuple [" + i + "]=" + sourceTuple[i]);
}
// set the new iterator direction.
this.forward = forward;
// clear current since the old lookahead choice is no longer valid.
this.current = -1;
}
}
public boolean hasNext() {
setForwardDirection(true/*forward*/);
return super.hasNext();
}
/**
* Note: The implementation of {@link #hasPrior()} closes parallels the
* implementation of {@link #hasNext()} in the base class.
*/
public boolean hasPrior() {
setForwardDirection(false/*forward*/);
/*
* Until we find an undeleted tuple (or any tuple if DELETED is
* true).
*/
while (true) {
if (current != -1) {
if(INFO) log.info("Already matched: source=" + current);
return true;
}
/*
* First, make sure that we have a tuple for each source iterator
* (unless that iterator is exhausted).
*/
int nexhausted = 0;
for (int i = 0; i < n; i++) {
if (sourceTuple[i] == null) {
if (sourceIterator[i].hasPrior()) {
sourceTuple[i] = sourceIterator[i].prior();
if (DEBUG)
log.debug("read sourceTuple[" + i + "]="
+ sourceTuple[i]);
} else {
nexhausted++;
}
}
}
if (nexhausted == n) {
// the aggregate iterator is exhausted.
return false;
}
/*
* Now consider the current tuple for each source iterator in turn
* and choose the _first_ iterator having a tuple whose key orders
* GTE all the others (or LTE if [reverseScan == true]). This is the
* previous tuple to be visited by the aggregate iterator.
*/
{
// current is index of the smallest key so far.
assert current == -1;
byte[] key = null; // smallest key so far.
for (int i = 0; i < n; i++) {
if (sourceTuple[i] == null) {
// This source is exhausted.
continue;
}
if (current == -1) {
current = i;
key = sourceTuple[i].getKey();
assert key != null;
} else {
final byte[] tmp = sourceTuple[i].getKey();
final int ret = BytesUtil.compareBytes(tmp, key);
// if (reverseScan ? ret < 0 : ret > 0) {
if (ret > 0) {
/*
* This key orders GT the current key.
*
* Note: This test MUST be strictly GT since GTE
* would break the precedence in which we are
* processing the source iterators and give us the
* key from the last source by preference when we
* need the key from the first source by preference.
*/
current = i;
key = tmp;
}
}
}
assert current != -1;
}
if (sourceTuple[current].isDeletedVersion() && !deleted) {
/*
* The tuple is marked as "deleted" and the caller did not
* request deleted tuples so we skip this key and begin again
* with the next key visible under the fused iterator view.
*/
if(INFO) {
log.info("Skipping deleted: source=" + current + ", tuple="
+ sourceTuple[current]);
}
/*
* Clear tuples from other sources having the same key as the
* current tuple.
*/
clearCurrent();
continue;
}
if(INFO) {
log.info("Will visit next: source=" + current + ", tuple: "
+ sourceTuple[current]);
}
return true;
}
}
public ITuple<E> prior() {
if (!hasPrior())
throw new NoSuchElementException();
return consumeLookaheadTuple();
}
public ITuple<E> seek(final byte[] key) {
// clear last visited.
lastVisited = -1;
// clear current.
current = -1;
// save the sought key.
lastKeyBuffer.reset().append(key);
for (int i = 0; i < n; i++) {
sourceTuple[i] = sourceIterator[i].seek(key);
if (sourceTuple[i] != null && current == -1) {
/*
* Choose the tuple reported by the first source iterator in the
* order in which the source iterators are processed. Any
* iterator that does not have a tuple for the seek key will
* report a null. The first non-null will therefore be the first
* iterator having an exact match for the given key.
*/
if (INFO) {
log.info("Found match: source=" + i + ", key="
+ BytesUtil.toString(key));
}
current = i;
}
}
// the lookahead tuples are primed for forward traversal.
forward = true;
if(!deleted){
for(int i=0; i<n; i++) {
if (sourceTuple[i] != null && sourceTuple[i].isDeletedVersion()) {
/*
* The tuple is marked as "deleted" and the caller did not
* request deleted tuples. In this case seek(byte[]) must
* return null.
*/
if (INFO)
log.info("Skipping deleted: source=" + current
+ ", tuple=" + sourceTuple[current]);
/*
* Clear tuples from other sources having the same key as the
* current tuple.
*/
clearCurrent();
return null;
}
}
}
if (current == -1) {
/*
* There is no tuple equal to the sought key.
*/
// no tuple for that key.
return null;
} else {
/*
* There is a tuple for that key, so consume and return it.
*/
return consumeLookaheadTuple();
}
}
/**
* Extended to make a copy of the key for each visited tuple.
*
* @see #lastKeyBuffer
*/
protected ITuple<E> consumeLookaheadTuple() {
assert current != -1;
lastKeyBuffer.reset().append(sourceTuple[current].getKey());
return super.consumeLookaheadTuple();
}
final public ITuple<E> seek(Object key) {
if (key == null)
throw new IllegalArgumentException();
return seek(getIndex().getIndexMetadata().getTupleSerializer()
.serializeKey(key));
}
/**
* Delegates the operation to the source view (correct deletion requires
* that a delete marker for the tuple is written onto first source index
* rather than deleting the tuple from the source from which it was
* materialized).
* <p>
* Note: You must specify {@link IRangeQuery#CURSOR} in order for
* {@link #remove()} to be supported.
*/
// @SuppressWarnings("unchecked")
@Override
public void remove() {
if (lastVisited == -1)
throw new IllegalStateException();
// /*
// * Use the last tuple actually visited for the source cursor that
// * materialized the tuple to be removed.
// */
// final ITuple tuple = ((ITupleCursor2)sourceIterator[lastVisited]).tuple();
//
// assert tuple != null;
//
// if(!tuple.getKeysRequested()) {
//
// throw new UnsupportedOperationException("KEYS not specified");
//
// }
//
// final byte[] key = tuple.getKey();
// the last key visited by the fused iterator.
final byte[] key = lastKeyBuffer.getKey();
/*
* Assuming that the ctor was given the FusedView, this will cause a
* deleted marker to be written on the first index in the fused view for
* this key. This is the correct semantics for deleting a tuple from a
* fused view.
*/
ndx.remove( key );
}
/**
* A copy of the key for the last tuple visited.
*/
final private KeyBuilder lastKeyBuffer = new KeyBuilder(0);
}