You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by am...@apache.org on 2018/02/13 00:09:52 UTC

[3/7] asterixdb git commit: [ASTERIXDB-2204][STO] Fix implementations and usages of IIndexCursor

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-rtree/src/main/java/org/apache/hyracks/storage/am/lsm/rtree/impls/TreeTupleSorter.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-rtree/src/main/java/org/apache/hyracks/storage/am/lsm/rtree/impls/TreeTupleSorter.java b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-rtree/src/main/java/org/apache/hyracks/storage/am/lsm/rtree/impls/TreeTupleSorter.java
index 6fe6b60..9ef305c 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-rtree/src/main/java/org/apache/hyracks/storage/am/lsm/rtree/impls/TreeTupleSorter.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-rtree/src/main/java/org/apache/hyracks/storage/am/lsm/rtree/impls/TreeTupleSorter.java
@@ -25,6 +25,7 @@ import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
 import org.apache.hyracks.storage.am.common.api.ITreeIndexCursor;
 import org.apache.hyracks.storage.am.common.api.ITreeIndexFrame;
 import org.apache.hyracks.storage.am.common.api.ITreeIndexTupleReference;
+import org.apache.hyracks.storage.common.EnforcedIndexCursor;
 import org.apache.hyracks.storage.common.ICursorInitialState;
 import org.apache.hyracks.storage.common.ISearchPredicate;
 import org.apache.hyracks.storage.common.MultiComparator;
@@ -32,7 +33,7 @@ import org.apache.hyracks.storage.common.buffercache.IBufferCache;
 import org.apache.hyracks.storage.common.buffercache.ICachedPage;
 import org.apache.hyracks.storage.common.file.BufferedFileHandle;
 
