[NO ISSUE][STO] Only re-use compatible accessors in LSM Cursors

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Currently, when a search operation starts, using a previously
  used LSMCursor, and the number of components didn't change, we
  re-use cursors and accessors.
- However, we didn't check the type of the underlying component
  and since disk cursors/accessors are incompatible with memory
  component trees, we end up getting ClassCastException.
- This change checks for compatibility. If the component/cursor
  pair are incompatible, the cursor and accessor are destroyed
  and new ones are created.

Change-Id: I0fe224af1049b53c8cc37448ee8bfa2ccd780564
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2608
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Luo Chen <cluo8@uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreePointSearchCursor.java b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreePointSearchCursor.java
index 1209e17..3520e3a 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreePointSearchCursor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreePointSearchCursor.java
@@ -22,6 +22,7 @@
 import java.util.List;
 
 import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.util.CleanupUtils;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
 import org.apache.hyracks.storage.am.bloomfilter.impls.BloomFilter;
 import org.apache.hyracks.storage.am.btree.impls.BTree;
@@ -162,7 +163,16 @@
         searchCallback = lsmInitialState.getSearchOperationCallback();
         predicate = (RangePredicate) lsmInitialState.getSearchPredicate();
         numBTrees = operationalComponents.size();
-        if (btreeCursors == null || btreeCursors.length != numBTrees) {
+        if (btreeCursors != null && btreeCursors.length != numBTrees) {
+            Throwable failure = CleanupUtils.destroy(null, btreeCursors);
+            btreeCursors = null;
+            failure = CleanupUtils.destroy(failure, btreeAccessors);
+            btreeAccessors = null;
+            if (failure != null) {
+                throw HyracksDataException.create(failure);
+            }
+        }
+        if (btreeCursors == null) {
             // object creation: should be relatively low
             btreeCursors = new ITreeIndexCursor[numBTrees];
             btreeAccessors = new BTreeAccessor[numBTrees];
@@ -175,8 +185,13 @@
             BTree btree = (BTree) component.getIndex();
             if (component.getType() == LSMComponentType.MEMORY) {
                 includeMutableComponent = true;
-                bloomFilters[i] = null;
+                if (bloomFilters[i] != null) {
+                    destroyAndNullifyCursorAtIndex(i);
+                }
             } else {
+                if (bloomFilters[i] == null) {
+                    destroyAndNullifyCursorAtIndex(i);
+                }
                 bloomFilters[i] = ((LSMBTreeWithBloomFilterDiskComponent) component).getBloomFilter();
             }
 
@@ -193,6 +208,18 @@
         foundTuple = false;
     }
 
+    private void destroyAndNullifyCursorAtIndex(int i) throws HyracksDataException {
+        // component at location i was a disk component before, and is now a memory component, or vise versa
+        bloomFilters[i] = null;
+        Throwable failure = CleanupUtils.destroy(null, btreeCursors[i]);
+        btreeCursors[i] = null;
+        failure = CleanupUtils.destroy(failure, btreeAccessors[i]);
+        btreeAccessors[i] = null;
+        if (failure != null) {
+            throw HyracksDataException.create(failure);
+        }
+    }
+
     @Override
     public void doNext() throws HyracksDataException {
         nextHasBeenCalled = true;
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreeRangeSearchCursor.java b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreeRangeSearchCursor.java
index bfda985..a675047 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreeRangeSearchCursor.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-lsm-btree/src/main/java/org/apache/hyracks/storage/am/lsm/btree/impls/LSMBTreeRangeSearchCursor.java
@@ -47,10 +47,9 @@
 public class LSMBTreeRangeSearchCursor extends LSMIndexSearchCursor {
     private final ArrayTupleReference copyTuple;
     private final RangePredicate reusablePred;
-
     private ISearchOperationCallback searchCallback;
-
     private BTreeAccessor[] btreeAccessors;
+    private boolean[] isMemoryComponent;
     private ArrayTupleBuilder tupleBuilder;
     private boolean canCallProceed = true;
     private boolean resultOfSearchCallbackProceed = false;
@@ -340,15 +339,19 @@
             // object creation: should be relatively low
             rangeCursors = new IIndexCursor[numBTrees];
             btreeAccessors = new BTreeAccessor[numBTrees];
+            isMemoryComponent = new boolean[numBTrees];
         } else if (rangeCursors.length != numBTrees) {
             // should destroy first
             Throwable failure = CleanupUtils.destroy(null, btreeAccessors);
+            btreeAccessors = null;
             failure = CleanupUtils.destroy(failure, rangeCursors);
+            rangeCursors = null;
             if (failure != null) {
                 throw HyracksDataException.create(failure);
             }
             rangeCursors = new IIndexCursor[numBTrees];
             btreeAccessors = new BTreeAccessor[numBTrees];
+            isMemoryComponent = new boolean[numBTrees];
         }
         for (int i = 0; i < numBTrees; i++) {
             ILSMComponent component = operationalComponents.get(i);
@@ -357,7 +360,7 @@
                 includeMutableComponent = true;
             }
             btree = (BTree) component.getIndex();
-            if (btreeAccessors[i] == null) {
+            if (btreeAccessors[i] == null || destroyIncompatible(component, i)) {
                 btreeAccessors[i] = btree.createAccessor(NoOpIndexAccessParameters.INSTANCE);
                 rangeCursors[i] = btreeAccessors[i].createSearchCursor(false);
             } else {
@@ -365,6 +368,7 @@
                 btreeAccessors[i].reset(btree, NoOpOperationCallback.INSTANCE, NoOpOperationCallback.INSTANCE);
                 rangeCursors[i].close();
             }
+            isMemoryComponent[i] = component.getType() == LSMComponentType.MEMORY;
         }
         IndexCursorUtils.open(btreeAccessors, rangeCursors, searchPred);
         try {
@@ -377,6 +381,22 @@
         }
     }
 
+    private boolean destroyIncompatible(ILSMComponent component, int index) throws HyracksDataException {
+        // exclusive or. if the component is memory and the previous one at that index was a disk component
+        // or vice versa, then we should destroy the cursor and accessor since they need to be recreated
+        if (component.getType() == LSMComponentType.MEMORY ^ isMemoryComponent[index]) {
+            Throwable failure = CleanupUtils.destroy(null, btreeAccessors[index]);
+            btreeAccessors[index] = null;
+            failure = CleanupUtils.destroy(failure, rangeCursors[index]);
+            rangeCursors[index] = null;
+            if (failure != null) {
+                throw HyracksDataException.create(failure);
+            }
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public boolean getSearchOperationCallbackProceedResult() {
         return resultOfSearchCallbackProceed;