@Override
  public void performOp(ITupleReference tuple, TestOperation op)
      throws HyracksDataException, IndexException {
    LSMBTreeAccessor accessor = (LSMBTreeAccessor) indexAccessor;
    IIndexCursor searchCursor = accessor.createSearchCursor(false);
    MultiComparator cmp = accessor.getMultiComparator();
    RangePredicate rangePred = new RangePredicate(tuple, tuple, true, true, cmp, cmp);

    switch (op) {
      case INSERT:
        try {
          accessor.insert(tuple);
        } catch (TreeIndexDuplicateKeyException e) {
          // Ignore duplicate keys, since we get random tuples.
        }
        break;

      case DELETE:
        // Create a tuple reference with only key fields.
        deleteTb.reset();
        for (int i = 0; i < numKeyFields; i++) {
          deleteTb.addField(tuple.getFieldData(i), tuple.getFieldStart(i), tuple.getFieldLength(i));
        }
        deleteTuple.reset(deleteTb.getFieldEndOffsets(), deleteTb.getByteArray());
        try {
          accessor.delete(deleteTuple);
        } catch (TreeIndexNonExistentKeyException e) {
          // Ignore non-existant keys, since we get random tuples.
        }
        break;

      case UPDATE:
        try {
          accessor.update(tuple);
        } catch (TreeIndexNonExistentKeyException e) {
          // Ignore non-existant keys, since we get random tuples.
        } catch (BTreeNotUpdateableException e) {
          // Ignore not updateable exception due to numKeys == numFields.
        }
        break;

      case POINT_SEARCH:
        searchCursor.reset();
        rangePred.setLowKey(tuple, true);
        rangePred.setHighKey(tuple, true);
        accessor.search(searchCursor, rangePred);
        consumeCursorTuples(searchCursor);
        break;

      case SCAN:
        searchCursor.reset();
        rangePred.setLowKey(null, true);
        rangePred.setHighKey(null, true);
        accessor.search(searchCursor, rangePred);
        consumeCursorTuples(searchCursor);
        break;

      case MERGE:
        accessor.scheduleMerge(NoOpIOOperationCallback.INSTANCE, lsmBTree.getImmutableComponents());
        break;

      default:
        throw new HyracksDataException("Op " + op.toString() + " not supported.");
    }
  }
  @Override
  public boolean hasNext() throws HyracksDataException, IndexException {
    if (nextHasBeenCalled) {
      return false;
    } else if (foundTuple) {
      return true;
    }
    boolean reconciled = false;
    for (int i = 0; i < numBTrees; ++i) {
      btreeAccessors[i].search(rangeCursors[i], predicate);
      if (rangeCursors[i].hasNext()) {
        rangeCursors[i].next();
        // We use the predicate's to lock the key instead of the tuple that we get from cursor to
        // avoid copying the tuple when we do the "unlatch dance"
        if (reconciled || searchCallback.proceed(predicate.getLowKey())) {
          // if proceed is successful, then there's no need for doing the "unlatch dance"
          if (((ILSMTreeTupleReference) rangeCursors[i].getTuple()).isAntimatter()) {
            searchCallback.cancel(predicate.getLowKey());
            rangeCursors[i].close();
            return false;
          } else {
            frameTuple = rangeCursors[i].getTuple();
            foundTuple = true;
            return true;
          }
        }
        if (i == 0 && includeMutableComponent) {
          // unlatch/unpin
          rangeCursors[i].reset();
          searchCallback.reconcile(predicate.getLowKey());
          reconciled = true;

          // retraverse
          btreeAccessors[0].search(rangeCursors[i], predicate);
          searchCallback.complete(predicate.getLowKey());
          if (rangeCursors[i].hasNext()) {
            rangeCursors[i].next();
            if (((ILSMTreeTupleReference) rangeCursors[i].getTuple()).isAntimatter()) {
              searchCallback.cancel(predicate.getLowKey());
              rangeCursors[i].close();
              return false;
            } else {
              frameTuple = rangeCursors[i].getTuple();
              foundTuple = true;
              return true;
            }
          } else {
            rangeCursors[i].close();
          }
        } else {
          frameTuple = rangeCursors[i].getTuple();
          searchCallback.reconcile(frameTuple);
          searchCallback.complete(frameTuple);
          foundTuple = true;
          return true;
        }
      } else {
        rangeCursors[i].close();
      }
    }
    return false;
  }