com.bigdata.btree.view.FusedTupleIterator Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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
*/
package com.bigdata.btree.view;
import java.util.NoSuchElementException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.bigdata.btree.DelegateTuple;
import com.bigdata.btree.IRangeQuery;
import com.bigdata.btree.ITuple;
import com.bigdata.btree.ITupleIterator;
import com.bigdata.util.BytesUtil;
/**
*
* An aggregate iterator view of the one or more source {@link ITupleIterator}s.
*
*
* @see FusedView#rangeIterator(byte[], byte[], int, int, com.bigdata.btree.filter.IFilterConstructor)
*
* @author Bryan Thompson
* @version $Id$
*/
public class FusedTupleIterator, E> implements
ITupleIterator {
protected static final Logger log = Logger.getLogger(FusedTupleIterator.class);
/**
* True iff the {@link #log} level is INFO or less.
*/
final protected boolean INFO = log.getEffectiveLevel().toInt() <= Level.INFO
.toInt();
/**
* True iff the {@link #log} level is DEBUG or less.
*/
final protected boolean DEBUG = log.getEffectiveLevel().toInt() <= Level.DEBUG
.toInt();
/**
* The flags specified to the ctor.
*/
protected final int flags;
/**
* True iff {@link IRangeQuery#DELETED} semantics will be applied (that is,
* true if the caller wants to see the deleted tuples).
*/
protected final boolean deleted;
/**
* The #of source iterators.
*/
protected final int n;
/**
* The source iterators in the order given to the ctor.
*/
protected final I[] sourceIterator;
/**
* The current {@link ITuple} from each source and null
if we
* need to get another {@link ITuple} from that source. The value for a
* source iterator that has been exhausted will remain null
.
* When all entries in this array are null
there are no more
* {@link ITuple}s to be visited and we are done.
*
* Note: We process the iterators in the order given. Unless
* {@link IRangeQuery#DELETED} are being materialized we will only visit the
* {@link ITuple} for the first iterator having a entry for that key. This
* is achieved by setting the elements in this array to null
* for any iterator having a {@link ITuple} for the same key.
*/
protected final ITuple[] sourceTuple;
// /**
// * true
iff {@link IRangeQuery#REVERSE} was specified for the
// * source iterator. When {@link IRangeQuery#REVERSE} was specified then the
// * source iterators will all use reverse traversal and we change the sense
// * of the comparison for the keys so that we impose a total descending key
// * order rather than a total ascending key order.
// */
// protected final boolean reverseScan;
/**
* Index into {@link #sourceIterator} and {@link #sourceTuple} of the iterator whose
* tuple will be returned next -or- -1
if we need to choose the
* next {@link ITuple} to be visited.
*/
protected int current = -1;
/**
* The index into {@link #sourceIterator} of the iterator whose tuple was last
* returned by {@link #next()}.
*/
protected int lastVisited = -1;
/**
* Create an {@link ITupleIterator} reading from an ordered set of source
* {@link ITupleIterator}s. The order of the source iterators is important.
* The first matching {@link ITuple} for a key will be the {@link ITuple}
* that gets returned. Other {@link ITuple}s for the same key will be from
* source iterators later in the precedence order will be silently skipped.
*
* @param flags
* The flags specified for the source iterators (it is up to the
* caller to make sure that the same flags were used for all
* iterators).
* @param deleted
* false
unless you want to see the deleted tuples
* in your application.
* @param sourceIterators
* Each source iterator MUST specify {@link IRangeQuery#DELETED}.
* This is NOT optional. The {@link IRangeQuery#DELETED} is
* required for the fused view iterator to recognize a deleted
* index entry and discard a historical undeleted entry later in
* the predence order for the view.
*/
@SuppressWarnings("unchecked")
public FusedTupleIterator(final int flags, final boolean deleted,
final I[] sourceIterators) {
assert sourceIterators != null;
assert sourceIterators.length > 0;
this.flags = flags;
this.n = sourceIterators.length;
// iff the aggregate iterator should visit deleted entries.
this.deleted = deleted;
for (int i = 0; i < n; i++) {
assert sourceIterators[i] != null;
}
this.sourceIterator = sourceIterators;
sourceTuple = new ITuple[n];
// reverseScan = (flags & IRangeQuery.REVERSE) != 0;
if (INFO)
log.info("nsources=" + n + ", deleted=" + deleted);//+", reverseScan="+reverseScan);
}
public boolean hasNext() {
/*
* 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].hasNext()) {
sourceTuple[i] = sourceIterator[i].next();
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
* LTE all the others (or GTE if [reverseScan == true]). This is the
* next 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 LT the current key.
*
* Note: This test MUST be strictly LT since LTE
* 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 next() {
if (!hasNext())
throw new NoSuchElementException();
return consumeLookaheadTuple();
}
/**
* {@link ITuple} revealing a tuple selected from a {@link FusedView} by a
* {@link FusedTupleIterator}.
*
* @author Bryan Thompson
* @version $Id$
* @param
*/
private static class FusedTuple extends DelegateTuple {
private final int sourceIndex;
private final long nvisited;
/**
* Note: Not whether or not this tuple is deleted but rather whether or
* not the {@link FusedTupleIterator} is visiting deleted tuples.
*
* @see FusedTupleIterator#deleted
*/
private final boolean deleted;
public FusedTuple(final ITuple delegate, final int sourceIndex,
final long nvisited, final boolean deleted) {
super(delegate);
this.sourceIndex = sourceIndex;
this.nvisited = nvisited;
this.deleted = deleted;
}
/**
* Turn off the deleted flag unless {@link IRangeQuery#DELETED} was
* specified for the {@link FusedTupleIterator} view.
*/
public int flags() {
int flags = super.flags();
if (!deleted) {
flags &= ~IRangeQuery.DELETED;
}
return flags;
}
public int getSourceIndex() {
return sourceIndex;
}
/** return the total visited count. */
@Override
public long getVisitCount() {
return nvisited;
}
}
/**
* Consume the {@link #current} source {@link ITuple}.
*
* @return The {@link #current} tuple.
*/
protected ITuple consumeLookaheadTuple() {
final long nvisited = this.nvisited++;
// save index of the iterator whose tuple is to be visited.
final int sourceIndex = lastVisited = current;
final ITuple tuple = new FusedTuple(sourceTuple[current],
sourceIndex, nvisited, deleted);
// clear tuples from other sources having the same key as the current tuple.
clearCurrent();
if(INFO) {
log.info("returning: tuple=" + tuple);
}
return tuple;
}
/**
*
* Clear tuples from other sources having the same key as the current tuple.
*
*
* If any source has the same key then we clear it's tuple since we have
* already returned a tuple for that key. This is necessary in order for the
* aggregate iterator to skip over additional tuples in other source streams
* once we identify a source stream having a tuple for a given key.
*
*
* @todo if we wanted to see duplicate tuples when reading from sources that
* are NOT a {@link FusedView} then an additional flag could be
* introduced to the ctor and this method could be modified to NOT
* skip over the tuples whose key is EQ to the current key.
*/
protected void clearCurrent() {
assert current != -1;
final byte[] key = sourceTuple[current].getKey();
for (int i = current + 1; i < n; i++) {
if (sourceTuple[i] == null) {
// this iterator is exhausted.
continue;
}
final byte[] tmp = sourceTuple[i].getKey();
final int ret = BytesUtil.compareBytes(key, tmp);
// if (ret < 0)
// throw new AssertionError();
if (ret == 0) {
// discard tuple.
sourceTuple[i] = null;
}
}
// clear the tuple that we are returning so that we will read another from that source.
sourceTuple[current] = null;
// clear so that we will look again.
current = -1;
}
/**
* The #of tuples visited so far.
*/
private long nvisited = 0L;
/**
* Operation is not supported.
*
* Note: Remove is not supported at this level. Instead you must use a
* {@link FusedTupleCursor}. This is handled automatically by
* {@link FusedView#rangeIterator(byte[], byte[], int, int, com.bigdata.btree.filter.IFilterConstructor)}.
*
* @throws UnsupportedOperationException
* always.
*/
public void remove() {
throw new UnsupportedOperationException();
}
}