-public class TreeTupleSorter implements ITreeIndexCursor {
+public class TreeTupleSorter extends EnforcedIndexCursor implements ITreeIndexCursor {
     private final static int INITIAL_SIZE = 1000000;
     private int numTuples;
     private int currentTupleIndex;
@@ -62,13 +63,13 @@ public class TreeTupleSorter implements ITreeIndexCursor {
     }
 
     @Override
-    public void close() {
+    public void doClose() {
         numTuples = 0;
         currentTupleIndex = 0;
     }
 
     @Override
-    public boolean hasNext() throws HyracksDataException {
+    public boolean doHasNext() throws HyracksDataException {
         if (numTuples <= currentTupleIndex) {
             return false;
         }
@@ -87,12 +88,12 @@ public class TreeTupleSorter implements ITreeIndexCursor {
     }
 
     @Override
-    public void next() {
+    public void doNext() {
         currentTupleIndex++;
     }
 
     @Override
-    public ITupleReference getTuple() {
+    public ITupleReference doGetTuple() {
         return frameTuple1;
     }
 
@@ -180,34 +181,32 @@ public class TreeTupleSorter implements ITreeIndexCursor {
     private int compare(int[] tPointers, int tp1, int tp2i, int tp2j) throws HyracksDataException {
         int i1 = tPointers[tp1 * 2];
         int j1 = tPointers[tp1 * 2 + 1];
-
         int i2 = tp2i;
         int j2 = tp2j;
-
         ICachedPage node1 = bufferCache.pin(BufferedFileHandle.getDiskPageId(fileId, i1), false);
-        leafFrame1.setPage(node1);
-        ICachedPage node2 = bufferCache.pin(BufferedFileHandle.getDiskPageId(fileId, i2), false);
-        leafFrame2.setPage(node2);
-
         try {
-            frameTuple1.resetByTupleOffset(leafFrame1.getBuffer().array(), j1);
-            frameTuple2.resetByTupleOffset(leafFrame2.getBuffer().array(), j2);
-
-            return cmp.selectiveFieldCompare(frameTuple1, frameTuple2, comparatorFields);
-
+            leafFrame1.setPage(node1);
+            ICachedPage node2 = bufferCache.pin(BufferedFileHandle.getDiskPageId(fileId, i2), false);
+            try {
+                leafFrame2.setPage(node2);
+                frameTuple1.resetByTupleOffset(leafFrame1.getBuffer().array(), j1);
+                frameTuple2.resetByTupleOffset(leafFrame2.getBuffer().array(), j2);
+                return cmp.selectiveFieldCompare(frameTuple1, frameTuple2, comparatorFields);
+            } finally {
+                bufferCache.unpin(node2);
+            }
         } finally {
             bufferCache.unpin(node1);
-            bufferCache.unpin(node2);
         }
     }
 
     @Override
-    public void open(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
+    public void doOpen(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
         // do nothing
     }
 
     @Override
-    public void destroy() throws HyracksDataException {
+    public void doDestroy() throws HyracksDataException {
         // do nothing
     }
 

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTree.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTree.java b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTree.java
index 1e71b7f..5582075 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTree.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTree.java
@@ -769,6 +769,7 @@ public class RTree extends AbstractTreeIndex {
     public class RTreeAccessor implements ITreeIndexAccessor {
         private RTree rtree;
         private RTreeOpContext ctx;
+        private boolean destroyed = false;
 
         public RTreeAccessor(RTree rtree, IModificationOperationCallback modificationCallback,
                 ISearchOperationCallback searchCallback) {
@@ -895,6 +896,15 @@ public class RTree extends AbstractTreeIndex {
                 }
             }
         }
+
+        @Override
+        public void destroy() throws HyracksDataException {
+            if (destroyed) {
+                return;
+            }
+            destroyed = true;
+            ctx.destroy();
+        }
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeOpContext.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeOpContext.java b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeOpContext.java
index dc2e83b..46e6b22 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeOpContext.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeOpContext.java
@@ -62,6 +62,7 @@ public class RTreeOpContext implements IIndexOperationContext, IExtraPageBlockHe
     private IModificationOperationCallback modificationCallback;
 
     private PermutingTupleReference tupleWithNonIndexFields;
+    private boolean destroyed = false;
 
     public RTreeOpContext(IRTreeLeafFrame leafFrame, IRTreeInteriorFrame interiorFrame, IPageManager freePageManager,
             IBinaryComparatorFactory[] cmpFactories, IModificationOperationCallback modificationCallback) {
@@ -207,4 +208,15 @@ public class RTreeOpContext implements IIndexOperationContext, IExtraPageBlockHe
     public void resetNonIndexFieldsTuple(ITupleReference newValue) {
         tupleWithNonIndexFields.reset(newValue);
     }
+
+    @Override
+    public void destroy() throws HyracksDataException {
+        if (destroyed) {
+            return;
+        }
+        destroyed = true;
+        if (cursor != null) {
+            cursor.destroy();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeSearchCursor.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeSearchCursor.java b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeSearchCursor.java
index 3f4e00a..f85c044 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeSearchCursor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-rtree/src/main/java/org/apache/hyracks/storage/am/rtree/impls/RTreeSearchCursor.java
@@ -25,6 +25,7 @@ import org.apache.hyracks.storage.am.common.api.ITreeIndexCursor;
 import org.apache.hyracks.storage.am.common.api.ITreeIndexTupleReference;
 import org.apache.hyracks.storage.am.rtree.api.IRTreeInteriorFrame;
 import org.apache.hyracks.storage.am.rtree.api.IRTreeLeafFrame;
+import org.apache.hyracks.storage.common.EnforcedIndexCursor;
 import org.apache.hyracks.storage.common.ICursorInitialState;
 import org.apache.hyracks.storage.common.ISearchPredicate;
 import org.apache.hyracks.storage.common.MultiComparator;
@@ -32,7 +33,7 @@ import org.apache.hyracks.storage.common.buffercache.IBufferCache;
 import org.apache.hyracks.storage.common.buffercache.ICachedPage;
 import org.apache.hyracks.storage.common.file.BufferedFileHandle;
 
-public class RTreeSearchCursor implements ITreeIndexCursor {
+public class RTreeSearchCursor extends EnforcedIndexCursor implements ITreeIndexCursor {
 
     private int fileId = -1;
     private ICachedPage page = null;
@@ -62,7 +63,7 @@ public class RTreeSearchCursor implements ITreeIndexCursor {
     }
 
     @Override
-    public void destroy() throws HyracksDataException {
+    public void doDestroy() throws HyracksDataException {
         if (readLatched) {
             page.releaseReadLatch();
             bufferCache.unpin(page);
@@ -75,7 +76,7 @@ public class RTreeSearchCursor implements ITreeIndexCursor {
     }
 
     @Override
-    public ITupleReference getTuple() {
+    public ITupleReference doGetTuple() {
         return frameTuple;
     }
 
@@ -163,7 +164,7 @@ public class RTreeSearchCursor implements ITreeIndexCursor {
     }
 
     @Override
-    public boolean hasNext() throws HyracksDataException {
+    public boolean doHasNext() throws HyracksDataException {
         if (page == null) {
             return false;
         }
@@ -198,12 +199,12 @@ public class RTreeSearchCursor implements ITreeIndexCursor {
     }
 
     @Override
-    public void next() throws HyracksDataException {
+    public void doNext() throws HyracksDataException {
         tupleIndex = tupleIndexInc;
     }
 
     @Override
-    public void open(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
+    public void doOpen(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
         // in case open is called multiple times without closing
         if (this.page != null) {
             this.page.releaseReadLatch();
@@ -239,8 +240,8 @@ public class RTreeSearchCursor implements ITreeIndexCursor {
     }
 
     @Override
-    public void close() throws HyracksDataException {
-        destroy();
+    public void doClose() throws HyracksDataException {
+        doDestroy();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/EnforcedIndexCursor.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/EnforcedIndexCursor.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/EnforcedIndexCursor.java
index 19dfbab..929faff 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/EnforcedIndexCursor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/EnforcedIndexCursor.java
@@ -19,83 +19,117 @@
 
 package org.apache.hyracks.storage.common;
 
+import java.util.Arrays;
+
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
-public class EnforcedIndexCursor implements IIndexCursor {
+public abstract class EnforcedIndexCursor implements IIndexCursor {
     enum State {
         CLOSED,
         OPENED,
         DESTROYED
     }
 
+    private static final boolean STORE_TRACES = false;
+    private static final boolean ENFORCE_NEXT_HAS_NEXT = true;
+    private static final boolean ENFORCE_OPEN_CLOSE_DESTROY = true;
+    private static final Logger LOGGER = LogManager.getLogger();
+
     private State state = State.CLOSED;
+    private StackTraceElement[] openCallStack;
+    private StackTraceElement[] destroyCallStack;
 
     @Override
-    public void open(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
-        if (state != State.CLOSED) {
+    public final void open(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
+        if (ENFORCE_OPEN_CLOSE_DESTROY && state != State.CLOSED) {
+            if (STORE_TRACES && destroyCallStack != null) {
+                LOGGER.log(Level.WARN, "The cursor was destroyed in " + Arrays.toString(destroyCallStack));
+            }
             throw new IllegalStateException("Cannot open a cursor in the state " + state);
         }
         doOpen(initialState, searchPred);
         state = State.OPENED;
+        if (STORE_TRACES) {
+            openCallStack = new Throwable().getStackTrace();
+        }
     }
 
-    protected void doOpen(ICursorInitialState initialState, ISearchPredicate searchPred) throws HyracksDataException {
-        // Do nothing
-    }
+    protected abstract void doOpen(ICursorInitialState initialState, ISearchPredicate searchPred)
+            throws HyracksDataException;
 
     @Override
-    public boolean hasNext() throws HyracksDataException {
-        if (state != State.OPENED) {
+    public final boolean hasNext() throws HyracksDataException {
+        if (ENFORCE_NEXT_HAS_NEXT && state != State.OPENED) {
             throw new IllegalStateException("Cannot call hasNext() on a cursor in the state " + state);
         }
         return doHasNext();
     }
 
-    protected boolean doHasNext() throws HyracksDataException {
-        return false;
-    }
+    protected abstract boolean doHasNext() throws HyracksDataException;
 
     @Override
-    public void next() throws HyracksDataException {
-        if (state != State.OPENED) {
+    public final void next() throws HyracksDataException {
+        if (ENFORCE_NEXT_HAS_NEXT && state != State.OPENED) {
             throw new IllegalStateException("Cannot call next() on a cursor in the state " + state);
         }
         doNext();
     }
 
-    protected void doNext() throws HyracksDataException {
-        // Do nothing
-    }
+    protected abstract void doNext() throws HyracksDataException;
 
     @Override
-    public void destroy() throws HyracksDataException {
-        if (state != State.CLOSED) {
-            throw new IllegalStateException("Cannot destroy a cursor in the state " + state);
+    public final void destroy() throws HyracksDataException {
+        if (ENFORCE_OPEN_CLOSE_DESTROY) {
+            if (state == State.DESTROYED) {
+                LOGGER.log(Level.WARN,
+                        "multiple cursor.destroy() call in " + Arrays.toString(new Throwable().getStackTrace()));
+                return;
+            } else if (state != State.CLOSED) {
+                if (STORE_TRACES && openCallStack != null) {
+                    LOGGER.log(Level.WARN, "The cursor was opened in " + Arrays.toString(openCallStack));
+                }
+                throw new IllegalStateException("Cannot destroy a cursor in the state " + state);
+            }
         }
-        doDestroy();
         state = State.DESTROYED;
+        try {
+            doDestroy();
+        } finally {
+            if (ENFORCE_OPEN_CLOSE_DESTROY && STORE_TRACES) {
+                destroyCallStack = new Throwable().getStackTrace();
+            }
+        }
     }
 
-    protected void doDestroy() throws HyracksDataException {
-        // Do nothing
-    }
+    protected abstract void doDestroy() throws HyracksDataException;
 
     @Override
-    public void close() throws HyracksDataException {
-        if (state != State.OPENED) {
-            throw new IllegalStateException("Cannot close a cursor in the state " + state);
+    public final void close() throws HyracksDataException {
+        if (ENFORCE_OPEN_CLOSE_DESTROY) {
+            if (state == State.CLOSED) {
+                return;
+            } else if (state == State.DESTROYED) {
+                throw new IllegalStateException("Cannot close a cursor in the state " + state);
+            }
         }
-        doClose();
         state = State.CLOSED;
+        doClose();
     }
 
-    private void doClose() throws HyracksDataException {
-        // Do nothing
-    }
+    protected abstract void doClose() throws HyracksDataException;
 
     @Override
-    public ITupleReference getTuple() {
-        return null;
+    public final ITupleReference getTuple() {
+        if (state == State.OPENED) {
+            return doGetTuple();
+        } else {
+            return null;
+        }
     }
+
+    protected abstract ITupleReference doGetTuple();
 }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndex.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndex.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndex.java
index 19b4856..4c0012d 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndex.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndex.java
@@ -134,7 +134,7 @@ public interface IIndex {
     /**
      * @param fillFactor
      * @param verifyInput
-     * @throws IndexException
+     * @throws HyracksDataException
      */
     public IIndexBulkLoader createBulkLoader(float fillFactor, boolean verifyInput, long numElementsHint,
             boolean checkIfEmptyIndex) throws HyracksDataException;

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexAccessor.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexAccessor.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexAccessor.java
index 27ca8c8..382aea2 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexAccessor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexAccessor.java
@@ -19,6 +19,7 @@
 
 package org.apache.hyracks.storage.common;
 
+import org.apache.hyracks.api.dataflow.IDestroyable;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
 
@@ -28,7 +29,7 @@ import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
  * can concurrently operate on the same IIndex (i.e., the IIndex must allow
  * concurrent operations).
  */
-public interface IIndexAccessor {
+public interface IIndexAccessor extends IDestroyable {
     /**
      * Inserts the given tuple.
      *
@@ -36,11 +37,10 @@ public interface IIndexAccessor {
      *            Tuple to be inserted.
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
-     * @throws IndexException
      *             If an index-specific constraint is violated, e.g., the key
      *             already exists.
      */
-    public void insert(ITupleReference tuple) throws HyracksDataException;
+    void insert(ITupleReference tuple) throws HyracksDataException;
 
     /**
      * Updates the tuple in the index matching the given tuple with the new
@@ -51,10 +51,9 @@ public interface IIndexAccessor {
      *            tuples contents.
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
-     * @throws IndexException
      *             If there is no matching tuple in the index.
      */
-    public void update(ITupleReference tuple) throws HyracksDataException;
+    void update(ITupleReference tuple) throws HyracksDataException;
 
     /**
      * Deletes the tuple in the index matching the given tuple.
@@ -63,10 +62,9 @@ public interface IIndexAccessor {
      *            Tuple to be deleted.
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
-     * @throws IndexException
      *             If there is no matching tuple in the index.
      */
-    public void delete(ITupleReference tuple) throws HyracksDataException;
+    void delete(ITupleReference tuple) throws HyracksDataException;
 
     /**
      * This operation is only supported by indexes with the notion of a unique key.
@@ -77,29 +75,29 @@ public interface IIndexAccessor {
      *            Tuple to be deleted.
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
-     * @throws IndexException
      *             If there is no matching tuple in the index.
      *
      */
-    public void upsert(ITupleReference tuple) throws HyracksDataException;
+    void upsert(ITupleReference tuple) throws HyracksDataException;
 
     /**
      * Creates a cursor appropriate for passing into search().
      *
      */
-    public IIndexCursor createSearchCursor(boolean exclusive);
+    IIndexCursor createSearchCursor(boolean exclusive);
 
     /**
      * Open the given cursor for an index search using the given predicate as
      * search condition.
      *
-     * @param icursor
+     * Note: if this call returns successfully, then the cursor is open, otherwise it is not.
+     *
+     * @param cursor
      *            Cursor over the index entries satisfying searchPred.
      * @param searchPred
      *            Search condition.
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
-     * @throws IndexException
      */
-    public void search(IIndexCursor cursor, ISearchPredicate searchPred) throws HyracksDataException;
+    void search(IIndexCursor cursor, ISearchPredicate searchPred) throws HyracksDataException;
 }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexBulkLoader.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexBulkLoader.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexBulkLoader.java
index 5c4d3c0..fc54903 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexBulkLoader.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexBulkLoader.java
@@ -37,7 +37,6 @@ public interface IIndexBulkLoader {
     /**
      * Finalize the bulk loading operation in the given context.
      *
-     * @throws IndexException
      * @throws HyracksDataException
      *             If the BufferCache throws while un/pinning or un/latching.
      */
@@ -46,6 +45,9 @@ public interface IIndexBulkLoader {
     /**
      * Release all resources held by this bulkloader, with no guarantee of
      * persisted content.
+     *
+     * @throws HyracksDataException
+     *             If the operation was completed through end() invocation before abort is called
      */
     void abort() throws HyracksDataException;
 }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexCursor.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexCursor.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexCursor.java
index b561e25..f704921 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexCursor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/IIndexCursor.java
@@ -19,6 +19,7 @@
 
 package org.apache.hyracks.storage.common;
 
+import org.apache.hyracks.api.dataflow.IDestroyable;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
 
@@ -44,12 +45,12 @@ import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
  * <li>DESTROYED</li>
  * </ul>
  * When a cursor object is created, it is in the CLOSED state.
- * CLOSED: The only legal calls are open() --> OPENED, or destroy() --> DESTROYED
+ * CLOSED: Legal calls are open() --> OPENED, or destroy() --> DESTROYED, close() --> no effect
  * OPENED: The only legal calls are hasNext(), next(), or close() --> CLOSED.
  * DESTROYED: All calls are illegal.
  * Cursors must enforce the cursor state machine
  */
-public interface IIndexCursor {
+public interface IIndexCursor extends IDestroyable {
     /**
      * Opens the cursor
      * if open succeeds, close must be called.
@@ -76,15 +77,8 @@ public interface IIndexCursor {
     void next() throws HyracksDataException;
 
     /**
-     * Destroys the cursor allowing for release of resources.
-     * The cursor can't be used anymore after this call.
-     *
-     * @throws HyracksDataException
-     */
-    void destroy() throws HyracksDataException;
-
-    /**
-     * Close the cursor when done with it after a successful open
+     * Close the cursor. If the cursor is already closed then invoking this
+     * method has no effect.
      *
      * @throws HyracksDataException
      */

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/buffercache/VirtualPage.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/buffercache/VirtualPage.java b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/buffercache/VirtualPage.java
index be384e0..d7ec4e9 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/buffercache/VirtualPage.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-common/src/main/java/org/apache/hyracks/storage/common/buffercache/VirtualPage.java
@@ -141,8 +141,9 @@ public class VirtualPage implements ICachedPage {
     @Override
     public String toString() {
         StringBuilder str = new StringBuilder();
-        str.append("{\"class\":\"" + getClass().getSimpleName() + "\", \"readers\":" + getReadLatchCount()
-                + ",\"writers\":" + (isWriteLatched()));
+        str.append("{\"class\":\"").append(getClass().getSimpleName()).append("\", \"readers\":")
+                .append(getReadLatchCount()).append(",\"writers\":").append(isWriteLatched());
+        str.append(",\"next\":").append(next);
         str.append("}");
         return str.toString();
     }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/AbstractSearchOperationCallbackTest.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/AbstractSearchOperationCallbackTest.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/AbstractSearchOperationCallbackTest.java
index 368430c..6e7717d 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/AbstractSearchOperationCallbackTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/AbstractSearchOperationCallbackTest.java
@@ -110,24 +110,27 @@ public abstract class AbstractSearchOperationCallbackTest extends AbstractOperat
         public Boolean call() throws Exception {
             lock.lock();
             try {
-                if (!insertTaskStarted) {
-                    condition.await();
+                try {
+                    while (!insertTaskStarted) {
+                        condition.await();
+                    }
+                    // begin a search on [101, +inf), blocking on 101
+                    TupleUtils.createIntegerTuple(builder, tuple, 101);
+                    predicate.setLowKey(tuple, true);
+                    predicate.setHighKey(null, true);
+                    accessor.search(cursor, predicate);
+                    try {
+                        consumeIntTupleRange(101, 101, true, 101);
+                        // consume tuples [102, 152], blocking on 151
+                        consumeIntTupleRange(102, 151, true, 152);
+                        // consume tuples [153, 300]
+                        consumeIntTupleRange(153, 300, false, -1);
+                    } finally {
+                        cursor.close();
+                    }
+                } finally {
+                    cursor.destroy();
                 }
-
-                // begin a search on [101, +inf), blocking on 101
-                TupleUtils.createIntegerTuple(builder, tuple, 101);
-                predicate.setLowKey(tuple, true);
-                predicate.setHighKey(null, true);
-                accessor.search(cursor, predicate);
-                consumeIntTupleRange(101, 101, true, 101);
-
-                // consume tuples [102, 152], blocking on 151
-                consumeIntTupleRange(102, 151, true, 152);
-
-                // consume tuples [153, 300]
-                consumeIntTupleRange(153, 300, false, -1);
-
-                cursor.destroy();
             } finally {
                 lock.unlock();
             }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexExamplesTest.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexExamplesTest.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexExamplesTest.java
index 9ca3b59..80e443d 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexExamplesTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexExamplesTest.java
@@ -760,16 +760,20 @@ public abstract class OrderedIndexExamplesTest {
             LOGGER.info("Ordered Scan:");
         }
         IIndexCursor scanCursor = indexAccessor.createSearchCursor(false);
-        RangePredicate nullPred = new RangePredicate(null, null, true, true, null, null);
-        indexAccessor.search(scanCursor, nullPred);
         try {
-            while (scanCursor.hasNext()) {
-                scanCursor.next();
-                ITupleReference frameTuple = scanCursor.getTuple();
-                String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info(rec);
+            RangePredicate nullPred = new RangePredicate(null, null, true, true, null, null);
+            indexAccessor.search(scanCursor, nullPred);
+            try {
+                while (scanCursor.hasNext()) {
+                    scanCursor.next();
+                    ITupleReference frameTuple = scanCursor.getTuple();
+                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
+                    if (LOGGER.isInfoEnabled()) {
+                        LOGGER.info(rec);
+                    }
                 }
+            } finally {
+                scanCursor.close();
             }
         } finally {
             scanCursor.destroy();
@@ -784,15 +788,17 @@ public abstract class OrderedIndexExamplesTest {
             ITreeIndexAccessor treeIndexAccessor = (ITreeIndexAccessor) indexAccessor;
             TreeIndexDiskOrderScanCursor diskOrderCursor =
                     (TreeIndexDiskOrderScanCursor) treeIndexAccessor.createDiskOrderScanCursor();
-            treeIndexAccessor.diskOrderScan(diskOrderCursor);
             try {
-                while (diskOrderCursor.hasNext()) {
-                    diskOrderCursor.next();
-                    ITupleReference frameTuple = diskOrderCursor.getTuple();
-                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                    if (LOGGER.isInfoEnabled()) {
+                treeIndexAccessor.diskOrderScan(diskOrderCursor);
+                try {
+                    while (diskOrderCursor.hasNext()) {
+                        diskOrderCursor.next();
+                        ITupleReference frameTuple = diskOrderCursor.getTuple();
+                        String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
                         LOGGER.info(rec);
                     }
+                } finally {
+                    diskOrderCursor.close();
                 }
             } finally {
                 diskOrderCursor.destroy();
@@ -821,24 +827,28 @@ public abstract class OrderedIndexExamplesTest {
             LOGGER.info("Range-Search in: [ " + lowKeyString + ", " + highKeyString + "]");
         }
         IIndexCursor rangeCursor = indexAccessor.createSearchCursor(false);
-        MultiComparator lowKeySearchCmp = BTreeUtils.getSearchMultiComparator(cmpFactories, lowKey);
-        MultiComparator highKeySearchCmp = BTreeUtils.getSearchMultiComparator(cmpFactories, highKey);
-        RangePredicate rangePred;
-        if (minFilterTuple != null && maxFilterTuple != null) {
-            rangePred = new RangePredicate(lowKey, highKey, true, true, lowKeySearchCmp, highKeySearchCmp,
-                    minFilterTuple, maxFilterTuple);
-        } else {
-            rangePred = new RangePredicate(lowKey, highKey, true, true, lowKeySearchCmp, highKeySearchCmp);
-        }
-        indexAccessor.search(rangeCursor, rangePred);
         try {
-            while (rangeCursor.hasNext()) {
-                rangeCursor.next();
-                ITupleReference frameTuple = rangeCursor.getTuple();
-                String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info(rec);
+            MultiComparator lowKeySearchCmp = BTreeUtils.getSearchMultiComparator(cmpFactories, lowKey);
+            MultiComparator highKeySearchCmp = BTreeUtils.getSearchMultiComparator(cmpFactories, highKey);
+            RangePredicate rangePred;
+            if (minFilterTuple != null && maxFilterTuple != null) {
+                rangePred = new RangePredicate(lowKey, highKey, true, true, lowKeySearchCmp, highKeySearchCmp,
+                        minFilterTuple, maxFilterTuple);
+            } else {
+                rangePred = new RangePredicate(lowKey, highKey, true, true, lowKeySearchCmp, highKeySearchCmp);
+            }
+            indexAccessor.search(rangeCursor, rangePred);
+            try {
+                while (rangeCursor.hasNext()) {
+                    rangeCursor.next();
+                    ITupleReference frameTuple = rangeCursor.getTuple();
+                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
+                    if (LOGGER.isInfoEnabled()) {
+                        LOGGER.info(rec);
+                    }
                 }
+            } finally {
+                rangeCursor.close();
             }
         } finally {
             rangeCursor.destroy();

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexTestUtils.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexTestUtils.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexTestUtils.java
index bf3c8e5..d3dac3e 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexTestUtils.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/btree/OrderedIndexTestUtils.java
@@ -99,37 +99,42 @@ public class OrderedIndexTestUtils extends TreeIndexTestUtils {
         MultiComparator lowKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), lowKey);
         MultiComparator highKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), highKey);
         IIndexCursor searchCursor = ctx.getIndexAccessor().createSearchCursor(false);
-        RangePredicate rangePred =
-                new RangePredicate(lowKey, highKey, lowKeyInclusive, highKeyInclusive, lowKeyCmp, highKeyCmp);
-        ctx.getIndexAccessor().search(searchCursor, rangePred);
-        // Get the subset of elements from the expected set within given key
-        // range.
-        CheckTuple lowKeyCheck = createCheckTupleFromTuple(lowKey, ctx.getFieldSerdes(), lowKeyCmp.getKeyFieldCount());
-        CheckTuple highKeyCheck =
-                createCheckTupleFromTuple(highKey, ctx.getFieldSerdes(), highKeyCmp.getKeyFieldCount());
-        SortedSet<CheckTuple> expectedSubset = null;
-        if (lowKeyCmp.getKeyFieldCount() < ctx.getKeyFieldCount()
-                || highKeyCmp.getKeyFieldCount() < ctx.getKeyFieldCount()) {
-            // Searching on a key prefix (low key or high key or both).
-            expectedSubset =
-                    getPrefixExpectedSubset((TreeSet<CheckTuple>) ctx.getCheckTuples(), lowKeyCheck, highKeyCheck);
-        } else {
-            // Searching on all key fields.
-            expectedSubset = ((TreeSet<CheckTuple>) ctx.getCheckTuples()).subSet(lowKeyCheck, lowKeyInclusive,
-                    highKeyCheck, highKeyInclusive);
-        }
-        Iterator<CheckTuple> checkIter = expectedSubset.iterator();
-        int actualCount = 0;
         try {
-            while (searchCursor.hasNext()) {
-                if (!checkIter.hasNext()) {
-                    fail("Range search returned more answers than expected.\nExpected: " + expectedSubset.size());
+            RangePredicate rangePred =
+                    new RangePredicate(lowKey, highKey, lowKeyInclusive, highKeyInclusive, lowKeyCmp, highKeyCmp);
+            int actualCount = 0;
+            SortedSet<CheckTuple> expectedSubset = null;
+            ctx.getIndexAccessor().search(searchCursor, rangePred);
+            try {
+                // Get the subset of elements from the expected set within given key
+                // range.
+                CheckTuple lowKeyCheck =
+                        createCheckTupleFromTuple(lowKey, ctx.getFieldSerdes(), lowKeyCmp.getKeyFieldCount());
+                CheckTuple highKeyCheck =
+                        createCheckTupleFromTuple(highKey, ctx.getFieldSerdes(), highKeyCmp.getKeyFieldCount());
+                if (lowKeyCmp.getKeyFieldCount() < ctx.getKeyFieldCount()
+                        || highKeyCmp.getKeyFieldCount() < ctx.getKeyFieldCount()) {
+                    // Searching on a key prefix (low key or high key or both).
+                    expectedSubset = getPrefixExpectedSubset((TreeSet<CheckTuple>) ctx.getCheckTuples(), lowKeyCheck,
+                            highKeyCheck);
+                } else {
+                    // Searching on all key fields.
+                    expectedSubset = ((TreeSet<CheckTuple>) ctx.getCheckTuples()).subSet(lowKeyCheck, lowKeyInclusive,
+                            highKeyCheck, highKeyInclusive);
                 }
-                searchCursor.next();
-                CheckTuple expectedTuple = checkIter.next();
-                ITupleReference tuple = searchCursor.getTuple();
-                compareActualAndExpected(tuple, expectedTuple, ctx.getFieldSerdes());
-                actualCount++;
+                Iterator<CheckTuple> checkIter = expectedSubset.iterator();
+                while (searchCursor.hasNext()) {
+                    if (!checkIter.hasNext()) {
+                        fail("Range search returned more answers than expected.\nExpected: " + expectedSubset.size());
+                    }
+                    searchCursor.next();
+                    CheckTuple expectedTuple = checkIter.next();
+                    ITupleReference tuple = searchCursor.getTuple();
+                    compareActualAndExpected(tuple, expectedTuple, ctx.getFieldSerdes());
+                    actualCount++;
+                }
+            } finally {
+                searchCursor.close();
             }
             if (actualCount < expectedSubset.size()) {
                 fail("Range search returned fewer answers than expected.\nExpected: " + expectedSubset.size()
@@ -146,41 +151,43 @@ public class OrderedIndexTestUtils extends TreeIndexTestUtils {
         }
         OrderedIndexTestContext ctx = (OrderedIndexTestContext) ictx;
         IIndexCursor searchCursor = ctx.getIndexAccessor().createSearchCursor(false);
-
-        ArrayTupleBuilder lowKeyBuilder = new ArrayTupleBuilder(ctx.getKeyFieldCount());
-        ArrayTupleReference lowKey = new ArrayTupleReference();
-        ArrayTupleBuilder highKeyBuilder = new ArrayTupleBuilder(ctx.getKeyFieldCount());
-        ArrayTupleReference highKey = new ArrayTupleReference();
-        RangePredicate rangePred = new RangePredicate(lowKey, highKey, true, true, null, null);
-
-        // Iterate through expected tuples, and perform a point search in the
-        // BTree to verify the tuple can be reached.
-        for (CheckTuple checkTuple : ctx.getCheckTuples()) {
-            createTupleFromCheckTuple(checkTuple, lowKeyBuilder, lowKey, ctx.getFieldSerdes());
-            createTupleFromCheckTuple(checkTuple, highKeyBuilder, highKey, ctx.getFieldSerdes());
-            MultiComparator lowKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), lowKey);
-            MultiComparator highKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), highKey);
-
-            rangePred.setLowKey(lowKey, true);
-            rangePred.setHighKey(highKey, true);
-            rangePred.setLowKeyComparator(lowKeyCmp);
-            rangePred.setHighKeyComparator(highKeyCmp);
-
-            ctx.getIndexAccessor().search(searchCursor, rangePred);
-
-            try {
-                // We expect exactly one answer.
-                if (searchCursor.hasNext()) {
-                    searchCursor.next();
-                    ITupleReference tuple = searchCursor.getTuple();
-                    compareActualAndExpected(tuple, checkTuple, ctx.getFieldSerdes());
-                }
-                if (searchCursor.hasNext()) {
-                    fail("Point search returned more than one answer.");
+        try {
+            ArrayTupleBuilder lowKeyBuilder = new ArrayTupleBuilder(ctx.getKeyFieldCount());
+            ArrayTupleReference lowKey = new ArrayTupleReference();
+            ArrayTupleBuilder highKeyBuilder = new ArrayTupleBuilder(ctx.getKeyFieldCount());
+            ArrayTupleReference highKey = new ArrayTupleReference();
+            RangePredicate rangePred = new RangePredicate(lowKey, highKey, true, true, null, null);
+
+            // Iterate through expected tuples, and perform a point search in the
+            // BTree to verify the tuple can be reached.
+            for (CheckTuple checkTuple : ctx.getCheckTuples()) {
+                createTupleFromCheckTuple(checkTuple, lowKeyBuilder, lowKey, ctx.getFieldSerdes());
+                createTupleFromCheckTuple(checkTuple, highKeyBuilder, highKey, ctx.getFieldSerdes());
+                MultiComparator lowKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), lowKey);
+                MultiComparator highKeyCmp = BTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), highKey);
+
+                rangePred.setLowKey(lowKey, true);
+                rangePred.setHighKey(highKey, true);
+                rangePred.setLowKeyComparator(lowKeyCmp);
+                rangePred.setHighKeyComparator(highKeyCmp);
+
+                ctx.getIndexAccessor().search(searchCursor, rangePred);
+                try {
+                    // We expect exactly one answer.
+                    if (searchCursor.hasNext()) {
+                        searchCursor.next();
+                        ITupleReference tuple = searchCursor.getTuple();
+                        compareActualAndExpected(tuple, checkTuple, ctx.getFieldSerdes());
+                    }
+                    if (searchCursor.hasNext()) {
+                        fail("Point search returned more than one answer.");
+                    }
+                } finally {
+                    searchCursor.close();
                 }
-            } finally {
-                searchCursor.destroy();
             }
+        } finally {
+            searchCursor.destroy();
         }
     }
 
@@ -479,25 +486,20 @@ public class OrderedIndexTestUtils extends TreeIndexTestUtils {
     public void checkExpectedResults(IIndexCursor cursor, Collection checkTuples, ISerializerDeserializer[] fieldSerdes,
             int keyFieldCount, Iterator<CheckTuple> checkIter) throws Exception {
         int actualCount = 0;
-        try {
-            while (cursor.hasNext()) {
-                if (!checkIter.hasNext()) {
-                    fail("Ordered scan returned more answers than expected.\nExpected: " + checkTuples.size());
-                }
-                cursor.next();
-                CheckTuple expectedTuple = checkIter.next();
-                ITupleReference tuple = cursor.getTuple();
-                compareActualAndExpected(tuple, expectedTuple, fieldSerdes);
-                actualCount++;
-            }
-            if (actualCount < checkTuples.size()) {
-                fail("Ordered scan returned fewer answers than expected.\nExpected: " + checkTuples.size()
-                        + "\nActual  : " + actualCount);
+        while (cursor.hasNext()) {
+            if (!checkIter.hasNext()) {
+                fail("Ordered scan returned more answers than expected.\nExpected: " + checkTuples.size());
             }
-        } finally {
-            cursor.destroy();
+            cursor.next();
+            CheckTuple expectedTuple = checkIter.next();
+            ITupleReference tuple = cursor.getTuple();
+            compareActualAndExpected(tuple, expectedTuple, fieldSerdes);
+            actualCount++;
+        }
+        if (actualCount < checkTuples.size()) {
+            fail("Ordered scan returned fewer answers than expected.\nExpected: " + checkTuples.size() + "\nActual  : "
+                    + actualCount);
         }
-
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/AbstractIndexTestWorker.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/AbstractIndexTestWorker.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/AbstractIndexTestWorker.java
index 5248a22..b9123a5 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/AbstractIndexTestWorker.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/AbstractIndexTestWorker.java
@@ -68,12 +68,8 @@ public abstract class AbstractIndexTestWorker extends Thread implements ITreeInd
     }
 
     protected void consumeCursorTuples(IIndexCursor cursor) throws HyracksDataException {
-        try {
-            while (cursor.hasNext()) {
-                cursor.next();
-            }
-        } finally {
-            cursor.destroy();
+        while (cursor.hasNext()) {
+            cursor.next();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/TreeIndexTestUtils.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/TreeIndexTestUtils.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/TreeIndexTestUtils.java
index f0b01a2..a703b0b 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/TreeIndexTestUtils.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/common/TreeIndexTestUtils.java
@@ -44,7 +44,6 @@ import org.apache.hyracks.storage.am.common.api.ITreeIndexCursor;
 import org.apache.hyracks.storage.common.IIndexBulkLoader;
 import org.apache.hyracks.storage.common.IIndexCursor;
 import org.apache.hyracks.storage.common.ISearchPredicate;
-import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -123,10 +122,19 @@ public abstract class TreeIndexTestUtils {
             LOGGER.info("Testing Scan.");
         }
         IIndexCursor scanCursor = ctx.getIndexAccessor().createSearchCursor(false);
-        ISearchPredicate nullPred = createNullSearchPredicate();
-        ctx.getIndexAccessor().search(scanCursor, nullPred);
-        Iterator<CheckTuple> checkIter = ctx.getCheckTuples().iterator();
-        checkExpectedResults(scanCursor, ctx.getCheckTuples(), ctx.getFieldSerdes(), ctx.getKeyFieldCount(), checkIter);
+        try {
+            ISearchPredicate nullPred = createNullSearchPredicate();
+            ctx.getIndexAccessor().search(scanCursor, nullPred);
+            try {
+                Iterator<CheckTuple> checkIter = ctx.getCheckTuples().iterator();
+                checkExpectedResults(scanCursor, ctx.getCheckTuples(), ctx.getFieldSerdes(), ctx.getKeyFieldCount(),
+                        checkIter);
+            } finally {
+                scanCursor.close();
+            }
+        } finally {
+            scanCursor.destroy();
+        }
     }
 
     public void checkDiskOrderScan(IIndexTestContext ctx) throws Exception {
@@ -135,34 +143,29 @@ public abstract class TreeIndexTestUtils {
                 LOGGER.info("Testing Disk-Order Scan.");
             }
             ITreeIndexAccessor treeIndexAccessor = (ITreeIndexAccessor) ctx.getIndexAccessor();
-            ITreeIndexCursor diskOrderCursor = treeIndexAccessor.createDiskOrderScanCursor();
-            treeIndexAccessor.diskOrderScan(diskOrderCursor);
-            int actualCount = 0;
             try {
-                while (diskOrderCursor.hasNext()) {
-                    diskOrderCursor.next();
-                    ITupleReference tuple = diskOrderCursor.getTuple();
-                    CheckTuple checkTuple =
-                            createCheckTupleFromTuple(tuple, ctx.getFieldSerdes(), ctx.getKeyFieldCount());
-                    if (!checkDiskOrderScanResult(tuple, checkTuple, ctx)) {
-                        fail("Disk-order scan returned unexpected answer: " + checkTuple.toString());
-                    }
-                    actualCount++;
-                }
-                if (actualCount < ctx.getCheckTuples().size()) {
-                    fail("Disk-order scan returned fewer answers than expected.\nExpected: "
-                            + ctx.getCheckTuples().size() + "\nActual  : " + actualCount);
-                }
-                if (actualCount > ctx.getCheckTuples().size()) {
-                    fail("Disk-order scan returned more answers than expected.\nExpected: "
-                            + ctx.getCheckTuples().size() + "\nActual  : " + actualCount);
-                }
-            } finally {
+                ITreeIndexCursor diskOrderCursor = treeIndexAccessor.createDiskOrderScanCursor();
                 try {
+                    int actualCount = 0;
+                    treeIndexAccessor.diskOrderScan(diskOrderCursor);
+                    try {
+                        actualCount = scan(ctx, diskOrderCursor);
+                    } finally {
+                        diskOrderCursor.close();
+                    }
+                    if (actualCount < ctx.getCheckTuples().size()) {
+                        fail("Disk-order scan returned fewer answers than expected.\nExpected: "
+                                + ctx.getCheckTuples().size() + "\nActual  : " + actualCount);
+                    }
+                    if (actualCount > ctx.getCheckTuples().size()) {
+                        fail("Disk-order scan returned more answers than expected.\nExpected: "
+                                + ctx.getCheckTuples().size() + "\nActual  : " + actualCount);
+                    }
+                } finally {
                     diskOrderCursor.destroy();
-                } catch (Exception ex) {
-                    LOGGER.log(Level.WARN, "Error during scan cursor close", ex);
                 }
+            } finally {
+                treeIndexAccessor.destroy();
             }
         } catch (UnsupportedOperationException e) {
             // Ignore exception because some indexes, e.g. the LSMTrees, don't
@@ -179,7 +182,19 @@ public abstract class TreeIndexTestUtils {
         }
     }
 
-    @SuppressWarnings("unchecked")
+    private int scan(IIndexTestContext ctx, ITreeIndexCursor diskOrderCursor) throws HyracksDataException {
+        int actualCount = 0;
+        while (diskOrderCursor.hasNext()) {
+            diskOrderCursor.next();
+            ITupleReference tuple = diskOrderCursor.getTuple();
+            CheckTuple checkTuple = createCheckTupleFromTuple(tuple, ctx.getFieldSerdes(), ctx.getKeyFieldCount());
+            if (!checkDiskOrderScanResult(tuple, checkTuple, ctx)) {
+                fail("Disk-order scan returned unexpected answer: " + checkTuple.toString());
+            }
+            actualCount++;
+        }
+        return actualCount;
+    }
 
     public Pair<ITupleReference, ITupleReference> insertIntTuples(IIndexTestContext ctx, int numTuples, Random rnd)
             throws Exception {

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/AbstractRTreeExamplesTest.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/AbstractRTreeExamplesTest.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/AbstractRTreeExamplesTest.java
index a36acf0..6505675 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/AbstractRTreeExamplesTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/AbstractRTreeExamplesTest.java
@@ -846,16 +846,20 @@ public abstract class AbstractRTreeExamplesTest {
             LOGGER.info("Scan:");
         }
         IIndexCursor scanCursor = indexAccessor.createSearchCursor(false);
-        SearchPredicate nullPred = new SearchPredicate(null, null);
-        indexAccessor.search(scanCursor, nullPred);
         try {
-            while (scanCursor.hasNext()) {
-                scanCursor.next();
-                ITupleReference frameTuple = scanCursor.getTuple();
-                String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info(rec);
+            SearchPredicate nullPred = new SearchPredicate(null, null);
+            indexAccessor.search(scanCursor, nullPred);
+            try {
+                while (scanCursor.hasNext()) {
+                    scanCursor.next();
+                    ITupleReference frameTuple = scanCursor.getTuple();
+                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
+                    if (LOGGER.isInfoEnabled()) {
+                        LOGGER.info(rec);
+                    }
                 }
+            } finally {
+                scanCursor.close();
             }
         } finally {
             scanCursor.destroy();
@@ -870,15 +874,17 @@ public abstract class AbstractRTreeExamplesTest {
             ITreeIndexAccessor treeIndexAccessor = (ITreeIndexAccessor) indexAccessor;
             TreeIndexDiskOrderScanCursor diskOrderCursor =
                     (TreeIndexDiskOrderScanCursor) treeIndexAccessor.createDiskOrderScanCursor();
-            treeIndexAccessor.diskOrderScan(diskOrderCursor);
             try {
-                while (diskOrderCursor.hasNext()) {
-                    diskOrderCursor.next();
-                    ITupleReference frameTuple = diskOrderCursor.getTuple();
-                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                    if (LOGGER.isInfoEnabled()) {
+                treeIndexAccessor.diskOrderScan(diskOrderCursor);
+                try {
+                    while (diskOrderCursor.hasNext()) {
+                        diskOrderCursor.next();
+                        ITupleReference frameTuple = diskOrderCursor.getTuple();
+                        String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
                         LOGGER.info(rec);
                     }
+                } finally {
+                    diskOrderCursor.close();
                 }
             } finally {
                 diskOrderCursor.destroy();
@@ -905,25 +911,27 @@ public abstract class AbstractRTreeExamplesTest {
             String kString = TupleUtils.printTuple(key, fieldSerdes);
             LOGGER.info("Range-Search using key: " + kString);
         }
-        IIndexCursor rangeCursor = indexAccessor.createSearchCursor(false);
         MultiComparator cmp = RTreeUtils.getSearchMultiComparator(cmpFactories, key);
-
         SearchPredicate rangePred;
         if (minFilterTuple != null && maxFilterTuple != null) {
             rangePred = new SearchPredicate(key, cmp, minFilterTuple, maxFilterTuple);
         } else {
             rangePred = new SearchPredicate(key, cmp);
         }
-
-        indexAccessor.search(rangeCursor, rangePred);
+        IIndexCursor rangeCursor = indexAccessor.createSearchCursor(false);
         try {
-            while (rangeCursor.hasNext()) {
-                rangeCursor.next();
-                ITupleReference frameTuple = rangeCursor.getTuple();
-                String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
-                if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info(rec);
+            indexAccessor.search(rangeCursor, rangePred);
+            try {
+                while (rangeCursor.hasNext()) {
+                    rangeCursor.next();
+                    ITupleReference frameTuple = rangeCursor.getTuple();
+                    String rec = TupleUtils.printTuple(frameTuple, fieldSerdes);
+                    if (LOGGER.isInfoEnabled()) {
+                        LOGGER.info(rec);
+                    }
                 }
+            } finally {
+                rangeCursor.close();
             }
         } finally {
             rangeCursor.destroy();

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/RTreeTestUtils.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/RTreeTestUtils.java b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/RTreeTestUtils.java
index f48da3a..165bf43 100644
--- a/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/RTreeTestUtils.java
+++ b/hyracks-fullstack/hyracks/hyracks-test-support/src/main/java/org/apache/hyracks/storage/am/rtree/RTreeTestUtils.java
@@ -72,18 +72,25 @@ public class RTreeTestUtils extends TreeIndexTestUtils {
         MultiComparator cmp = RTreeUtils.getSearchMultiComparator(ctx.getComparatorFactories(), key);
 
         IIndexCursor searchCursor = ctx.getIndexAccessor().createSearchCursor(false);
-        SearchPredicate searchPred = new SearchPredicate(key, cmp);
-        ctx.getIndexAccessor().search(searchCursor, searchPred);
-
-        // Get the subset of elements from the expected set within given key
-        // range.
-        RTreeCheckTuple keyCheck =
-                (RTreeCheckTuple) createCheckTupleFromTuple(key, ctx.getFieldSerdes(), cmp.getKeyFieldCount());
+        try {
+            SearchPredicate searchPred = new SearchPredicate(key, cmp);
+            ctx.getIndexAccessor().search(searchCursor, searchPred);
+            try {
+                // Get the subset of elements from the expected set within given key
+                // range.
+                RTreeCheckTuple keyCheck =
+                        (RTreeCheckTuple) createCheckTupleFromTuple(key, ctx.getFieldSerdes(), cmp.getKeyFieldCount());
 
-        HashMultiSet<RTreeCheckTuple> expectedResult = null;
+                HashMultiSet<RTreeCheckTuple> expectedResult = null;
 
-        expectedResult = getRangeSearchExpectedResults(ctx.getCheckTuples(), keyCheck);
-        checkExpectedResults(searchCursor, expectedResult, ctx.getFieldSerdes(), ctx.getKeyFieldCount(), null);
+                expectedResult = getRangeSearchExpectedResults(ctx.getCheckTuples(), keyCheck);
+                checkExpectedResults(searchCursor, expectedResult, ctx.getFieldSerdes(), ctx.getKeyFieldCount(), null);
+            } finally {
+                searchCursor.close();
+            }
+        } finally {
+            searchCursor.destroy();
+        }
     }
 
     @SuppressWarnings("unchecked")
@@ -176,27 +183,22 @@ public class RTreeTestUtils extends TreeIndexTestUtils {
     public void checkExpectedResults(IIndexCursor cursor, Collection checkTuples, ISerializerDeserializer[] fieldSerdes,
             int keyFieldCount, Iterator<CheckTuple> checkIter) throws Exception {
         int actualCount = 0;
-        try {
-            while (cursor.hasNext()) {
-                cursor.next();
-                ITupleReference tuple = cursor.getTuple();
-                RTreeCheckTuple checkTuple =
-                        (RTreeCheckTuple) createCheckTupleFromTuple(tuple, fieldSerdes, keyFieldCount);
-                if (!checkTuples.contains(checkTuple)) {
-                    fail("Scan or range search returned unexpected answer: " + checkTuple.toString());
-                }
-                actualCount++;
-            }
-            if (actualCount < checkTuples.size()) {
-                fail("Scan or range search returned fewer answers than expected.\nExpected: " + checkTuples.size()
-                        + "\nActual  : " + actualCount);
-            }
-            if (actualCount > checkTuples.size()) {
-                fail("Scan or range search returned more answers than expected.\nExpected: " + checkTuples.size()
-                        + "\nActual  : " + actualCount);
+        while (cursor.hasNext()) {
+            cursor.next();
+            ITupleReference tuple = cursor.getTuple();
+            RTreeCheckTuple checkTuple = (RTreeCheckTuple) createCheckTupleFromTuple(tuple, fieldSerdes, keyFieldCount);
+            if (!checkTuples.contains(checkTuple)) {
+                fail("Scan or range search returned unexpected answer: " + checkTuple.toString());
             }
-        } finally {
-            cursor.destroy();
+            actualCount++;
+        }
+        if (actualCount < checkTuples.size()) {
+            fail("Scan or range search returned fewer answers than expected.\nExpected: " + checkTuples.size()
+                    + "\nActual  : " + actualCount);
+        }
+        if (actualCount > checkTuples.size()) {
+            fail("Scan or range search returned more answers than expected.\nExpected: " + checkTuples.size()
+                    + "\nActual  : " + actualCount);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
index b618834..45d6989 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
@@ -67,6 +67,13 @@
     </dependency>
     <dependency>
       <groupId>org.apache.hyracks</groupId>
+      <artifactId>hyracks-storage-am-common</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hyracks</groupId>
       <artifactId>hyracks-api</artifactId>
       <version>${project.version}</version>
     </dependency>

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java
new file mode 100644
index 0000000..291ab8a
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree;
+import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.IIndexCursor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class BTreeCountingCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static BTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new BTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        BTreeSearchCursorTest.staticInsertBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        return btree.createAccessor(
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE));
+    }
+
+    @Override
+    protected IIndexCursor createCursor(IIndexAccessor accessor) {
+        return ((BTreeAccessor) accessor).createCountingSearchCursor();
+    }
+}

http://git-wip-us.apache.org/repos/asf/asterixdb/blob/4ff6a36d/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java
----------------------------------------------------------------------
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java
new file mode 100644
index 0000000..73a9bb6
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class BTreeRangeSearchCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static BTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new BTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        BTreeSearchCursorTest.staticInsertBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        IndexAccessParameters actx =
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
+        return btree.createAccessor(actx);
+    }
+}