[ASTERIXDB-3168][COMP] Refactor/share CBO code

Change-Id: I4c164cd517315f8ab9ae8df2baf06b4a72fedee5
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17495
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/Cost.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/Cost.java
index 5dda277..45e6691 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/Cost.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/Cost.java
@@ -22,7 +22,7 @@
 public class Cost implements ICost {
 
     public static final double MAX_CARD = 1.0e200;
-    protected static final int COST_EQ = 0;
+    private static final int COST_EQ = 0;
 
     private final double cost;
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/CostMethods.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/CostMethods.java
index 5dafa54..887cc94 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/CostMethods.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/CostMethods.java
@@ -40,12 +40,12 @@
         maxMemorySize = getMaxMemorySize();
     }
 
-    public long getBufferCacheSize() {
+    private long getBufferCacheSize() {
         MetadataProvider metadataProvider = (MetadataProvider) optCtx.getMetadataProvider();
         return metadataProvider.getStorageProperties().getBufferCacheSize();
     }
 
-    public long getBufferCachePageSize() {
+    private long getBufferCachePageSize() {
         MetadataProvider metadataProvider = (MetadataProvider) optCtx.getMetadataProvider();
         return metadataProvider.getStorageProperties().getBufferCachePageSize();
     }
@@ -63,7 +63,11 @@
         return new Cost(jn.computeJoinCardinality());
     }
 
-    public Cost costIndexScan(JoinNode jn) {
+    public Cost costIndexScan(JoinNode jn, double indexSel) {
+        return new Cost(jn.computeJoinCardinality());
+    }
+
+    public Cost costIndexDataScan(JoinNode jn, double indexSel) {
         return new Cost(jn.computeJoinCardinality());
     }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/ICostMethods.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/ICostMethods.java
index 47ef617..ef4af41 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/ICostMethods.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/cost/ICostMethods.java
@@ -24,7 +24,7 @@
 public interface ICostMethods {
     Cost costFullScan(JoinNode jn);
 
-    Cost costIndexScan(JoinNode jn);
+    Cost costIndexScan(JoinNode jn, double indexSel);
 
     Cost costHashJoin(JoinNode currentJn);
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/ContainsExpressionVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/ContainsExpressionVisitor.java
new file mode 100644
index 0000000..6016949
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/ContainsExpressionVisitor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.asterix.optimizer.rules.cbo;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionReferenceTransform;
+
+public class ContainsExpressionVisitor implements ILogicalExpressionReferenceTransform {
+    private ILogicalExpression expr;
+
+    protected void setExpression(ILogicalExpression expr) {
+        this.expr = expr;
+    }
+
+    @Override
+    public boolean transform(Mutable<ILogicalExpression> exprRef) throws AlgebricksException {
+        ILogicalExpression expression = exprRef.getValue();
+        boolean result = expr.equals(expression);
+
+        if (!result && expression.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+            AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expression;
+            for (Mutable<ILogicalExpression> arg : funcExpr.getArguments()) {
+                if (transform(arg)) {
+                    return true;
+                }
+            }
+        }
+
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
index 8ea1452..f2319c4 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
@@ -67,7 +67,7 @@
 
     private static final Logger LOGGER = LogManager.getLogger();
 
-    protected final JoinEnum joinEnum;
+    private final JoinEnum joinEnum;
 
     public EnumerateJoinsRule(JoinEnum joinEnum) {
         this.joinEnum = joinEnum;
@@ -602,7 +602,7 @@
         return root;
     }
 
-    public static void printPlan(IPlanPrettyPrinter pp, AbstractLogicalOperator op, String text)
+    protected static void printPlan(IPlanPrettyPrinter pp, AbstractLogicalOperator op, String text)
             throws AlgebricksException {
         if (LOGGER.isTraceEnabled()) {
             pp.reset();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinCondition.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinCondition.java
index bc7ea3f..7cf1ebd 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinCondition.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinCondition.java
@@ -23,27 +23,27 @@
 
 public class JoinCondition {
 
-    public static final int NO_JC = -1;
+    protected static final int NO_JC = -1;
 
-    public ILogicalExpression joinCondition;
-    public boolean derived = false;
-    public boolean partOfComposite = false;
-    public int numberOfVars = 0; // how many variables
-    public int componentNumber = 0; // for identifying if join graph is connected
-    public int datasetBits;
+    protected ILogicalExpression joinCondition;
+    private boolean derived = false;
+    protected boolean partOfComposite = false;
+    protected int numberOfVars = 0; // how many variables
+    protected int componentNumber = 0; // for identifying if join graph is connected
+    protected int datasetBits;
     // used for triangle detection; we strictly do not mean left and right here.
     // first and second sides would be more appropriate
-    public int leftSideBits;
-    public int rightSideBits;
-    public double selectivity;
-    public comparisonOp comparisonType;
+    protected int leftSideBits;
+    protected int rightSideBits;
+    protected double selectivity;
+    protected comparisonOp comparisonType;
 
-    public enum comparisonOp {
+    protected enum comparisonOp {
         OP_EQ,
         OP_OTHER
     }
 
-    public ILogicalExpression getJoinCondition() {
+    protected ILogicalExpression getJoinCondition() {
         return joinCondition;
     }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinEnum.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinEnum.java
index 891c82d..46a67e8 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinEnum.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinEnum.java
@@ -30,9 +30,15 @@
 
 import org.apache.asterix.common.annotations.IndexedNLJoinExpressionAnnotation;
 import org.apache.asterix.common.annotations.SecondaryIndexSearchPreferenceAnnotation;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.metadata.declared.DataSource;
 import org.apache.asterix.metadata.declared.DataSourceId;
+import org.apache.asterix.metadata.declared.DatasetDataSource;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.metadata.declared.SampleDataSource;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.om.base.AInt64;
 import org.apache.asterix.om.base.AOrderedList;
 import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.om.constants.AsterixConstantValue;
@@ -57,6 +63,7 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.HashJoinExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
+import org.apache.hyracks.algebricks.core.algebra.expressions.PredicateCardinalityAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
@@ -66,11 +73,13 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.EmptyTupleSourceOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
 import org.apache.hyracks.algebricks.core.rewriter.base.PhysicalOptimizationConfig;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
 import org.apache.hyracks.api.exceptions.Warning;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -84,19 +93,19 @@
     protected List<PlanNode> allPlans; // list of all plans
     protected JoinNode[] jnArray; // array of all join nodes
     protected int jnArraySize;
-    protected List<Pair<EmptyTupleSourceOperator, DataSourceScanOperator>> emptyTupleAndDataSourceOps;
+    private List<Pair<EmptyTupleSourceOperator, DataSourceScanOperator>> emptyTupleAndDataSourceOps;
     protected Map<EmptyTupleSourceOperator, ILogicalOperator> joinLeafInputsHashMap;
     protected List<ILogicalExpression> singleDatasetPreds;
-    protected List<AssignOperator> assignOps;
-    protected List<ILogicalOperator> joinOps;
+    private List<AssignOperator> assignOps;
+    private List<ILogicalOperator> joinOps;
     protected ILogicalOperator localJoinOp; // used in nestedLoopsApplicable code.
     protected IOptimizationContext optCtx;
+
     protected Stats stats;
-    protected PhysicalOptimizationConfig physOptConfig;
-    protected boolean cboMode;
-    protected boolean cboTestMode;
+    private boolean cboMode;
+    private boolean cboTestMode;
     protected int numberOfTerms;
-    protected AbstractLogicalOperator op;
+    private AbstractLogicalOperator op;
     protected boolean connectedJoinGraph;
     protected boolean forceJoinOrderMode;
     protected String queryPlanShape;
@@ -106,13 +115,12 @@
     public JoinEnum() {
     }
 
-    public void initEnum(AbstractLogicalOperator op, boolean cboMode, boolean cboTestMode, int numberOfFromTerms,
+    protected void initEnum(AbstractLogicalOperator op, boolean cboMode, boolean cboTestMode, int numberOfFromTerms,
             List<Pair<EmptyTupleSourceOperator, DataSourceScanOperator>> emptyTupleAndDataSourceOps,
             Map<EmptyTupleSourceOperator, ILogicalOperator> joinLeafInputsHashMap, List<ILogicalOperator> joinOps,
             List<AssignOperator> assignOps, IOptimizationContext context) {
         this.singleDatasetPreds = new ArrayList<>();
         this.joinConditions = new ArrayList<>();
-        this.assignOps = new ArrayList<>();
         this.joinHints = new HashMap<>();
         this.allPlans = new ArrayList<>();
         this.numberOfTerms = numberOfFromTerms;
@@ -120,7 +128,6 @@
         this.cboTestMode = cboTestMode;
         this.connectedJoinGraph = true;
         this.optCtx = context;
-        this.physOptConfig = context.getPhysicalOptimizationConfig();
         this.emptyTupleAndDataSourceOps = emptyTupleAndDataSourceOps;
         this.joinLeafInputsHashMap = joinLeafInputsHashMap;
         this.assignOps = assignOps;
@@ -143,7 +150,7 @@
         }
     }
 
-    public List<JoinCondition> getJoinConditions() {
+    protected List<JoinCondition> getJoinConditions() {
         return joinConditions;
     }
 
@@ -151,27 +158,23 @@
         return allPlans;
     }
 
-    public JoinNode[] getJnArray() {
+    protected JoinNode[] getJnArray() {
         return jnArray;
     }
 
-    public Cost getCostHandle() {
+    protected Cost getCostHandle() {
         return (Cost) cost;
     }
 
-    public CostMethods getCostMethodsHandle() {
+    protected CostMethods getCostMethodsHandle() {
         return (CostMethods) costMethods;
     }
 
-    public Stats getStatsHandle() {
+    protected Stats getStatsHandle() {
         return stats;
     }
 
-    public Map<EmptyTupleSourceOperator, ILogicalOperator> getJoinLeafInputsHashMap() {
-        return joinLeafInputsHashMap;
-    }
-
-    public ILogicalOperator findLeafInput(List<LogicalVariable> logicalVars) throws AlgebricksException {
+    protected ILogicalOperator findLeafInput(List<LogicalVariable> logicalVars) throws AlgebricksException {
         Set<LogicalVariable> vars = new HashSet<>();
         for (Pair<EmptyTupleSourceOperator, DataSourceScanOperator> emptyTupleAndDataSourceOp : emptyTupleAndDataSourceOps) {
             EmptyTupleSourceOperator emptyOp = emptyTupleAndDataSourceOp.getFirst();
@@ -187,7 +190,7 @@
         return null;
     }
 
-    public ILogicalExpression combineAllConditions(List<Integer> newJoinConditions) {
+    protected ILogicalExpression combineAllConditions(List<Integer> newJoinConditions) {
         if (newJoinConditions.size() == 0) {
             // this is a cartesian product
             return ConstantExpression.TRUE;
@@ -207,7 +210,7 @@
         return andExpr;
     }
 
-    public ILogicalExpression getNestedLoopJoinExpr(List<Integer> newJoinConditions) {
+    protected ILogicalExpression getNestedLoopJoinExpr(List<Integer> newJoinConditions) {
         if (newJoinConditions.size() != 1) {
             // may remove this restriction later if possible
             return null;
@@ -216,7 +219,7 @@
         return jc.joinCondition;
     }
 
-    public ILogicalExpression getHashJoinExpr(List<Integer> newJoinConditions) {
+    protected ILogicalExpression getHashJoinExpr(List<Integer> newJoinConditions) {
         if (newJoinConditions.size() == 0) {
             // this is a cartesian product
             return ConstantExpression.TRUE;
@@ -245,7 +248,7 @@
         return eqPredFound ? andExpr : null;
     }
 
-    public HashJoinExpressionAnnotation findHashJoinHint(List<Integer> newJoinConditions) {
+    protected HashJoinExpressionAnnotation findHashJoinHint(List<Integer> newJoinConditions) {
         for (int i : newJoinConditions) {
             JoinCondition jc = joinConditions.get(i);
             if (jc.comparisonType != JoinCondition.comparisonOp.OP_EQ) {
@@ -263,7 +266,7 @@
         return null;
     }
 
-    public BroadcastExpressionAnnotation findBroadcastHashJoinHint(List<Integer> newJoinConditions) {
+    protected BroadcastExpressionAnnotation findBroadcastHashJoinHint(List<Integer> newJoinConditions) {
         for (int i : newJoinConditions) {
             JoinCondition jc = joinConditions.get(i);
             if (jc.comparisonType != JoinCondition.comparisonOp.OP_EQ) {
@@ -281,7 +284,7 @@
         return null;
     }
 
-    public IndexedNLJoinExpressionAnnotation findNLJoinHint(List<Integer> newJoinConditions) {
+    protected IndexedNLJoinExpressionAnnotation findNLJoinHint(List<Integer> newJoinConditions) {
         for (int i : newJoinConditions) {
             JoinCondition jc = joinConditions.get(i);
             ILogicalExpression expr = jc.joinCondition;
@@ -316,7 +319,7 @@
         return false;
     }
 
-    public int findJoinNodeIndexByName(String name) {
+    protected int findJoinNodeIndexByName(String name) {
         for (int i = 1; i <= this.numberOfTerms; i++) {
             if (name.equals(jnArray[i].datasetNames.get(0))) {
                 return i;
@@ -328,7 +331,7 @@
         return JoinNode.NO_JN;
     }
 
-    public int findJoinNodeIndex(LogicalVariable lv) throws AlgebricksException {
+    protected int findJoinNodeIndex(LogicalVariable lv) throws AlgebricksException {
         List<Pair<EmptyTupleSourceOperator, DataSourceScanOperator>> emptyTupleAndDataSourceOps =
                 this.emptyTupleAndDataSourceOps;
         Map<EmptyTupleSourceOperator, ILogicalOperator> joinLeafInputsHashMap = this.joinLeafInputsHashMap;
@@ -372,7 +375,7 @@
 
     // This finds all the join Conditions in the whole query. This is a global list of all join predicates.
     // It also fills in the dataset Bits for each join predicate.
-    protected void findJoinConditions() throws AlgebricksException {
+    private void findJoinConditionsAndAssignSels() throws AlgebricksException {
         List<Mutable<ILogicalExpression>> conjs = new ArrayList<>();
         for (ILogicalOperator jOp : joinOps) {
             AbstractBinaryJoinOperator joinOp = (AbstractBinaryJoinOperator) jOp;
@@ -418,15 +421,6 @@
         // now fill the datasetBits for each join condition.
         for (JoinCondition jc : joinConditions) {
             ILogicalExpression joinExpr = jc.joinCondition;
-            /*
-            if (joinExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
-                AbstractFunctionCallExpression afce = (AbstractFunctionCallExpression) joinExpr;
-                // remove all the join method type annotations.
-                afce.removeAnnotation(BroadcastExpressionAnnotation.class);
-                afce.removeAnnotation(IndexedNLJoinExpressionAnnotation.class);
-                afce.removeAnnotation(HashJoinExpressionAnnotation.class);
-            }
-             */
             usedVars.clear();
             joinExpr.getUsedVariables(usedVars);
             // We only set these for join predicates that have exactly two tables
@@ -646,27 +640,7 @@
         return jnNumber;
     }
 
-    protected int enumerateBaseLevelJoinNodes() throws AlgebricksException {
-        int lastBaseLevelJnNum = initializeBaseLevelJoinNodes();
-        if (lastBaseLevelJnNum == PlanNode.NO_PLAN) {
-            return PlanNode.NO_PLAN;
-        }
-        int dataScanPlan = PlanNode.NO_PLAN;
-        for (int i = 1; i <= numberOfTerms; i++) {
-            JoinNode jn = jnArray[i];
-            EmptyTupleSourceOperator ets = emptyTupleAndDataSourceOps.get(i - 1).getFirst();
-            ILogicalOperator leafInput = joinLeafInputsHashMap.get(ets);
-            dataScanPlan = jn.addSingleDatasetPlans();
-            if (dataScanPlan == PlanNode.NO_PLAN) {
-                return PlanNode.NO_PLAN;
-            }
-            // We may not add any index plans, so need to check for NO_PLAN
-            jn.addIndexAccessPlans(leafInput);
-        }
-        return numberOfTerms;
-    }
-
-    protected int initializeBaseLevelJoinNodes() throws AlgebricksException {
+    private int initializeBaseLevelJoinNodes() throws AlgebricksException {
         // join nodes have been allocated in the JoinEnum
         // add a dummy Plan Node; we do not want planNode at position 0 to be a valid plan
         PlanNode pn = new PlanNode(0, this);
@@ -738,8 +712,212 @@
         return numberOfTerms;
     }
 
+    // Most of this work is done in the very first line by calling initializeBaseLevelJoinNodes().
+    // the remaining work here is to find the selectivities of the predicates using sampling.
+    // By the time execution reaches this point, samples are guaranteed to exist on all datasets,
+    // so some of the checks can be removed.
+    private int enumerateBaseLevelJoinNodes() throws AlgebricksException {
+        int lastBaseLevelJnNum = initializeBaseLevelJoinNodes(); // initialize the level 1 join nodes
+        if (lastBaseLevelJnNum == PlanNode.NO_PLAN) {
+            return PlanNode.NO_PLAN;
+        }
+
+        int dataScanPlan;
+        JoinNode[] jnArray = this.getJnArray();
+        for (int i = 1; i <= this.numberOfTerms; i++) {
+            JoinNode jn = jnArray[i];
+            Index.SampleIndexDetails idxDetails = jn.getIdxDetails();
+            EmptyTupleSourceOperator ets = this.emptyTupleAndDataSourceOps.get(i - 1).getFirst();
+            ILogicalOperator leafInput = this.joinLeafInputsHashMap.get(ets);
+            if (!cboTestMode) {
+                if (idxDetails == null) {
+                    continue;
+                }
+                double origDatasetCard, finalDatasetCard, sampleCard;
+
+                ILogicalOperator parent = findDataSourceScanOperatorParent(leafInput);
+                DataSourceScanOperator scanOp = this.emptyTupleAndDataSourceOps.get(i - 1).getSecond();
+                if (scanOp == null) {
+                    continue; // what happens to the cards and sizes then? this may happen in case of in lists
+                }
+
+                finalDatasetCard = origDatasetCard = idxDetails.getSourceCardinality();
+
+                List<List<IAObject>> result;
+                SelectOperator selop = (SelectOperator) findASelectOp(leafInput);
+
+                if (jn.getCardinality() == jn.getOrigCardinality() && selop != null) { // this means there was no selectivity hint provided
+                    SampleDataSource sampledatasource = getSampleDataSource(scanOp);
+                    DataSourceScanOperator deepCopyofScan =
+                            (DataSourceScanOperator) OperatorManipulationUtil.bottomUpCopyOperators(scanOp);
+                    deepCopyofScan.setDataSource(sampledatasource);
+
+                    // if there is only one conjunct, I do not have to call the sampling query during index selection!
+                    // insert this in place of the scandatasourceOp.
+                    parent.getInputs().get(0).setValue(deepCopyofScan);
+                    // There are predicates here. So skip the predicates and get the original dataset card.
+                    // Now apply all the predicates and get the card after all predicates are applied.
+                    result = stats.runSamplingQuery(this.optCtx, leafInput);
+                    double predicateCardinality = ((double) ((AInt64) result.get(0).get(0)).getLongValue());
+                    if (predicateCardinality == 0.0) {
+                        predicateCardinality = 0.0001 * idxDetails.getSampleCardinalityTarget();
+                    }
+                    // now scale up
+                    sampleCard = Math.min(idxDetails.getSampleCardinalityTarget(), origDatasetCard);
+                    if (sampleCard == 0) { // should not happen unless the original dataset is empty
+                        sampleCard = 1; // we may have to make some adjustments to costs when the sample returns very rows.
+
+                        IWarningCollector warningCollector = optCtx.getWarningCollector();
+                        if (warningCollector.shouldWarn()) {
+                            warningCollector
+                                    .warn(Warning.of(scanOp.getSourceLocation(), ErrorCode.SAMPLE_HAS_ZERO_ROWS));
+                        }
+                    }
+
+                    finalDatasetCard *= predicateCardinality / sampleCard;
+                    // now switch the input back.
+                    parent.getInputs().get(0).setValue(scanOp);
+                    jn.setCardinality(finalDatasetCard);
+                }
+            }
+            dataScanPlan = jn.addSingleDatasetPlans();
+            if (dataScanPlan == PlanNode.NO_PLAN) {
+                return PlanNode.NO_PLAN;
+            }
+            // We may not add any index plans, so need to check for NO_PLAN
+            jn.addIndexAccessPlans(leafInput);
+        }
+        return this.numberOfTerms;
+    }
+
+    private boolean isPredicateCardinalityAnnotationPresent(ILogicalExpression leExpr) {
+        if (leExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+            AbstractFunctionCallExpression afcExpr = (AbstractFunctionCallExpression) leExpr;
+            PredicateCardinalityAnnotation pca = afcExpr.getAnnotation(PredicateCardinalityAnnotation.class);
+            if (pca != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Since we need to switch the datasource to the sample, we need the parent, so we can do the necessary
+    // linked list manipulation.
+    protected ILogicalOperator findDataSourceScanOperatorParent(ILogicalOperator op) {
+        ILogicalOperator parent = op;
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.DATASOURCESCAN)) {
+                return parent;
+            }
+            parent = op;
+            op = op.getInputs().get(0).getValue();
+        }
+        return null;
+    }
+
+    // we need to switch the datascource from the dataset source to the corresponding sample datasource.
+    // Little tricky how this is done!
+    protected SampleDataSource getSampleDataSource(DataSourceScanOperator scanOp) throws AlgebricksException {
+        DataverseName dataverseName = stats.findDataverseName(scanOp);
+        DataSource ds = (DataSource) scanOp.getDataSource();
+        DataSourceId dsid = ds.getId();
+        MetadataProvider mdp = (MetadataProvider) this.optCtx.getMetadataProvider();
+        Index index = mdp.findSampleIndex(dataverseName, dsid.getDatasourceName());
+        DatasetDataSource dds = (DatasetDataSource) ds;
+        SampleDataSource sds = new SampleDataSource(dds.getDataset(), index.getIndexName(), ds.getItemType(),
+                ds.getMetaItemType(), ds.getDomain());
+        return sds;
+    }
+
+    private ILogicalOperator findASelectOp(ILogicalOperator op) {
+
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+
+            if (op.getOperatorTag() == LogicalOperatorTag.SELECT) {
+                return op;
+            }
+
+            op = op.getInputs().get(0).getValue();
+        }
+        return null;
+    }
+
+    // Find the join conditions. Assign selectivities to the join conditions from any user provided annotation hints.
+    // If there are no annotation hints, use samples to find the selectivities of the single table predicates
+    // found inside of complex join predicates (as in q7). A lot of extra code has gone into making q7 work.
+    private void findJoinConditions() throws AlgebricksException {
+        findJoinConditionsAndAssignSels();
+        List<List<IAObject>> result;
+        double predicateCardinality;
+        // for all the singleVarExprs, we need to issue a sample query. These exprs did not get assigned a selectivity.
+        for (ILogicalExpression exp : this.singleDatasetPreds) {
+            if (isPredicateCardinalityAnnotationPresent(exp)) {
+                continue; // no need to get selectivity from sample in case of user provided hints.
+            }
+            List<LogicalVariable> vars = new ArrayList<>();
+            exp.getUsedVariables(vars);
+            if (vars.size() == 1) { // just being really safe. If samples have size 0, there are issues.
+                double origDatasetCard, finalDatasetCard, sampleCard, predicateCard;
+                ILogicalOperator leafInput = findLeafInput(vars);
+                ILogicalOperator parent = findDataSourceScanOperatorParent(leafInput);
+                DataSourceScanOperator scanOp = (DataSourceScanOperator) parent.getInputs().get(0).getValue();
+
+                if (scanOp == null) {
+                    continue; // what happens to the cards and sizes then? this may happen in case of in lists
+                }
+
+                Index index = getStatsHandle().findSampleIndex(scanOp, this.optCtx);
+                if (index == null) {
+                    continue; // no sample found
+                }
+                Index.SampleIndexDetails idxDetails = (Index.SampleIndexDetails) index.getIndexDetails();
+                origDatasetCard = idxDetails.getSourceCardinality();
+                sampleCard = Math.min(idxDetails.getSampleCardinalityTarget(), origDatasetCard); // handle datasets whose card is small
+                if (sampleCard == 0) {
+                    sampleCard = 1;
+                    IWarningCollector warningCollector = optCtx.getWarningCollector();
+                    if (warningCollector.shouldWarn()) {
+                        warningCollector.warn(Warning.of(scanOp.getSourceLocation(), ErrorCode.SAMPLE_HAS_ZERO_ROWS));
+                    }
+                }
+
+                // replace the dataScanSourceOperator with the sampling source
+                SampleDataSource sampledatasource = getSampleDataSource(scanOp);
+                DataSourceScanOperator deepCopyofScan =
+                        (DataSourceScanOperator) OperatorManipulationUtil.bottomUpCopyOperators(scanOp);
+                deepCopyofScan.setDataSource(sampledatasource);
+
+                // insert this in place of the scandatasourceOp.
+                parent.getInputs().get(0).setValue(deepCopyofScan);
+
+                // Need to add a selectOperator on top of leafInput.
+                SelectOperator selOp = new SelectOperator(new MutableObject<>(exp));
+                selOp.getInputs().add(new MutableObject<>(leafInput));
+                result = stats.runSamplingQuery(this.optCtx, selOp);
+                predicateCardinality = ((double) ((AInt64) result.get(0).get(0)).getLongValue());
+                if (predicateCardinality == 0.0) {
+                    predicateCardinality = 0.0001 * idxDetails.getSampleCardinalityTarget();
+                }
+
+                PredicateCardinalityAnnotation anno =
+                        new PredicateCardinalityAnnotation(predicateCardinality / sampleCard);
+                AbstractFunctionCallExpression afce = (AbstractFunctionCallExpression) exp;
+                afce.putAnnotation(anno);
+                // now switch the input back.
+                parent.getInputs().get(0).setValue(scanOp);
+            }
+        }
+
+        if (this.singleDatasetPreds.size() > 0) { // We did not have selectivities for these before. Now we do.
+            for (JoinCondition jc : joinConditions) {
+                jc.selectivity = stats.getSelectivityFromAnnotationMain(jc.getJoinCondition(), false);
+                // we may be repeating some work here, but that is ok. This will rarely happen (happens in q7 tpch)
+            }
+        }
+    }
+
     // main entry point in this file
-    public int enumerateJoins() throws AlgebricksException {
+    protected int enumerateJoins() throws AlgebricksException {
         // create a localJoinOp for use in calling existing nested loops code.
         InnerJoinOperator dummyInput = new InnerJoinOperator(null, null, null);
         localJoinOp = new InnerJoinOperator(new MutableObject<>(ConstantExpression.TRUE),
@@ -792,12 +970,12 @@
         return sb.toString();
     }
 
-    public static boolean getForceJoinOrderMode(IOptimizationContext context) {
+    private static boolean getForceJoinOrderMode(IOptimizationContext context) {
         PhysicalOptimizationConfig physOptConfig = context.getPhysicalOptimizationConfig();
         return physOptConfig.getForceJoinOrderMode();
     }
 
-    public static String getQueryPlanShape(IOptimizationContext context) {
+    private static String getQueryPlanShape(IOptimizationContext context) {
         PhysicalOptimizationConfig physOptConfig = context.getPhysicalOptimizationConfig();
         return physOptConfig.getQueryPlanShapeMode();
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinNode.java
index 417f178..fa2a144 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/JoinNode.java
@@ -20,7 +20,9 @@
 package org.apache.asterix.optimizer.rules.cbo;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -29,6 +31,7 @@
 
 import org.apache.asterix.common.annotations.IndexedNLJoinExpressionAnnotation;
 import org.apache.asterix.common.annotations.SkipSecondaryIndexSearchExpressionAnnotation;
+import org.apache.asterix.common.config.DatasetConfig;
 import org.apache.asterix.metadata.entities.Index;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.optimizer.cost.Cost;
@@ -41,6 +44,7 @@
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
@@ -66,7 +70,6 @@
 
 public class JoinNode {
     private static final Logger LOGGER = LogManager.getLogger();
-
     protected JoinEnum joinEnum;
     protected int jnArrayIndex;
     protected int datasetBits; // this is bitmap of all the keyspaceBits present in this joinNode
@@ -74,73 +77,52 @@
     protected List<String> datasetNames;
     protected List<String> aliases;
     protected int cheapestPlanIndex;
-    protected ICost cheapestPlanCost;
+    private ICost cheapestPlanCost;
     protected double origCardinality; // without any selections
     protected double cardinality;
     protected double size;
     protected List<Integer> planIndexesArray; // indexes into the PlanNode array in enumerateJoins
-    protected int jnIndex, level, highestDatasetId;
-    protected JoinNode rightJn, leftJn;
-    protected List<Integer> applicableJoinConditions;
+    protected int jnIndex;
+    protected int level;
+    protected int highestDatasetId;
+    private JoinNode rightJn;
+    private JoinNode leftJn;
+    private List<Integer> applicableJoinConditions;
     protected EmptyTupleSourceOperator correspondingEmptyTupleSourceOp; // There is a 1-1 relationship between the LVs and the dataSourceScanOps and the leafInputs.
-    protected List<Pair<IAccessMethod, Index>> chosenIndexes;
-    protected Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs;
+    private List<Pair<IAccessMethod, Index>> chosenIndexes;
+    private Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs;
     protected Index.SampleIndexDetails idxDetails;
+    private List<Triple<Index, Double, AbstractFunctionCallExpression>> IndexCostInfo;
+    // The triple above is : Index, selectivity, and the index expression
     protected static int NO_JN = -1;
-    protected static int NO_CARDS = -2;
+    private static int NO_CARDS = -2;
 
-    public JoinNode(int i) {
+    private JoinNode(int i) {
         this.jnArrayIndex = i;
         planIndexesArray = new ArrayList<>();
         cheapestPlanIndex = PlanNode.NO_PLAN;
         size = 1; // for now, will be the size of the doc for this joinNode
     }
 
-    public JoinNode(int i, JoinEnum joinE) {
+    protected JoinNode(int i, JoinEnum joinE) {
         this(i);
         joinEnum = joinE;
         cheapestPlanCost = joinEnum.getCostHandle().maxCost();
     }
 
-    public boolean IsBaseLevelJoinNode() {
+    protected boolean IsBaseLevelJoinNode() {
         return this.jnArrayIndex <= joinEnum.numberOfTerms;
     }
 
-    public boolean IsHigherLevelJoinNode() {
+    protected boolean IsHigherLevelJoinNode() {
         return !IsBaseLevelJoinNode();
     }
 
-    public double computeJoinCardinality() {
-        JoinNode[] jnArray = joinEnum.getJnArray();
-        List<JoinCondition> joinConditions = joinEnum.getJoinConditions();
-
-        this.applicableJoinConditions = new ArrayList<>();
-        findApplicableJoinConditions();
-
-        if (LOGGER.isTraceEnabled() && this.applicableJoinConditions.size() == 0) {
-            LOGGER.trace("applicable Join Conditions size is 0 in join Node " + this.jnArrayIndex);
-        }
-
-        // Wonder if this computation will result in an overflow exception. Better to multiply them with selectivities also.
-        double productJoinCardinality = 1.0;
-        for (int idx : this.datasetIndexes) {
-            productJoinCardinality *= jnArray[idx].cardinality;
-        }
-
-        double productJoinSels = 1.0;
-        for (int idx : this.applicableJoinConditions) {
-            if (!joinConditions.get(idx).partOfComposite) {
-                productJoinSels *= joinConditions.get(idx).selectivity;
-            }
-        }
-        return productJoinCardinality * productJoinSels;
-    }
-
     public double getCardinality() {
         return cardinality;
     }
 
-    public void setCardinality(double card) {
+    protected void setCardinality(double card) {
         cardinality = card;
     }
 
@@ -148,11 +130,11 @@
         return origCardinality;
     }
 
-    public void setOrigCardinality(double card) {
+    protected void setOrigCardinality(double card) {
         origCardinality = card;
     }
 
-    public void setAvgDocSize(double avgDocSize) {
+    protected void setAvgDocSize(double avgDocSize) {
         size = avgDocSize;
     }
 
@@ -172,19 +154,19 @@
         return rightJn;
     }
 
-    public List<String> getAliases() {
+    private List<String> getAliases() {
         return aliases;
     }
 
-    public List<String> getDatasetNames() {
+    protected List<String> getDatasetNames() {
         return datasetNames;
     }
 
-    public Index.SampleIndexDetails getIdxDetails() {
+    protected Index.SampleIndexDetails getIdxDetails() {
         return idxDetails;
     }
 
-    protected boolean nestedLoopsApplicable(ILogicalExpression joinExpr) throws AlgebricksException {
+    private boolean nestedLoopsApplicable(ILogicalExpression joinExpr) throws AlgebricksException {
 
         List<LogicalVariable> usedVarList = new ArrayList<>();
         joinExpr.getUsedVariables(usedVarList);
@@ -243,7 +225,7 @@
         return (one & two) == one;
     }
 
-    protected void findApplicableJoinConditions() {
+    private void findApplicableJoinConditions() {
         List<JoinCondition> joinConditions = joinEnum.getJoinConditions();
 
         int i = 0;
@@ -255,7 +237,7 @@
         }
     }
 
-    protected List<Integer> getNewJoinConditionsOnly() {
+    private List<Integer> getNewJoinConditionsOnly() {
         List<Integer> newJoinConditions = new ArrayList<>();
         JoinNode leftJn = this.leftJn;
         JoinNode rightJn = this.rightJn;
@@ -281,7 +263,121 @@
         return newJoinConditions; // this can be of size 0 because this may be a cartesian join
     }
 
-    public int addSingleDatasetPlans() {
+    public double computeJoinCardinality() {
+        JoinNode[] jnArray = joinEnum.getJnArray();
+        List<JoinCondition> joinConditions = joinEnum.getJoinConditions();
+        double joinCard;
+
+        this.applicableJoinConditions = new ArrayList<>();
+        findApplicableJoinConditions();
+
+        if (LOGGER.isTraceEnabled() && this.applicableJoinConditions.size() == 0) {
+            LOGGER.trace("applicable Join Conditions size is 0 in join Node " + this.jnArrayIndex);
+        }
+
+        // Wonder if this computation will result in an overflow exception. Better to multiply them with selectivities also.
+        double productJoinCardinality = 1.0;
+        for (int idx : this.datasetIndexes) {
+            productJoinCardinality *= jnArray[idx].cardinality;
+        }
+
+        double productJoinSels = 1.0;
+        for (int idx : this.applicableJoinConditions) {
+            if (!joinConditions.get(idx).partOfComposite) {
+                productJoinSels *= joinConditions.get(idx).selectivity;
+            }
+        }
+        joinCard = productJoinCardinality * productJoinSels;
+
+        double redundantSel = 1.0;
+        // Now see if any redundant edges are present; R.a = S.a and S.a = T.a ==> R.a = T.a.
+        // One of them must be removed to estimate cardinality correctly.
+        if (this.applicableJoinConditions.size() >= 3) {
+            redundantSel = removeRedundantPred(this.applicableJoinConditions);
+        }
+
+        // By dividing by redundantSel, we are undoing the earlier multiplication of all the selectivities.
+        return joinCard / redundantSel;
+    }
+
+    private static double adjustSelectivities(JoinCondition jc1, JoinCondition jc2, JoinCondition jc3) {
+        double sel;
+        if (jc1.comparisonType == JoinCondition.comparisonOp.OP_EQ
+                && jc2.comparisonType == JoinCondition.comparisonOp.OP_EQ
+                && jc3.comparisonType == JoinCondition.comparisonOp.OP_EQ) {
+            sel = findRedundantSel(jc1.selectivity, jc2.selectivity, jc3.selectivity);
+        } else {
+            // at least one of the predicates in not an equality predicate
+            //this can get messy here, as 1, or 2 or all 3 can be non equality
+            // we will just drop the first one we find now
+            if (jc1.comparisonType != JoinCondition.comparisonOp.OP_EQ) {
+                sel = jc1.selectivity;
+            } else if (jc2.comparisonType != JoinCondition.comparisonOp.OP_EQ) {
+                sel = jc2.selectivity;
+            } else {
+                sel = jc3.selectivity;
+            }
+        }
+        return sel;
+    }
+
+    // if a redundant edge is found, we need to eliminate one of the edges.
+    // If two triangles share an edge, removing the common edge will suffice
+    // Each edge has two vertices. So we can only handle predicate with exactly two tables such as R.a = S.a
+    // We will not handle cases such as R.a + S.a = T.a
+    // It should be easy to identify two vertex edges as only two bits will be set for such conditions.
+    private double removeRedundantPred(List<Integer> applicablePredicatesInCurrentJn) {
+        double redundantSel = 1.0;
+        List<JoinCondition> joinConditions = joinEnum.getJoinConditions();
+        JoinCondition jc1, jc2, jc3;
+        int[] vertices = new int[6];
+        int[] verticesCopy = new int[6];
+        for (int i = 0; i <= applicablePredicatesInCurrentJn.size() - 3; i++) {
+            jc1 = joinConditions.get(applicablePredicatesInCurrentJn.get(i));
+            if (jc1.partOfComposite) {
+                continue; // must ignore these or the same triangles will be found more than once.
+            }
+            vertices[0] = jc1.leftSideBits;
+            vertices[1] = jc1.rightSideBits;
+            for (int j = i + 1; j <= applicablePredicatesInCurrentJn.size() - 2; j++) {
+                jc2 = joinConditions.get(applicablePredicatesInCurrentJn.get(j));
+                if (jc2.partOfComposite) {
+                    continue;
+                }
+                vertices[2] = jc2.leftSideBits;
+                vertices[3] = jc2.rightSideBits;
+                for (int k = j + 1; k <= applicablePredicatesInCurrentJn.size() - 1; k++) {
+                    jc3 = joinConditions.get(applicablePredicatesInCurrentJn.get(k));
+                    if (jc3.partOfComposite) {
+                        continue;
+                    }
+                    vertices[4] = jc3.leftSideBits;
+                    vertices[5] = jc3.rightSideBits;
+
+                    System.arraycopy(vertices, 0, verticesCopy, 0, 6);
+                    Arrays.sort(verticesCopy);
+                    if (verticesCopy[0] == verticesCopy[1] && verticesCopy[2] == verticesCopy[3]
+                            && verticesCopy[4] == verticesCopy[5]) {
+                        // redundant edge found
+                        redundantSel *= adjustSelectivities(jc1, jc2, jc3);
+                    }
+                }
+            }
+        }
+        return redundantSel;
+    }
+
+    private static double findRedundantSel(double sel1, double sel2, double sel3) {
+        double[] sels = new double[3];
+        sels[0] = sel1;
+        sels[1] = sel2;
+        sels[2] = sel3;
+
+        Arrays.sort(sels); // we are sorting to make this deterministic
+        return sels[1]; // the middle one is closest to one of the extremes
+    }
+
+    protected int addSingleDatasetPlans() {
         List<PlanNode> allPlans = joinEnum.allPlans;
         ICost opCost, totalCost;
 
@@ -290,58 +386,61 @@
         if (this.cheapestPlanIndex == PlanNode.NO_PLAN || opCost.costLT(this.cheapestPlanCost)) {
             // for now just add one plan
             PlanNode pn = new PlanNode(allPlans.size(), joinEnum);
-            pn.jn = this;
+            pn.setJoinNode(this);
             pn.datasetName = this.datasetNames.get(0);
             pn.correspondingEmptyTupleSourceOp = this.correspondingEmptyTupleSourceOp;
-            pn.jnIndexes[0] = this.jnArrayIndex;
-            pn.jnIndexes[1] = JoinNode.NO_JN;
-            pn.planIndexes[0] = PlanNode.NO_PLAN; // There ane no plans below this plan.
-            pn.planIndexes[1] = PlanNode.NO_PLAN; // There ane no plans below this plan.
+            pn.setLeftJoinIndex(this.jnArrayIndex);
+            pn.setRightJoinIndex(JoinNode.NO_JN);
+            pn.setLeftPlanIndex(PlanNode.NO_PLAN); // There ane no plans below this plan.
+            pn.setRightPlanIndex(PlanNode.NO_PLAN); // There ane no plans below this plan.
             pn.opCost = opCost;
             pn.scanOp = PlanNode.ScanMethod.TABLE_SCAN;
             pn.totalCost = totalCost;
 
             allPlans.add(pn);
-            this.planIndexesArray.add(allPlans.size() - 1);
+            this.planIndexesArray.add(pn.allPlansIndex);
             this.cheapestPlanCost = totalCost;
-            this.cheapestPlanIndex = allPlans.size() - 1;
+            this.cheapestPlanIndex = pn.allPlansIndex;
             return this.cheapestPlanIndex;
         }
         return PlanNode.NO_PLAN;
     }
 
-    protected void buildIndexPlan(boolean forceIndexPlan) {
-        List<PlanNode> allPlans = joinEnum.allPlans;
-        ICost opCost, totalCost;
+    private AbstractFunctionCallExpression buildExpr(List<IOptimizableFuncExpr> exprs,
+            List<Pair<Integer, Integer>> pairs) {
+        int i;
+        if (pairs.size() == 1) {
+            i = pairs.get(0).getFirst();
+            return exprs.get(i).getFuncExpr();
+        }
 
-        opCost = joinEnum.getCostMethodsHandle().costIndexScan(this);
-        totalCost = opCost;
-        if (this.cheapestPlanIndex == PlanNode.NO_PLAN || opCost.costLT(this.cheapestPlanCost) || forceIndexPlan) {
-            // for now just add one plan
-            PlanNode pn = new PlanNode(allPlans.size(), joinEnum);
-            pn.jn = this;
-            pn.datasetName = this.datasetNames.get(0);
-            pn.correspondingEmptyTupleSourceOp = this.correspondingEmptyTupleSourceOp;
-            pn.jnIndexes[0] = this.jnArrayIndex;
-            pn.jnIndexes[1] = JoinNode.NO_JN;
-            pn.planIndexes[0] = PlanNode.NO_PLAN; // There ane no plans below this plan.
-            pn.planIndexes[1] = PlanNode.NO_PLAN; // There ane no plans below this plan.
-            pn.opCost = opCost;
-            pn.scanOp = PlanNode.ScanMethod.INDEX_SCAN;
-            pn.totalCost = totalCost;
+        ScalarFunctionCallExpression andExpr = new ScalarFunctionCallExpression(
+                BuiltinFunctions.getBuiltinFunctionInfo(AlgebricksBuiltinFunctions.AND));
 
-            allPlans.add(pn);
-            this.planIndexesArray.add(allPlans.size() - 1);
-            this.cheapestPlanCost = totalCost;
-            this.cheapestPlanIndex = allPlans.size() - 1;
+        for (i = 0; i < pairs.size(); i++) {
+            IOptimizableFuncExpr expr = exprs.get(pairs.get(i).getFirst());
+            andExpr.getArguments().add(new MutableObject<>(expr.getFuncExpr()));
+        }
+        return andExpr;
+    }
+
+    private void setSkipIndexAnnotationsForUnusedIndexes() {
+        for (int i = 0; i < IndexCostInfo.size(); i++) {
+            if (IndexCostInfo.get(i).second == -1.0) {
+                AbstractFunctionCallExpression afce = IndexCostInfo.get(i).third;
+                // this index has to be skipped, so find the corresponding expression
+                afce.putAnnotation(SkipSecondaryIndexSearchExpressionAnnotation
+                        .newInstance(Collections.singleton(IndexCostInfo.get(i).first.getIndexName())));
+            }
         }
     }
 
-    protected void costAndChooseIndexPlans(ILogicalOperator leafInput,
+    private void costAndChooseIndexPlans(ILogicalOperator leafInput,
             Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs) throws AlgebricksException {
-        // Skip indexes with selectivity greater than 0.1, add the SKIP_SECONDARY_INDEX annotation to its expression.
+        SelectOperator selOp;
         double sel;
-        int exprIndex;
+
+        List<Triple<Index, Double, AbstractFunctionCallExpression>> IndexCostInfo = new ArrayList<>();
         for (Map.Entry<IAccessMethod, AccessMethodAnalysisContext> amEntry : analyzedAMs.entrySet()) {
             AccessMethodAnalysisContext analysisCtx = amEntry.getValue();
             Iterator<Map.Entry<Index, List<Pair<Integer, Integer>>>> indexIt =
@@ -350,24 +449,144 @@
             while (indexIt.hasNext()) {
                 Map.Entry<Index, List<Pair<Integer, Integer>>> indexEntry = indexIt.next();
                 Index chosenIndex = indexEntry.getKey();
-                exprIndex = indexEntry.getValue().get(0).getFirst();
-                IOptimizableFuncExpr expr = exprs.get(exprIndex);
-                AbstractFunctionCallExpression afce = expr.getFuncExpr();
+                if (chosenIndex.getIndexType().equals(DatasetConfig.IndexType.LENGTH_PARTITIONED_WORD_INVIX)
+                        || chosenIndex.getIndexType().equals(DatasetConfig.IndexType.SINGLE_PARTITION_WORD_INVIX)
+                        || chosenIndex.getIndexType().equals(DatasetConfig.IndexType.LENGTH_PARTITIONED_NGRAM_INVIX)
+                        || chosenIndex.getIndexType().equals(DatasetConfig.IndexType.SINGLE_PARTITION_NGRAM_INVIX)) {
+                    continue;
+                }
+                AbstractFunctionCallExpression afce = buildExpr(exprs, indexEntry.getValue());
                 PredicateCardinalityAnnotation selectivityAnnotation =
                         afce.getAnnotation(PredicateCardinalityAnnotation.class);
-                if (joinEnum.findUseIndexHint(afce)) {
-                    buildIndexPlan(true);
-                } else if (selectivityAnnotation != null) {
+                if (selectivityAnnotation != null) {
                     sel = selectivityAnnotation.getSelectivity();
-                    if (sel >= joinEnum.stats.SELECTIVITY_FOR_SECONDARY_INDEX_SELECTION) {
-                        afce.putAnnotation(SkipSecondaryIndexSearchExpressionAnnotation
-                                .newInstance(Collections.singleton(chosenIndex.getIndexName())));
+                } else {
+                    if (leafInput.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                        selOp = (SelectOperator) leafInput;
                     } else {
-                        buildIndexPlan(false);
+                        selOp = new SelectOperator(new MutableObject<>(afce));
+                        selOp.getInputs().add(new MutableObject<>(leafInput));
                     }
+                    sel = joinEnum.getStatsHandle().findSelectivityForThisPredicate(selOp, afce, this.origCardinality);
+                }
+                IndexCostInfo.add(new Triple<>(chosenIndex, sel, afce));
+            }
+        }
+        this.IndexCostInfo = IndexCostInfo;
+        if (IndexCostInfo.size() > 0) {
+            buildIndexPlans();
+        }
+        setSkipIndexAnnotationsForUnusedIndexes();
+    }
+
+    private void buildIndexPlans() {
+        List<PlanNode> allPlans = joinEnum.getAllPlans();
+        ICost opCost, totalCost;
+        List<Triple<Index, Double, AbstractFunctionCallExpression>> mandatoryIndexesInfo = new ArrayList<>();
+        List<Triple<Index, Double, AbstractFunctionCallExpression>> optionalIndexesInfo = new ArrayList<>();
+        double sel = 1.0;
+        opCost = this.joinEnum.getCostHandle().zeroCost();
+        for (int i = 0; i < IndexCostInfo.size(); i++) {
+            if (joinEnum.findUseIndexHint(IndexCostInfo.get(i).third)) {
+                mandatoryIndexesInfo.add(IndexCostInfo.get(i));
+            } else {
+                optionalIndexesInfo.add(IndexCostInfo.get(i));
+            }
+        }
+
+        List<ICost> indexCosts = new ArrayList<>(); // these are the costs associated with the index only
+        // First cost all the mandatory indexes. These will be in the plan regardless of the cost
+        if (mandatoryIndexesInfo.size() > 0) {
+            for (int i = 0; i < mandatoryIndexesInfo.size(); i++) {
+                indexCosts.add(joinEnum.getCostMethodsHandle().costIndexScan(this, mandatoryIndexesInfo.get(i).second));
+            }
+
+            opCost = this.joinEnum.getCostHandle().zeroCost();
+
+            for (int i = 0; i < mandatoryIndexesInfo.size(); i++) {
+                opCost = opCost.costAdd(indexCosts.get(i)); // opCost will have all the index scan costs
+                sel *= mandatoryIndexesInfo.get(i).second; // assuming selectivities are independent for now
+            }
+
+            // Now add the data Scan cost.
+            ICost dataScanCost = joinEnum.getCostMethodsHandle().costIndexDataScan(this, sel);
+            opCost = opCost.costAdd(dataScanCost); // opCost now has the total cost of all the mandatory indexes + data costs.
+
+        }
+
+        ICost mandatoryIndexesCost = opCost; // This will be added at the end to the total cost irrespective of optimality.
+
+        // Now lets deal with the optional indexes. These are the ones without any hints on them.
+        List<ICost> dataCosts = new ArrayList<>(); // these are the costs associated with accessing the data records
+        indexCosts.clear();
+        if (optionalIndexesInfo.size() > 0) {
+            optionalIndexesInfo.sort(Comparator.comparingDouble(o -> o.second)); // sort on selectivity.
+
+            // find the costs using one index at a time first.
+
+            // sel is now the selectivity of all the previous mandatory indexes.
+            for (int i = 0; i < optionalIndexesInfo.size(); i++) {
+                indexCosts.add(joinEnum.getCostMethodsHandle().costIndexScan(this, optionalIndexesInfo.get(i).second)); // I0; I1; I2; ...
+                // Now get the cost of the datascans involved with the multiplied selectivity
+                // dataCost (0) will contain the dataScan cost with the first index
+                //dataCost (1) will contain the dataScan cost with the first index and the 2nd index and so on.
+                sel *= optionalIndexesInfo.get(i).second; // assuming selectivities are independent for now
+                dataCosts.add(joinEnum.getCostMethodsHandle().costIndexDataScan(this, sel)); // D0; D01; D012; ...
+            }
+
+            // At the of of the above loop, I0, I1, I2 ... have been computed
+            // Also D0, D01, D012 ... have been computed.
+
+            opCost = indexCosts.get(0).costAdd(dataCosts.get(0));
+            //opCost is now the cost of the first (and cheapest) optional index plus the corresponding data scan
+
+            //Intersect the first two and then the first three and so on.
+            //If the cost does not decrease, then stop
+
+            ICost newIdxCost = indexCosts.get(0); // I0
+            ICost currentCost;
+            for (int i = 1; i < optionalIndexesInfo.size(); i++) {
+                newIdxCost = newIdxCost.costAdd(indexCosts.get(i)); // I0 + I1; I0 + I1 + I2
+                currentCost = newIdxCost.costAdd(dataCosts.get(i)); // I0 + I1 + D01; I0 + I1 + I2 + D012
+                if (currentCost.costLT(opCost)) { // save this cost and try adding one more index
+                    opCost = currentCost;
+                } else {
+                    // set the selectivites of the indexes not picked to be -1.0, so we can set
+                    // the skp index annotations correctly
+                    for (int j = i; j < optionalIndexesInfo.size(); j++) {
+                        optionalIndexesInfo.get(j).second = -1.0;
+                    }
+                    break; // can't get any cheaper.
                 }
             }
         }
+
+        // opCost is now the total cost of the indexes chosen along with the associated data scan cost.
+        if (opCost.costGT(this.cheapestPlanCost)) { // cheapest plan cost is the data scan cost.
+            for (int j = 0; j < optionalIndexesInfo.size(); j++) {
+                optionalIndexesInfo.get(j).second = -1.0; // remove all indexes from consideration.
+            }
+        }
+
+        totalCost = opCost.costAdd(mandatoryIndexesCost); // cost of all the indexes chosen
+        if (opCost.costLT(this.cheapestPlanCost) || mandatoryIndexesInfo.size() > 0) {
+            PlanNode pn = new PlanNode(allPlans.size(), joinEnum);
+            pn.setJoinNode(this);
+            pn.setDatasetName(getDatasetNames().get(0));
+            pn.setEmptyTupleSourceOp(this.correspondingEmptyTupleSourceOp);
+            pn.setLeftJoinIndex(this.jnArrayIndex);
+            pn.setRightJoinIndex(JoinNode.NO_JN);
+            pn.setLeftPlanIndex(PlanNode.NO_PLAN); // There ane no plans below this plan.
+            pn.setRightPlanIndex(PlanNode.NO_PLAN); // There ane no plans below this plan.
+            pn.setOpCost(totalCost);
+            pn.setScanMethod(PlanNode.ScanMethod.INDEX_SCAN);
+            pn.setTotalCost(totalCost);
+
+            allPlans.add(pn);
+            this.planIndexesArray.add(pn.allPlansIndex);
+            this.cheapestPlanCost = totalCost; // in the presence of mandatory indexes, this may not be the cheapest plan! But we have no choice!
+            this.cheapestPlanIndex = pn.allPlansIndex;
+        }
     }
 
     private SelectOperator copySelExprsAndSetTrue(List<ILogicalExpression> selExprs, List<SelectOperator> selOpers,
@@ -440,7 +659,7 @@
         return changes;
     }
 
-    public void addIndexAccessPlans(ILogicalOperator leafInput) throws AlgebricksException {
+    protected void addIndexAccessPlans(ILogicalOperator leafInput) throws AlgebricksException {
         IntroduceSelectAccessMethodRule tmp = new IntroduceSelectAccessMethodRule();
         List<Pair<IAccessMethod, Index>> chosenIndexes = new ArrayList<>();
         Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs = new TreeMap<>();
@@ -464,7 +683,7 @@
         }
     }
 
-    protected int buildHashJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
+    private int buildHashJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
             HashJoinExpressionAnnotation hintHashJoin) {
         List<PlanNode> allPlans = joinEnum.allPlans;
         PlanNode pn;
@@ -500,11 +719,11 @@
             if (this.cheapestPlanIndex == PlanNode.NO_PLAN || totalCost.costLT(this.cheapestPlanCost)
                     || hintHashJoin != null) {
                 pn = new PlanNode(allPlans.size(), joinEnum);
-                pn.jn = this;
-                pn.jnIndexes[0] = leftJn.jnArrayIndex;
-                pn.jnIndexes[1] = rightJn.jnArrayIndex;
-                pn.planIndexes[0] = leftPlan;
-                pn.planIndexes[1] = rightPlan;
+                pn.setJoinNode(this);
+                pn.setLeftJoinIndex(leftJn.jnArrayIndex);
+                pn.setRightJoinIndex(rightJn.jnArrayIndex);
+                pn.setLeftPlanIndex(leftPlan);
+                pn.setRightPlanIndex(rightPlan);
                 pn.joinOp = PlanNode.JoinMethod.HYBRID_HASH_JOIN; // need to check that all the conditions have equality predicates ONLY.
                 pn.joinHint = hintHashJoin;
                 pn.side = HashJoinExpressionAnnotation.BuildSide.RIGHT;
@@ -522,7 +741,7 @@
         return PlanNode.NO_PLAN;
     }
 
-    protected int buildBroadcastHashJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
+    private int buildBroadcastHashJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
             BroadcastExpressionAnnotation hintBroadcastHashJoin) {
         List<PlanNode> allPlans = joinEnum.allPlans;
         PlanNode pn;
@@ -559,11 +778,11 @@
             if (this.cheapestPlanIndex == PlanNode.NO_PLAN || totalCost.costLT(this.cheapestPlanCost)
                     || hintBroadcastHashJoin != null) {
                 pn = new PlanNode(allPlans.size(), joinEnum);
-                pn.jn = this;
-                pn.jnIndexes[0] = leftJn.jnArrayIndex;
-                pn.jnIndexes[1] = rightJn.jnArrayIndex;
-                pn.planIndexes[0] = leftPlan;
-                pn.planIndexes[1] = rightPlan;
+                pn.setJoinNode(this);
+                pn.setLeftJoinIndex(leftJn.jnArrayIndex);
+                pn.setRightJoinIndex(rightJn.jnArrayIndex);
+                pn.setLeftPlanIndex(leftPlan);
+                pn.setRightPlanIndex(rightPlan);
                 pn.joinOp = PlanNode.JoinMethod.BROADCAST_HASH_JOIN; // need to check that all the conditions have equality predicates ONLY.
                 pn.joinHint = hintBroadcastHashJoin;
                 pn.side = HashJoinExpressionAnnotation.BuildSide.RIGHT;
@@ -582,7 +801,7 @@
         return PlanNode.NO_PLAN;
     }
 
-    protected int buildNLJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression nestedLoopJoinExpr,
+    private int buildNLJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression nestedLoopJoinExpr,
             IndexedNLJoinExpressionAnnotation hintNLJoin) throws AlgebricksException {
         // Build a nested loops plan, first check if it is possible
         // left right order must be preserved and right side should be a single data set
@@ -612,11 +831,11 @@
         if (this.cheapestPlanIndex == PlanNode.NO_PLAN || totalCost.costLT(this.cheapestPlanCost)
                 || hintNLJoin != null) {
             pn = new PlanNode(allPlans.size(), joinEnum);
-            pn.jn = this;
-            pn.jnIndexes[0] = leftJn.jnArrayIndex;
-            pn.jnIndexes[1] = rightJn.jnArrayIndex;
-            pn.planIndexes[0] = leftPlan;
-            pn.planIndexes[1] = rightPlan;
+            pn.setJoinNode(this);
+            pn.setLeftJoinIndex(leftJn.jnArrayIndex);
+            pn.setRightJoinIndex(rightJn.jnArrayIndex);
+            pn.setLeftPlanIndex(leftPlan);
+            pn.setRightPlanIndex(rightPlan);
             pn.joinOp = PlanNode.JoinMethod.INDEX_NESTED_LOOP_JOIN;
             pn.joinHint = hintNLJoin;
             pn.joinExpr = nestedLoopJoinExpr; // save it so can be used to add the NESTED annotation in getNewTree.
@@ -631,7 +850,7 @@
         return PlanNode.NO_PLAN;
     }
 
-    protected int buildCPJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
+    private int buildCPJoinPlan(JoinNode leftJn, JoinNode rightJn, ILogicalExpression hashJoinExpr,
             ILogicalExpression nestedLoopJoinExpr) {
         // Now build a cartesian product nested loops plan
         List<PlanNode> allPlans = joinEnum.allPlans;
@@ -668,11 +887,11 @@
         totalCost = cpCost.costAdd(rightExchangeCost).costAdd(childCosts);
         if (this.cheapestPlanIndex == PlanNode.NO_PLAN || totalCost.costLT(this.cheapestPlanCost)) {
             pn = new PlanNode(allPlans.size(), joinEnum);
-            pn.jn = this;
-            pn.jnIndexes[0] = leftJn.jnArrayIndex;
-            pn.jnIndexes[1] = rightJn.jnArrayIndex;
-            pn.planIndexes[0] = leftPlan;
-            pn.planIndexes[1] = rightPlan;
+            pn.setJoinNode(this);
+            pn.setLeftJoinIndex(leftJn.jnArrayIndex);
+            pn.setRightJoinIndex(rightJn.jnArrayIndex);
+            pn.setLeftPlanIndex(leftPlan);
+            pn.setRightPlanIndex(rightPlan);
             pn.joinOp = PlanNode.JoinMethod.CARTESIAN_PRODUCT_JOIN;
             pn.joinExpr = Objects.requireNonNullElse(cpJoinExpr, ConstantExpression.TRUE);
             pn.opCost = cpCost;
@@ -1000,7 +1219,7 @@
         return sb.toString();
     }
 
-    public void printCostOfAllPlans(StringBuilder sb) {
+    protected void printCostOfAllPlans(StringBuilder sb) {
         List<PlanNode> allPlans = joinEnum.allPlans;
         ICost minCost = joinEnum.getCostHandle().maxCost();
         for (int planIndex : planIndexesArray) {
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/PlanNode.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/PlanNode.java
index 1e91347..7ccd435 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/PlanNode.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/PlanNode.java
@@ -29,33 +29,33 @@
 
 public class PlanNode {
 
-    public static int NO_PLAN = -1;
+    protected static int NO_PLAN = -1;
 
     private final JoinEnum joinEnum;
-    int allPlansIndex;
-    int[] planIndexes;
-    int[] jnIndexes;
-    JoinNode jn;
-    String datasetName;
-    ICost opCost;
-    ICost totalCost;
-    ICost leftExchangeCost;
-    ICost rightExchangeCost;
-    JoinMethod joinOp;
-    IExpressionAnnotation joinHint;
+    protected int allPlansIndex;
+    protected int[] planIndexes;
+    protected int[] jnIndexes;
+    protected JoinNode jn;
+    protected String datasetName;
+    protected ICost opCost;
+    protected ICost totalCost;
+    protected ICost leftExchangeCost;
+    protected ICost rightExchangeCost;
+    protected JoinMethod joinOp;
+    protected IExpressionAnnotation joinHint;
     // Used to indicate which side to build for HJ and which side to broadcast for BHJ.
-    HashJoinExpressionAnnotation.BuildSide side;
-    ScanMethod scanOp;
-    ILogicalExpression joinExpr;
-    DataSourceScanOperator correspondingDataSourceScanOp;
-    EmptyTupleSourceOperator correspondingEmptyTupleSourceOp;
+    protected HashJoinExpressionAnnotation.BuildSide side;
+    protected ScanMethod scanOp;
+    protected ILogicalExpression joinExpr;
+    private DataSourceScanOperator correspondingDataSourceScanOp;
+    protected EmptyTupleSourceOperator correspondingEmptyTupleSourceOp;
 
     public enum ScanMethod {
         INDEX_SCAN,
         TABLE_SCAN
     }
 
-    public enum JoinMethod {
+    protected enum JoinMethod {
         HYBRID_HASH_JOIN,
         BROADCAST_HASH_JOIN,
         INDEX_NESTED_LOOP_JOIN,
@@ -73,14 +73,10 @@
         return allPlansIndex;
     }
 
-    public int[] getPlanIndexes() {
+    private int[] getPlanIndexes() {
         return planIndexes;
     }
 
-    public int getLeftPlanIndex() {
-        return planIndexes[0];
-    }
-
     public PlanNode getLeftPlanNode() {
         if (planIndexes[0] == NO_PLAN) {
             return null;
@@ -95,47 +91,51 @@
         return joinEnum.allPlans.get(planIndexes[1]);
     }
 
-    public JoinNode getJoinNode() {
+    protected JoinNode getJoinNode() {
         return jn;
     }
 
-    public void setJoinNode(JoinNode jn) {
+    protected void setJoinNode(JoinNode jn) {
         this.jn = jn;
     }
 
-    public int getRightPlanIndex() {
-        return planIndexes[1];
-    }
-
-    public void setRightPlanIndex(int index) {
-        this.planIndexes[1] = index;
-    }
-
     public int getLeftJoinIndex() {
         return jnIndexes[0];
     }
 
-    public void setLeftPlanIndex(int index) {
-        this.planIndexes[0] = index;
-    }
-
-    public void setLeftJoinIndex(int index) {
+    protected void setLeftJoinIndex(int index) {
         this.jnIndexes[0] = index;
     }
 
-    public void setRightJoinIndex(int index) {
-        this.jnIndexes[1] = index;
-    }
-
     public int getRightJoinIndex() {
         return jnIndexes[1];
     }
 
+    protected void setRightJoinIndex(int index) {
+        this.jnIndexes[1] = index;
+    }
+
+    protected int getLeftPlanIndex() {
+        return planIndexes[0];
+    }
+
+    protected void setLeftPlanIndex(int index) {
+        this.planIndexes[0] = index;
+    }
+
+    protected int getRightPlanIndex() {
+        return planIndexes[1];
+    }
+
+    protected void setRightPlanIndex(int index) {
+        this.planIndexes[1] = index;
+    }
+
     public boolean IsScanNode() {
         return getLeftPlanIndex() == NO_PLAN && getRightPlanIndex() == NO_PLAN;
     }
 
-    public boolean IsJoinNode() {
+    protected boolean IsJoinNode() {
         return getLeftPlanIndex() != NO_PLAN && getRightPlanIndex() != NO_PLAN;
     }
 
@@ -152,23 +152,23 @@
         return new Pair<>("", "");
     }
 
-    public String getDatasetName() {
+    private String getDatasetName() {
         return datasetName;
     }
 
-    public void setDatasetName(String dsName) {
+    protected void setDatasetName(String dsName) {
         this.datasetName = dsName;
     }
 
-    public DataSourceScanOperator getDataSourceScanOp() {
+    private DataSourceScanOperator getDataSourceScanOp() {
         return correspondingDataSourceScanOp; // This applies only to singleDataSetPlans
     }
 
-    public EmptyTupleSourceOperator getEmptyTupleSourceOp() {
+    protected EmptyTupleSourceOperator getEmptyTupleSourceOp() {
         return correspondingEmptyTupleSourceOp; // This applies only to singleDataSetPlans
     }
 
-    public void setEmptyTupleSourceOp(EmptyTupleSourceOperator emptyTupleSourceOp) {
+    protected void setEmptyTupleSourceOp(EmptyTupleSourceOperator emptyTupleSourceOp) {
         this.correspondingEmptyTupleSourceOp = emptyTupleSourceOp; // This applies only to singleDataSetPlans
     }
 
@@ -176,11 +176,11 @@
         return opCost;
     }
 
-    public void setOpCost(ICost cost) {
+    protected void setOpCost(ICost cost) {
         this.opCost = cost;
     }
 
-    public double computeOpCost() {
+    protected double computeOpCost() {
         return opCost.computeTotalCost();
     }
 
@@ -188,7 +188,7 @@
         return totalCost;
     }
 
-    public void setTotalCost(ICost tc) {
+    protected void setTotalCost(ICost tc) {
         this.totalCost = tc;
     }
 
@@ -200,7 +200,7 @@
         return rightExchangeCost;
     }
 
-    public double computeTotalCost() {
+    protected double computeTotalCost() {
         return totalCost.computeTotalCost();
     }
 
@@ -208,11 +208,11 @@
         return scanOp;
     }
 
-    public void setScanMethod(ScanMethod sm) {
+    protected void setScanMethod(ScanMethod sm) {
         this.scanOp = sm;
     }
 
-    public JoinMethod getJoinOp() {
+    protected JoinMethod getJoinOp() {
         return joinOp;
     }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/Stats.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/Stats.java
index b3c4876..785d56b 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/Stats.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/Stats.java
@@ -20,13 +20,23 @@
 package org.apache.asterix.optimizer.rules.cbo;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.compiler.provider.IRuleSetFactory;
 import org.apache.asterix.metadata.declared.DataSource;
 import org.apache.asterix.metadata.declared.DataSourceId;
 import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.metadata.declared.SampleDataSource;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.om.base.AInt64;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.functions.BuiltinFunctionInfo;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.optimizer.base.AnalysisUtil;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
@@ -35,29 +45,34 @@
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.JoinProductivityAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.PredicateCardinalityAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
 import org.apache.hyracks.api.exceptions.ErrorCode;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 import org.apache.hyracks.api.exceptions.Warning;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
 public class Stats {
-
-    public double SELECTIVITY_FOR_SECONDARY_INDEX_SELECTION = 0.1;
-
-    protected IOptimizationContext optCtx;
-    protected JoinEnum joinEnum;
+    private static final Logger LOGGER = LogManager.getLogger();
+    private final IOptimizationContext optCtx;
+    private final JoinEnum joinEnum;
 
     public Stats(IOptimizationContext context, JoinEnum joinE) {
         optCtx = context;
         joinEnum = joinE;
     }
 
-    public DataverseName findDataverseName(DataSourceScanOperator scanOp) {
+    protected DataverseName findDataverseName(DataSourceScanOperator scanOp) {
         if (scanOp == null) {
             // this should rarely happen (IN lists may cause this)
             return null;
@@ -66,7 +81,7 @@
         return dsid.getDataverseName();
     }
 
-    public Index findSampleIndex(DataSourceScanOperator scanOp, IOptimizationContext context)
+    protected Index findSampleIndex(DataSourceScanOperator scanOp, IOptimizationContext context)
             throws AlgebricksException {
         DataverseName dataverseName = findDataverseName(scanOp);
         DataSource ds = (DataSource) scanOp.getDataSource();
@@ -130,7 +145,7 @@
 
     // The expression we get may not be a base condition. It could be comprised of ors and ands and nots. So have to
     //recursively find the overall selectivity.
-    protected double getSelectivityFromAnnotation(AbstractFunctionCallExpression afcExpr, boolean join)
+    private double getSelectivityFromAnnotation(AbstractFunctionCallExpression afcExpr, boolean join)
             throws AlgebricksException {
         double sel = 1.0;
 
@@ -166,7 +181,7 @@
             }
         }
 
-        double s = 1.0;
+        double s;
         PredicateCardinalityAnnotation pca = afcExpr.getAnnotation(PredicateCardinalityAnnotation.class);
         if (pca != null) {
             s = pca.getSelectivity();
@@ -192,7 +207,8 @@
         return sel;
     }
 
-    public double getSelectivityFromAnnotationMain(ILogicalExpression leExpr, boolean join) throws AlgebricksException {
+    protected double getSelectivityFromAnnotationMain(ILogicalExpression leExpr, boolean join)
+            throws AlgebricksException {
         double sel = 1.0;
 
         if (leExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
@@ -225,7 +241,7 @@
         return sel;
     }
 
-    protected double getSelectivity(SubplanOperator subplanOp) throws AlgebricksException {
+    private double getSelectivity(SubplanOperator subplanOp) throws AlgebricksException {
         double sel = 1.0; // safe to return 1 if there is no annotation
         //ILogicalOperator op = subplanOp;
         ILogicalOperator op = subplanOp.getNestedPlans().get(0).getRoots().get(0).getValue();
@@ -242,4 +258,229 @@
         }
         return sel;
     }
+
+    private int countOps(ILogicalOperator op, LogicalOperatorTag tag) {
+        int count = 0;
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(tag)) {
+                count++;
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+        return count;
+    }
+
+    private SubplanOperator findSubplanWithExpr(ILogicalOperator op, ILogicalExpression exp)
+            throws AlgebricksException {
+        /*private final */ ContainsExpressionVisitor visitor = new ContainsExpressionVisitor();
+        SubplanOperator subOp;
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                subOp = (SubplanOperator) op;
+                ILogicalOperator nextOp = subOp.getNestedPlans().get(0).getRoots().get(0).getValue();
+
+                while (nextOp != null) {
+                    visitor.setExpression(exp);
+                    if (nextOp.acceptExpressionTransform(visitor)) {
+                        return subOp;
+                    }
+
+                    if (nextOp.getInputs().isEmpty()) {
+                        break;
+                    }
+                    nextOp = nextOp.getInputs().get(0).getValue();
+                }
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+        return null;
+    }
+
+    private List<ILogicalExpression> storeSubplanSelectsAndMakeThemTrue(ILogicalOperator op) {
+        List<ILogicalExpression> selExprs = new ArrayList<>();
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                if (op.getInputs().get(0).getValue().getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                    SelectOperator selOp = (SelectOperator) op;
+                    selExprs.add(selOp.getCondition().getValue());
+                    selOp.getCondition().setValue(ConstantExpression.TRUE);
+                }
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+        return selExprs;
+    }
+
+    private void restoreAllSubplanSelects(ILogicalOperator op, List<ILogicalExpression> selExprs) {
+        int i = 0;
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                if (op.getInputs().get(0).getValue().getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                    SelectOperator selOp = (SelectOperator) op;
+                    selOp.getCondition().setValue(selExprs.get(i));
+                    i++;
+                }
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+    }
+
+    // For the SubOp subplan, leave the selection condition the same but all other selects and subsplan selects should be marked true
+    private List<ILogicalExpression> storeSubplanSelectsAndMakeThemTrue(ILogicalOperator op, SubplanOperator subOp) {
+        List<ILogicalExpression> selExprs = new ArrayList<>();
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                ILogicalOperator op2 = op.getInputs().get(0).getValue();
+                if (op2.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                    SubplanOperator subOp2 = (SubplanOperator) op2;
+                    if (subOp2 != subOp) {
+                        SelectOperator selOp = (SelectOperator) op;
+                        selExprs.add(selOp.getCondition().getValue());
+                        selOp.getCondition().setValue(ConstantExpression.TRUE);
+                    } // else leave expression as is.
+                } else { // a non subplan select
+                    SelectOperator selOp = (SelectOperator) op;
+                    selExprs.add(selOp.getCondition().getValue());
+                    selOp.getCondition().setValue(ConstantExpression.TRUE);
+                }
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+        return selExprs;
+    }
+
+    private void restoreAllSubplanSelectConditions(ILogicalOperator op, List<ILogicalExpression> selExprs,
+            SubplanOperator subOp) {
+        int i = 0;
+        while (op != null && op.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
+            if (op.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                ILogicalOperator op2 = op.getInputs().get(0).getValue();
+                if (op2.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                    SubplanOperator subOp2 = (SubplanOperator) op2;
+                    if (subOp2 != subOp) {
+                        SelectOperator selOp = (SelectOperator) op;
+                        selOp.getCondition().setValue(selExprs.get(i));
+                        i++;
+                    }
+                } else { // a non subplan select
+                    SelectOperator selOp = (SelectOperator) op;
+                    selOp.getCondition().setValue(selExprs.get(i));
+                }
+            }
+            op = op.getInputs().get(0).getValue();
+        }
+    }
+
+    protected double findSelectivityForThisPredicate(SelectOperator selOp, AbstractFunctionCallExpression exp,
+            double datasetCard) throws AlgebricksException {
+        // replace the SelOp.condition with the new exp and replace it at the end
+        // The Selop here is the start of the leafInput.
+
+        ILogicalOperator parent = joinEnum.findDataSourceScanOperatorParent(selOp);
+        DataSourceScanOperator scanOp = (DataSourceScanOperator) parent.getInputs().get(0).getValue();
+
+        if (scanOp == null) {
+            return 1.0; // what happens to the cards and sizes then? this may happen in case of in lists
+        }
+
+        Index index = findSampleIndex(scanOp, optCtx);
+        if (index == null) {
+            return 1.0;
+        }
+
+        Index.SampleIndexDetails idxDetails = (Index.SampleIndexDetails) index.getIndexDetails();
+        double origDatasetCard = idxDetails.getSourceCardinality();
+        // origDatasetCard must be equal to datasetCard. So we do not need datasetCard passed in here. VIJAY check if
+        // this parameter can be removed.
+        double sampleCard = Math.min(idxDetails.getSampleCardinalityTarget(), origDatasetCard);
+        if (sampleCard == 0) {
+            sampleCard = 1;
+            IWarningCollector warningCollector = optCtx.getWarningCollector();
+            if (warningCollector.shouldWarn()) {
+                warningCollector.warn(Warning.of(scanOp.getSourceLocation(),
+                        org.apache.asterix.common.exceptions.ErrorCode.SAMPLE_HAS_ZERO_ROWS));
+            }
+        }
+
+        // replace the dataScanSourceOperator with the sampling source
+        SampleDataSource sampledatasource = joinEnum.getSampleDataSource(scanOp);
+        DataSourceScanOperator deepCopyofScan =
+                (DataSourceScanOperator) OperatorManipulationUtil.bottomUpCopyOperators(scanOp);
+        deepCopyofScan.setDataSource(sampledatasource);
+
+        int numSubplans = countOps(selOp, LogicalOperatorTag.SUBPLAN);
+
+        List<List<IAObject>> result;
+
+        // insert this in place of the scandatasourceOp.
+        parent.getInputs().get(0).setValue(deepCopyofScan);
+        if (numSubplans == 0) { // just switch the predicates; the simplest case. There should be no other case if subplans were canonical
+            ILogicalExpression saveExprs = selOp.getCondition().getValue();
+            selOp.getCondition().setValue(exp);
+            result = runSamplingQuery(optCtx, selOp);
+            selOp.getCondition().setValue(saveExprs);
+        } else {
+            int numSelects = countOps(selOp, LogicalOperatorTag.SELECT);
+            int nonSubplanSelects = numSelects - numSubplans;
+
+            if (numSubplans == 1 && nonSubplanSelects == 0) {
+                result = runSamplingQuery(optCtx, selOp); // no need to switch anything
+            } else { // the painful part; have to find where exp that is passed in is coming from. >= 1 and >= 1 case
+                // Assumption is that there is exaclty one select condition above each subplan.
+                // This was ensured before this routine is called
+                SubplanOperator subOp = findSubplanWithExpr(selOp, exp);
+                if (subOp == null) { // the exp is not coming from a subplan
+                    List<ILogicalExpression> selExprs;
+                    selExprs = storeSubplanSelectsAndMakeThemTrue(selOp); // all these will be marked true and will be resorted later.
+                    result = runSamplingQuery(optCtx, selOp);
+                    restoreAllSubplanSelects(selOp, selExprs);
+                } else { // found the matching subPlan oper. Only keep this predicate and make all others true and then restore them.
+                    List<ILogicalExpression> selExprs;
+                    selExprs = storeSubplanSelectsAndMakeThemTrue(selOp, subOp); // all these will be marked true and will be resorted later.
+                    result = runSamplingQuery(optCtx, selOp);
+                    restoreAllSubplanSelectConditions(selOp, selExprs, subOp);
+                }
+            }
+        }
+        // switch  the scanOp back
+        parent.getInputs().get(0).setValue(scanOp);
+
+        double predicateCardinality = (double) ((AInt64) result.get(0).get(0)).getLongValue();
+        if (predicateCardinality == 0.0) {
+            predicateCardinality = 0.0001 * idxDetails.getSampleCardinalityTarget();
+        }
+        double sel = (double) predicateCardinality / sampleCard;
+        return sel;
+    }
+
+    protected List<List<IAObject>> runSamplingQuery(IOptimizationContext ctx, ILogicalOperator logOp)
+            throws AlgebricksException {
+        LOGGER.info("***running sample query***");
+
+        IOptimizationContext newCtx = ctx.getOptimizationContextFactory().cloneOptimizationContext(ctx);
+
+        ILogicalOperator newScanOp = OperatorManipulationUtil.bottomUpCopyOperators(logOp);
+
+        List<Mutable<ILogicalExpression>> aggFunArgs = new ArrayList<>(1);
+        aggFunArgs.add(new MutableObject<>(ConstantExpression.TRUE));
+        BuiltinFunctionInfo countFn = BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.COUNT);
+        AggregateFunctionCallExpression aggExpr = new AggregateFunctionCallExpression(countFn, false, aggFunArgs);
+
+        List<Mutable<ILogicalExpression>> aggExprList = new ArrayList<>(1);
+        aggExprList.add(new MutableObject<>(aggExpr));
+
+        List<LogicalVariable> aggVarList = new ArrayList<>(1);
+        LogicalVariable aggVar = newCtx.newVar();
+        aggVarList.add(aggVar);
+
+        AggregateOperator newAggOp = new AggregateOperator(aggVarList, aggExprList);
+        newAggOp.getInputs().add(new MutableObject<>(newScanOp));
+
+        Mutable<ILogicalOperator> newAggOpRef = new MutableObject<>(newAggOp);
+
+        OperatorPropertiesUtil.typeOpRec(newAggOpRef, newCtx);
+        LOGGER.info("***returning from sample query***");
+
+        return AnalysisUtil.runQuery(newAggOpRef, Arrays.asList(aggVar), newCtx, IRuleSetFactory.RuleSetKind.SAMPLING);
+    }
 }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java
index 485e359..2658804 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/AnalyzingTestExecutor.java
@@ -68,6 +68,7 @@
                 analyzeStmt.append(dv);
             }
             analyzeStmt.append(ds);
+            analyzeStmt.append(" WITH {\"sample-seed\": \"1000\"}");
             analyzeStmt.append(";");
             InputStream resultStream = executeQueryService(analyzeStmt.toString(), getQueryServiceUri(SQLPP),
                     TestCaseContext.OutputFormat.CLEAN_JSON);
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query1.plan
new file mode 100644
index 0000000..498ee33
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query1.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- DATASOURCE_SCAN (test.KSI)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query2.plan
new file mode 100644
index 0000000..498ee33
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-indexes/query2.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- DATASOURCE_SCAN (test.KSI)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/query7.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/query7.plan
new file mode 100644
index 0000000..9e577d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/query7.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- ASSIGN  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- ASSIGN  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- DATASOURCE_SCAN (TestDataverse.Dataset1)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-1/query1.plan
new file mode 100644
index 0000000..1abd71a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-1/query1.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-2/query1.plan
new file mode 100644
index 0000000..1abd71a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-2/query1.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query1.plan
new file mode 100644
index 0000000..c5a8718
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query1.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query2.plan
new file mode 100644
index 0000000..c5a8718
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/multiple-quantifiers/use-case-3/query2.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-1/query1.plan
new file mode 100644
index 0000000..1abd71a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-1/query1.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-2/query1.plan
new file mode 100644
index 0000000..1abd71a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-2/query1.plan
@@ -0,0 +1,21 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query1.plan
new file mode 100644
index 0000000..c5a8718
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query1.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query2.plan
new file mode 100644
index 0000000..c5a8718
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/array-index/select-quantified-queries/use-case-3/query2.plan
@@ -0,0 +1,22 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- DATASOURCE_SCAN (TestYelp.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-10.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-10.plan
new file mode 100644
index 0000000..83ba8b8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-10.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$20(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-11.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-11.plan
new file mode 100644
index 0000000..9643deb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-11.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$20(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-12.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-12.plan
new file mode 100644
index 0000000..13375cb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-12.plan
@@ -0,0 +1,37 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- INTERSECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$29(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- REPLICATE  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$34(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_5k_10k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- REPLICATE  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-13.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-13.plan
new file mode 100644
index 0000000..83ba8b8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-13.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$20(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-14.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-14.plan
new file mode 100644
index 0000000..6eb942d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-14.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$20(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-15.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-15.plan
new file mode 100644
index 0000000..725e81e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-15.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$28(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-16.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-16.plan
new file mode 100644
index 0000000..00ba0d7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-16.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-17.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-17.plan
new file mode 100644
index 0000000..6e732ed
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-17.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$29(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-3.plan
new file mode 100644
index 0000000..9643deb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-3.plan
@@ -0,0 +1,19 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$20(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-4.plan
new file mode 100644
index 0000000..cf09754
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-4.plan
@@ -0,0 +1,37 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- INTERSECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$28(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_1k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- REPLICATE  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$32(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_2k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- REPLICATE  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-5.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-5.plan
new file mode 100644
index 0000000..13375cb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-5.plan
@@ -0,0 +1,37 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- INTERSECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$29(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- REPLICATE  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$34(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_5k_10k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- REPLICATE  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-7.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-7.plan
index 86e54c9..00ba0d7 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-7.plan
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-7.plan
@@ -1,12 +1,19 @@
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  -- SORT_MERGE_EXCHANGE [$$23(ASC) ]  |PARTITIONED|
-    -- STABLE_SORT [$$23(ASC)]  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
       -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
         -- STREAM_PROJECT  |PARTITIONED|
           -- STREAM_SELECT  |PARTITIONED|
             -- ASSIGN  |PARTITIONED|
               -- STREAM_PROJECT  |PARTITIONED|
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  -- DATASOURCE_SCAN (test.tenk)  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                      -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-8.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-8.plan
new file mode 100644
index 0000000..7e83ccb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-8.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- INTERSECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_2k_5k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-9.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-9.plan
new file mode 100644
index 0000000..7e83ccb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/btree-index-selection/hints-use-index/hints-use-index-9.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- SORT_MERGE_EXCHANGE [$$24(ASC) ]  |PARTITIONED|
+    -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- INTERSECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_1k_2k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.tenk.idx_2k_5k)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/hash-join-with-redundant-variable/hash-join-with-redundant-variable.1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/hash-join-with-redundant-variable/hash-join-with-redundant-variable.1.plan
index 65df3c2..d44143c 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/hash-join-with-redundant-variable/hash-join-with-redundant-variable.1.plan
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/hash-join-with-redundant-variable/hash-join-with-redundant-variable.1.plan
@@ -4,8 +4,8 @@
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
         -- AGGREGATE  |PARTITIONED|
           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-            -- HYBRID_HASH_JOIN [$$130][$$129]  |PARTITIONED|
-              -- HASH_PARTITION_EXCHANGE [$$130]  |PARTITIONED|
+            -- HYBRID_HASH_JOIN [$$137][$$136]  |PARTITIONED|
+              -- HASH_PARTITION_EXCHANGE [$$137]  |PARTITIONED|
                 -- STREAM_PROJECT  |PARTITIONED|
                   -- STREAM_SELECT  |PARTITIONED|
                     -- ASSIGN  |PARTITIONED|
@@ -14,49 +14,31 @@
                           -- DATASOURCE_SCAN (test.region)  |PARTITIONED|
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                               -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-              -- HASH_PARTITION_EXCHANGE [$$129]  |PARTITIONED|
+              -- HASH_PARTITION_EXCHANGE [$$136]  |PARTITIONED|
                 -- STREAM_PROJECT  |PARTITIONED|
                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                    -- HYBRID_HASH_JOIN [$$127, $$131][$$128, $$132]  |PARTITIONED|
-                      -- HASH_PARTITION_EXCHANGE [$$127, $$131]  |PARTITIONED|
+                    -- HYBRID_HASH_JOIN [$$124][$$123]  |PARTITIONED|
+                      -- HASH_PARTITION_EXCHANGE [$$124]  |PARTITIONED|
                         -- STREAM_PROJECT  |PARTITIONED|
                           -- ASSIGN  |PARTITIONED|
                             -- STREAM_PROJECT  |PARTITIONED|
                               -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                -- DATASOURCE_SCAN (test.lineitem)  |PARTITIONED|
+                                -- DATASOURCE_SCAN (test.nation)  |PARTITIONED|
                                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                     -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                      -- HASH_PARTITION_EXCHANGE [$$128, $$132]  |PARTITIONED|
+                      -- HASH_PARTITION_EXCHANGE [$$123]  |PARTITIONED|
                         -- STREAM_PROJECT  |PARTITIONED|
                           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                            -- HYBRID_HASH_JOIN [$$126][$$125]  |PARTITIONED|
-                              -- HASH_PARTITION_EXCHANGE [$$126]  |PARTITIONED|
-                                -- STREAM_PROJECT  |PARTITIONED|
-                                  -- STREAM_SELECT  |PARTITIONED|
-                                    -- STREAM_PROJECT  |PARTITIONED|
-                                      -- ASSIGN  |PARTITIONED|
-                                        -- STREAM_PROJECT  |PARTITIONED|
-                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                            -- DATASOURCE_SCAN (test.orders)  |PARTITIONED|
-                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                              -- HASH_PARTITION_EXCHANGE [$$125]  |PARTITIONED|
+                            -- HYBRID_HASH_JOIN [$$134, $$122][$$135, $$123]  |PARTITIONED|
+                              -- HASH_PARTITION_EXCHANGE [$$134, $$122]  |PARTITIONED|
                                 -- STREAM_PROJECT  |PARTITIONED|
                                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                    -- HYBRID_HASH_JOIN [$$117][$$116]  |PARTITIONED|
-                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- HYBRID_HASH_JOIN [$$139][$$138]  |PARTITIONED|
+                                      -- HASH_PARTITION_EXCHANGE [$$139]  |PARTITIONED|
                                         -- STREAM_PROJECT  |PARTITIONED|
                                           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                            -- HYBRID_HASH_JOIN [$$117][$$115]  |PARTITIONED|
-                                              -- HASH_PARTITION_EXCHANGE [$$117]  |PARTITIONED|
-                                                -- STREAM_PROJECT  |PARTITIONED|
-                                                  -- ASSIGN  |PARTITIONED|
-                                                    -- STREAM_PROJECT  |PARTITIONED|
-                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                        -- DATASOURCE_SCAN (test.nation)  |PARTITIONED|
-                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                                              -- HASH_PARTITION_EXCHANGE [$$115]  |PARTITIONED|
+                                            -- HYBRID_HASH_JOIN [$$132][$$133]  |PARTITIONED|
+                                              -- HASH_PARTITION_EXCHANGE [$$132]  |PARTITIONED|
                                                 -- STREAM_PROJECT  |PARTITIONED|
                                                   -- ASSIGN  |PARTITIONED|
                                                     -- STREAM_PROJECT  |PARTITIONED|
@@ -64,11 +46,29 @@
                                                         -- DATASOURCE_SCAN (test.customer)  |PARTITIONED|
                                                           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                                             -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                                      -- HASH_PARTITION_EXCHANGE [$$116]  |PARTITIONED|
+                                              -- HASH_PARTITION_EXCHANGE [$$133]  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- STREAM_SELECT  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- DATASOURCE_SCAN (test.orders)  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                      -- HASH_PARTITION_EXCHANGE [$$138]  |PARTITIONED|
                                         -- STREAM_PROJECT  |PARTITIONED|
                                           -- ASSIGN  |PARTITIONED|
                                             -- STREAM_PROJECT  |PARTITIONED|
                                               -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                                -- DATASOURCE_SCAN (test.supplier)  |PARTITIONED|
+                                                -- DATASOURCE_SCAN (test.lineitem)  |PARTITIONED|
                                                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                                     -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                              -- HASH_PARTITION_EXCHANGE [$$135, $$123]  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- DATASOURCE_SCAN (test.supplier)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance-check.plan
new file mode 100644
index 0000000..334be33
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance-check.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$19(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance.plan
new file mode 100644
index 0000000..334be33
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-edit-distance.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$19(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-edit-distance.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-edit-distance.plan
new file mode 100644
index 0000000..0589004
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-edit-distance.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-jaccard.plan
new file mode 100644
index 0000000..b415aa0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-fuzzyeq-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard-check.plan
new file mode 100644
index 0000000..76c3b3b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard-check.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard.plan
new file mode 100644
index 0000000..76c3b3b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ngram-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check.plan
new file mode 100644
index 0000000..971ae76
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check.plan
@@ -0,0 +1,16 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$18(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- STREAM_SELECT  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check_ps.plan
new file mode 100644
index 0000000..a88b1e8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance-check_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$18(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance.plan
new file mode 100644
index 0000000..971ae76
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance.plan
@@ -0,0 +1,16 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$18(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- STREAM_SELECT  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance_ps.plan
new file mode 100644
index 0000000..a88b1e8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-edit-distance_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$18(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$18(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$22(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance.plan
new file mode 100644
index 0000000..ad32edd
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance.plan
@@ -0,0 +1,16 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$17(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$17(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- STREAM_SELECT  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan
new file mode 100644
index 0000000..7a151d5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$17(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$17(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-jaccard.plan
new file mode 100644
index 0000000..a9465ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-fuzzyeq-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$19(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard-check.plan
new file mode 100644
index 0000000..56b3d06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard-check.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard.plan
new file mode 100644
index 0000000..56b3d06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/olist-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-fuzzyeq-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-fuzzyeq-jaccard.plan
new file mode 100644
index 0000000..a9465ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-fuzzyeq-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$19(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard-check.plan
new file mode 100644
index 0000000..56b3d06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard-check.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard.plan
new file mode 100644
index 0000000..56b3d06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/ulist-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-fuzzyeq-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-fuzzyeq-jaccard.plan
new file mode 100644
index 0000000..f4a3522
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-fuzzyeq-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.keyword_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard-check.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard-check.plan
new file mode 100644
index 0000000..043f7d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard-check.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.keyword_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard.plan
new file mode 100644
index 0000000..043f7d4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-basic/word-jaccard.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$21(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.keyword_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_01.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_01.plan
new file mode 100644
index 0000000..6d1d661
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_01.plan
@@ -0,0 +1,15 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- STREAM_SELECT  |PARTITIONED|
+        -- ASSIGN  |PARTITIONED|
+          -- STREAM_PROJECT  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- STABLE_SORT [$$45(ASC)]  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_02.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_02.plan
new file mode 100644
index 0000000..6d1d661
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let-panic-nopanic_02.plan
@@ -0,0 +1,15 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- STREAM_SELECT  |PARTITIONED|
+        -- ASSIGN  |PARTITIONED|
+          -- STREAM_PROJECT  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- STABLE_SORT [$$45(ASC)]  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let.plan
new file mode 100644
index 0000000..f1cac23
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-let.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$30(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-substring.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-substring.plan
new file mode 100644
index 0000000..2b26d45
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-edit-distance-check-substring.plan
@@ -0,0 +1,16 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_SELECT  |PARTITIONED|
+          -- STREAM_PROJECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-let.plan
new file mode 100644
index 0000000..06ac713
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-let.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$32(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-multi-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-multi-let.plan
new file mode 100644
index 0000000..1d0f0a4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ngram-jaccard-check-multi-let.plan
@@ -0,0 +1,17 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_SELECT  |PARTITIONED|
+          -- STREAM_PROJECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$58(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.ngram_index)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let.plan
new file mode 100644
index 0000000..5b54be6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let.plan
@@ -0,0 +1,16 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$29(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$29(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- STREAM_SELECT  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$33(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let_ps.plan
new file mode 100644
index 0000000..e8039ef
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-edit-distance-check-let_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$29(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$29(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$33(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$33(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-jaccard-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-jaccard-check-let.plan
new file mode 100644
index 0000000..c597083
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/olist-jaccard-check-let.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ulist-jaccard-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ulist-jaccard-check-let.plan
new file mode 100644
index 0000000..c597083
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/ulist-jaccard-check-let.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-let.plan
new file mode 100644
index 0000000..c30299a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-let.plan
@@ -0,0 +1,13 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_SELECT  |PARTITIONED|
+      -- STREAM_PROJECT  |PARTITIONED|
+        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+          -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STABLE_SORT [$$32(ASC)]  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.keyword_index)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-multi-let.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-multi-let.plan
new file mode 100644
index 0000000..99e1895
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/inverted-index-complex/word-jaccard-check-multi-let.plan
@@ -0,0 +1,17 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_SELECT  |PARTITIONED|
+          -- STREAM_PROJECT  |PARTITIONED|
+            -- ASSIGN  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  -- BTREE_SEARCH (test.DBLP.DBLP)  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- STABLE_SORT [$$58(ASC)]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.DBLP.keyword_index)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance-check_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance-check_ps.plan
new file mode 100644
index 0000000..7597fd2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance-check_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$20(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance_ps.plan
new file mode 100644
index 0000000..7597fd2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-edit-distance_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$20(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$20(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$25(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan
new file mode 100644
index 0000000..6e8c23d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-basic/olist-fuzzyeq-edit-distance_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$19(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$19(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$24(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-complex/olist-edit-distance-check-let_ps.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-complex/olist-edit-distance-check-let_ps.plan
new file mode 100644
index 0000000..5c43154
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/nested-index/inverted-index-complex/olist-edit-distance-check-let_ps.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+        -- STABLE_SORT [$$31(ASC)]  |PARTITIONED|
+          -- RANGE_PARTITION_EXCHANGE [$$31(ASC)]  |PARTITIONED|
+            -- FORWARD  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- REPLICATE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                -- AGGREGATE  |UNPARTITIONED|
+                  -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+                    -- AGGREGATE  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- REPLICATE  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STREAM_SELECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (test.Customers.Customers)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- LENGTH_PARTITIONED_INVERTED_INDEX_SEARCH (test.Customers.interests_index)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/query-ASTERIXDB-2700.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/query-ASTERIXDB-2700.plan
new file mode 100644
index 0000000..5c72fa2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_cbo/query-ASTERIXDB-2700.plan
@@ -0,0 +1,28 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- BTREE_SEARCH (bigfun.GleambookMessagesComposite.GleambookMessagesComposite)  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- STABLE_SORT [$$58(ASC), $$59(ASC)]  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (bigfun.GleambookMessagesComposite.authorIdIx)  |PARTITIONED|
+                              -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                -- ASSIGN  |PARTITIONED|
+                                  -- SORT_MERGE_EXCHANGE [$$48(ASC) ]  |PARTITIONED|
+                                    -- STABLE_SORT [$$48(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- STREAM_SELECT  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- DATASOURCE_SCAN (bigfun.GleambookUsersComposite)  |PARTITIONED|
+                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
index e7f6361..5994520 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
@@ -1,18 +1,18 @@
-distribute result [$$51] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+distribute result [$$51] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+  exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+    project ([$$51]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+        exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
         -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
           -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+            exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              join (eq($$55, $$58)) [cardinality: 2.1, op-cost: 2000000.0, total-cost: 1.1E7]
+              join (eq($$55, $$58)) [cardinality: 1000000.0, op-cost: 2000000.0, total-cost: 1.1E7]
               -- HYBRID_HASH_JOIN [$$58][$$55]  |PARTITIONED|
                 exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 7000000.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
index 554d42f..453c83d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
@@ -1,62 +1,62 @@
-distribute result [$$48] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+distribute result [$$51] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
   exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$48]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    project ([$$51]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$48] <- [{"n_nationkey": $$55, "s_nationkey": $$53, "c_nationkey": $$52}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
       -- ASSIGN  |PARTITIONED|
         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-        -- SORT_MERGE_EXCHANGE [$$55(ASC), $$53(ASC), $$52(ASC) ]  |PARTITIONED|
-          order (ASC, $$55) (ASC, $$53) (ASC, $$52) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-          -- STABLE_SORT [$$55(ASC), $$53(ASC), $$52(ASC)]  |PARTITIONED|
+        -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              join (eq($$52, $$55)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-              -- HYBRID_HASH_JOIN [$$55][$$52]  |PARTITIONED|
+              join (eq($$55, $$58)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- HYBRID_HASH_JOIN [$$58][$$55]  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (eq($$53, $$55)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                  -- HYBRID_HASH_JOIN [$$55][$$53]  |PARTITIONED|
+                  join (eq($$56, $$58)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- HYBRID_HASH_JOIN [$$58][$$56]  |PARTITIONED|
                     exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                    -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
-                      project ([$$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- HASH_PARTITION_EXCHANGE [$$58]  |PARTITIONED|
+                      project ([$$58]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          data-scan []<-[$$55, $$n] <- tpch.Nation [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          data-scan []<-[$$58, $$n] <- tpch.Nation [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- DATASOURCE_SCAN  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                               empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                               -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
                     exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                    -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
-                      project ([$$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                      project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
-                        assign [$$53] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ASSIGN  |PARTITIONED|
                           project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- STREAM_PROJECT  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              data-scan []<-[$$56, $$s] <- tpch.Supplier [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              data-scan []<-[$$59, $$s] <- tpch.Supplier [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                               -- DATASOURCE_SCAN  |PARTITIONED|
                                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                   empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                   -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                -- HASH_PARTITION_EXCHANGE [$$52]  |PARTITIONED|
-                  project ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
+                  project ([$$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                   -- STREAM_PROJECT  |PARTITIONED|
-                    assign [$$52] <- [$$c.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    assign [$$55] <- [$$c.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                     -- ASSIGN  |PARTITIONED|
                       project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          data-scan []<-[$$57, $$c] <- tpch.Customer [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- DATASOURCE_SCAN  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
index 554d42f..453c83d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
@@ -1,62 +1,62 @@
-distribute result [$$48] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+distribute result [$$51] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
   exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$48]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+    project ([$$51]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$48] <- [{"n_nationkey": $$55, "s_nationkey": $$53, "c_nationkey": $$52}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
       -- ASSIGN  |PARTITIONED|
         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-        -- SORT_MERGE_EXCHANGE [$$55(ASC), $$53(ASC), $$52(ASC) ]  |PARTITIONED|
-          order (ASC, $$55) (ASC, $$53) (ASC, $$52) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-          -- STABLE_SORT [$$55(ASC), $$53(ASC), $$52(ASC)]  |PARTITIONED|
+        -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+          -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              join (eq($$52, $$55)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-              -- HYBRID_HASH_JOIN [$$55][$$52]  |PARTITIONED|
+              join (eq($$55, $$58)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+              -- HYBRID_HASH_JOIN [$$58][$$55]  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (eq($$53, $$55)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                  -- HYBRID_HASH_JOIN [$$55][$$53]  |PARTITIONED|
+                  join (eq($$56, $$58)) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                  -- HYBRID_HASH_JOIN [$$58][$$56]  |PARTITIONED|
                     exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                    -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
-                      project ([$$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- HASH_PARTITION_EXCHANGE [$$58]  |PARTITIONED|
+                      project ([$$58]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          data-scan []<-[$$55, $$n] <- tpch.Nation [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          data-scan []<-[$$58, $$n] <- tpch.Nation [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- DATASOURCE_SCAN  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                               empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                               -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
                     exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                    -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
-                      project ([$$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                      project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
-                        assign [$$53] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                        assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ASSIGN  |PARTITIONED|
                           project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- STREAM_PROJECT  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              data-scan []<-[$$56, $$s] <- tpch.Supplier [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              data-scan []<-[$$59, $$s] <- tpch.Supplier [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                               -- DATASOURCE_SCAN  |PARTITIONED|
                                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                   empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                   -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                -- HASH_PARTITION_EXCHANGE [$$52]  |PARTITIONED|
-                  project ([$$52]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
+                  project ([$$55]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                   -- STREAM_PROJECT  |PARTITIONED|
-                    assign [$$52] <- [$$c.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                    assign [$$55] <- [$$c.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                     -- ASSIGN  |PARTITIONED|
                       project ([$$c]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- STREAM_PROJECT  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          data-scan []<-[$$57, $$c] <- tpch.Customer [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                          data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                           -- DATASOURCE_SCAN  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
index b11c296..e7816b3 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
@@ -1,22 +1,22 @@
-distribute result [$$51] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+distribute result [$$51] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+  exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+    project ([$$51]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+        exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
         -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
           -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+            exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$59, $$56, $$55]) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+              project ([$$59, $$56, $$55]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.1E7]
+                exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.1E7]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 2.1, op-cost: 2000000.0, total-cost: 1.1E7]
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 1000000.0, op-cost: 2000000.0, total-cost: 1.1E7]
                   -- HYBRID_HASH_JOIN [$$59, $$56][$$55, $$66]  |PARTITIONED|
                     exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 7000000.0]
                     -- HASH_PARTITION_EXCHANGE [$$59, $$56]  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
index c492146..3da3c3e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
@@ -1,64 +1,64 @@
-distribute result [$$48] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+distribute result [$$51] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+  exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$48]) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+    project ([$$51]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$48] <- [{"n_nationkey": $$56, "s_nationkey": $$53, "c_nationkey": $$52}] [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
-        -- SORT_MERGE_EXCHANGE [$$56(ASC), $$53(ASC), $$52(ASC) ]  |PARTITIONED|
-          order (ASC, $$56) (ASC, $$53) (ASC, $$52) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
-          -- STABLE_SORT [$$56(ASC), $$53(ASC), $$52(ASC)]  |PARTITIONED|
-            exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+        exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
+        -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
+          -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
+            exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$56, $$53, $$52]) [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+              project ([$$59, $$56, $$55]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 1.0E7]
+                exchange [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1.0E7]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$52, $$56), eq($$53, $$63))) [cardinality: 2.1, op-cost: 2000000.0, total-cost: 1.0E7]
-                  -- HYBRID_HASH_JOIN [$$56, $$53][$$52, $$63]  |PARTITIONED|
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 1000000.0, op-cost: 2000000.0, total-cost: 1.0E7]
+                  -- HYBRID_HASH_JOIN [$$59, $$56][$$55, $$66]  |PARTITIONED|
                     exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 6000000.0]
-                    -- HASH_PARTITION_EXCHANGE [$$56, $$53]  |PARTITIONED|
-                      project ([$$53, $$56]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 5000000.0]
+                    -- HASH_PARTITION_EXCHANGE [$$59, $$56]  |PARTITIONED|
+                      project ([$$56, $$59]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 5000000.0]
                       -- STREAM_PROJECT  |PARTITIONED|
                         exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 6000000.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          unnest-map [$$56, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$53, 1, $$53, true, true, true) [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 5000000.0]
+                          unnest-map [$$59, $$n] <- index-search("Nation", 0, "tpch", "Nation", true, true, 1, $$56, 1, $$56, true, true, true) [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 5000000.0]
                           -- BTREE_SEARCH  |PARTITIONED|
                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              order (ASC, $$53) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                              -- STABLE_SORT [$$53(ASC)]  |PARTITIONED|
+                              order (ASC, $$56) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                              -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
                                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
-                                -- HASH_PARTITION_EXCHANGE [$$53]  |PARTITIONED|
-                                  project ([$$53]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                -- HASH_PARTITION_EXCHANGE [$$56]  |PARTITIONED|
+                                  project ([$$56]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                   -- STREAM_PROJECT  |PARTITIONED|
-                                    assign [$$53] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
+                                    assign [$$56] <- [$$s.getField(3)] [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                     -- ASSIGN  |PARTITIONED|
                                       project ([$$s]) [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                       -- STREAM_PROJECT  |PARTITIONED|
                                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                          data-scan []<-[$$55, $$s] <- tpch.Supplier [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 1000000.0]
+                                          data-scan []<-[$$58, $$s] <- tpch.Supplier [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 1000000.0]
                                           -- DATASOURCE_SCAN  |PARTITIONED|
                                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                               empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                               -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
                     exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 2000000.0]
-                    -- HASH_PARTITION_EXCHANGE [$$52, $$63]  |PARTITIONED|
-                      assign [$$63] <- [$$52] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
+                    -- HASH_PARTITION_EXCHANGE [$$55, $$66]  |PARTITIONED|
+                      assign [$$66] <- [$$55] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
                       -- ASSIGN  |PARTITIONED|
-                        project ([$$52]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
+                        project ([$$55]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
                         -- STREAM_PROJECT  |PARTITIONED|
-                          assign [$$52] <- [$$c.getField(3)] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
+                          assign [$$55] <- [$$c.getField(3)] [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
                           -- ASSIGN  |PARTITIONED|
                             project ([$$c]) [cardinality: 1000000.0, op-cost: 0.0, total-cost: 1000000.0]
                             -- STREAM_PROJECT  |PARTITIONED|
                               exchange [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 2000000.0]
                               -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                data-scan []<-[$$57, $$c] <- tpch.Customer [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 1000000.0]
+                                data-scan []<-[$$60, $$c] <- tpch.Customer [cardinality: 1000000.0, op-cost: 1000000.0, total-cost: 1000000.0]
                                 -- DATASOURCE_SCAN  |PARTITIONED|
                                   exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
index 25663a0..0e78a96 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.04.plan
@@ -1,22 +1,22 @@
-distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+distribute result [$$51] [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+  exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+    project ([$$51]) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+        exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
         -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
           -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+            exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$58, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+              project ([$$58, $$56, $$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+                exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 15.0, op-cost: 175.0, total-cost: 560.0]
+                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 150.0, op-cost: 175.0, total-cost: 560.0]
                   -- HYBRID_HASH_JOIN [$$55, $$66][$$58, $$56]  |PARTITIONED|
                     exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
                     -- HASH_PARTITION_EXCHANGE [$$55, $$66]  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
index 2ace4af..b4a0891 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.10.plan
@@ -1,22 +1,22 @@
-distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+distribute result [$$51] [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+  exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+    project ([$$51]) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+        exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
         -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
           -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+            exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$58, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+              project ([$$58, $$56, $$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+                exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 15.0, op-cost: 225.0, total-cost: 500.0]
+                  join (and(eq($$55, $$58), eq($$56, $$66))) [cardinality: 150.0, op-cost: 225.0, total-cost: 500.0]
                   -- HYBRID_HASH_JOIN [$$55, $$66][$$58, $$56]  |PARTITIONED|
                     exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
                     -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
index 354a94b..5bf8d6d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.12.plan
@@ -1,18 +1,18 @@
-distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+distribute result [$$51] [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+  exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+    project ([$$51]) [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+      assign [$$51] <- [{"n_nationkey": $$58, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+        exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
         -- SORT_MERGE_EXCHANGE [$$58(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+          order (ASC, $$58) (ASC, $$56) (ASC, $$55) [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
           -- STABLE_SORT [$$58(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 605.0]
+            exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 605.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              join (eq($$55, $$58)) [cardinality: 15.0, op-cost: 175.0, total-cost: 605.0]
+              join (eq($$55, $$58)) [cardinality: 150.0, op-cost: 175.0, total-cost: 605.0]
               -- HYBRID_HASH_JOIN [$$55][$$58]  |PARTITIONED|
                 exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
                 -- HASH_PARTITION_EXCHANGE [$$55]  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
index 66124a1..ea91cd5 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.14.plan
@@ -1,22 +1,22 @@
-distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+distribute result [$$51] [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+  exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+    project ([$$51]) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+        exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
         -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
           -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+            exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$59, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+              project ([$$59, $$56, $$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 560.0]
+                exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 560.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 15.0, op-cost: 175.0, total-cost: 560.0]
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 150.0, op-cost: 175.0, total-cost: 560.0]
                   -- HYBRID_HASH_JOIN [$$55, $$66][$$59, $$56]  |PARTITIONED|
                     exchange [cardinality: 150.0, op-cost: 150.0, total-cost: 300.0]
                     -- HASH_PARTITION_EXCHANGE [$$55, $$66]  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
index 0b31760..2700e8a 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/join/hash-join-with-redundant-variable/hash-join-with-redundant-variable.16.plan
@@ -1,22 +1,22 @@
-distribute result [$$51] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+distribute result [$$51] [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
 -- DISTRIBUTE_RESULT  |PARTITIONED|
-  exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+  exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-    project ([$$51]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+    project ([$$51]) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
     -- STREAM_PROJECT  |PARTITIONED|
-      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+      assign [$$51] <- [{"n_nationkey": $$59, "s_nationkey": $$56, "c_nationkey": $$55}] [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
       -- ASSIGN  |PARTITIONED|
-        exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+        exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
         -- SORT_MERGE_EXCHANGE [$$59(ASC), $$56(ASC), $$55(ASC) ]  |PARTITIONED|
-          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+          order (ASC, $$59) (ASC, $$56) (ASC, $$55) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
           -- STABLE_SORT [$$59(ASC), $$56(ASC), $$55(ASC)]  |PARTITIONED|
-            exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+            exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              project ([$$59, $$56, $$55]) [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+              project ([$$59, $$56, $$55]) [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 15.0, op-cost: 0.0, total-cost: 500.0]
+                exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 500.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 15.0, op-cost: 225.0, total-cost: 500.0]
+                  join (and(eq($$55, $$59), eq($$56, $$66))) [cardinality: 150.0, op-cost: 225.0, total-cost: 500.0]
                   -- HYBRID_HASH_JOIN [$$55, $$66][$$59, $$56]  |PARTITIONED|
                     exchange [cardinality: 150.0, op-cost: 0.0, total-cost: 150.0]
                     -- RANDOM_PARTITION_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan
index 38c5f61..49f6ad2 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.plan
@@ -1,18 +1,18 @@
-distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
         -- STREAM_LIMIT  |PARTITIONED|
-          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
           -- STREAM_PROJECT  |PARTITIONED|
-            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 1152.42]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              data-scan []<-[$$18, $$19, $$c] <- test.LineItem condition (and(lt($$c.getField(2), 150), lt($$c.getField(5), 10000))) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              data-scan []<-[$$18, $$19, $$c] <- test.LineItem condition (and(lt($$c.getField(2), 150), lt($$c.getField(5), 10000))) limit 10 [cardinality: 6005.0, op-cost: 1152.42, total-cost: 1152.42]
               -- DATASOURCE_SCAN  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan
index a9d2b4f..3b85e2a 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.plan
@@ -1,24 +1,24 @@
-distribute result [$$20] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+distribute result [$$20] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        project ([$$20]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        project ([$$20]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
         -- STREAM_PROJECT  |PARTITIONED|
-          assign [$$20] <- [{"shipdate": substring($$c.getField(10), 0, 4), "suppkey": gt($$21, 0)}] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          assign [$$20] <- [{"shipdate": substring($$c.getField(10), 0, 4), "suppkey": gt($$21, 0)}] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
           -- ASSIGN  |PARTITIONED|
-            limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            limit 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
             -- STREAM_LIMIT  |PARTITIONED|
-              assign [$$21] <- [$$c.getField(2)] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+              assign [$$21] <- [$$c.getField(2)] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
               -- ASSIGN  |PARTITIONED|
-                project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
                 -- STREAM_PROJECT  |PARTITIONED|
-                  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+                  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
                   -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                    data-scan []<-[$$22, $$23, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 5 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+                    data-scan []<-[$$22, $$23, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 5 [cardinality: 6005.0, op-cost: 6010.65, total-cost: 6010.65]
                     -- DATASOURCE_SCAN  |PARTITIONED|
                       exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                       -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan
index f5fb51e..96d3acf 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.plan
@@ -1,18 +1,18 @@
-distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
         -- STREAM_LIMIT  |PARTITIONED|
-          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
           -- STREAM_PROJECT  |PARTITIONED|
-            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              data-scan []<-[$$15, $$16, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              data-scan []<-[$$15, $$16, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6010.65, total-cost: 6010.65]
               -- DATASOURCE_SCAN  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan
index 7aa0db8..383e104 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.plan
@@ -1,18 +1,18 @@
-distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+distribute result [$$c] [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+  exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+    limit 5 offset 5 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+      exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+        limit 10 [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
         -- STREAM_LIMIT  |PARTITIONED|
-          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+          project ([$$c]) [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
           -- STREAM_PROJECT  |PARTITIONED|
-            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6005.0]
+            exchange [cardinality: 6005.0, op-cost: 0.0, total-cost: 6010.65]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              data-scan []<-[$$17, $$18, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6005.0, total-cost: 6005.0]
+              data-scan []<-[$$17, $$18, $$c] <- test.LineItem condition (lt($$c.getField(2), 150)) limit 10 [cardinality: 6005.0, op-cost: 6010.65, total-cost: 6010.65]
               -- DATASOURCE_SCAN  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan
index 6427067..7c2bdbe 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.3.plan
@@ -1,18 +1,18 @@
-distribute result [$$paper] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+distribute result [$$paper] [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    limit 5 offset 5 [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        limit 10 [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
         -- STREAM_LIMIT  |PARTITIONED|
-          project ([$$paper]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          project ([$$paper]) [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
           -- STREAM_PROJECT  |PARTITIONED|
-            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 24.0]
             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-              data-scan []<-[$$15, $$paper] <- test.DBLP1 condition (contains($$paper.getField(1), "kimL89")) limit 10 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+              data-scan []<-[$$15, $$paper] <- test.DBLP1 condition (contains($$paper.getField(1), "kimL89")) limit 10 [cardinality: 100.0, op-cost: 24.0, total-cost: 24.0]
               -- DATASOURCE_SCAN  |PARTITIONED|
                 exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan
index e175907..fb2c1fd 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.5.plan
@@ -1,24 +1,24 @@
-distribute result [$$37] [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+distribute result [$$37] [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+  exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 2 [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+    limit 2 [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+      exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        project ([$$37]) [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+        project ([$$37]) [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
         -- STREAM_PROJECT  |PARTITIONED|
-          assign [$$37] <- [{"dblpid": $$38}] [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+          assign [$$37] <- [{"dblpid": $$38}] [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
           -- ASSIGN  |PARTITIONED|
-            limit 2 [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+            limit 2 [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
             -- STREAM_LIMIT  |PARTITIONED|
-              project ([$$38]) [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+              project ([$$38]) [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 600.0]
+                exchange [cardinality: 2.1, op-cost: 0.0, total-cost: 205.0]
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  join (eq($$38, $$41)) [cardinality: 100.0, op-cost: 200.0, total-cost: 600.0]
+                  join (eq($$38, $$41)) [cardinality: 2.1, op-cost: 100.0, total-cost: 205.0]
                   -- HYBRID_HASH_JOIN [$$38][$$41]  |PARTITIONED|
-                    exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                    exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
                     -- HASH_PARTITION_EXCHANGE [$$38]  |PARTITIONED|
                       project ([$$38]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
                       -- STREAM_PROJECT  |PARTITIONED|
@@ -26,7 +26,7 @@
                         -- ASSIGN  |PARTITIONED|
                           project ([$$d]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
                           -- STREAM_PROJECT  |PARTITIONED|
-                            exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                            exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                               data-scan []<-[$$39, $$d] <- test.DBLP1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
                               -- DATASOURCE_SCAN  |PARTITIONED|
@@ -34,29 +34,29 @@
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                   empty-tuple-source [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                   -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
-                    exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                    exchange [cardinality: 100.0, op-cost: 4.0, total-cost: 5.0]
                     -- HASH_PARTITION_EXCHANGE [$$41]  |PARTITIONED|
-                      project ([$$41]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                      project ([$$41]) [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                       -- STREAM_PROJECT  |UNPARTITIONED|
-                        assign [$$41] <- [get-item($$30, 0).getField(0).getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                        assign [$$41] <- [get-item($$30, 0).getField(0).getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                         -- ASSIGN  |UNPARTITIONED|
-                          aggregate [$$30] <- [listify($$29)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                          aggregate [$$30] <- [listify($$29)] [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                           -- AGGREGATE  |UNPARTITIONED|
-                            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                             -- STREAM_LIMIT  |UNPARTITIONED|
-                              exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                              exchange [cardinality: 100.0, op-cost: 4.0, total-cost: 5.0]
                               -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-                                project ([$$29]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                project ([$$29]) [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                                 -- STREAM_PROJECT  |PARTITIONED|
-                                  assign [$$29] <- [{"d": $$d}] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                  assign [$$29] <- [{"d": $$d}] [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                                   -- ASSIGN  |PARTITIONED|
-                                    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                                     -- STREAM_LIMIT  |PARTITIONED|
-                                      project ([$$d]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                                      project ([$$d]) [cardinality: 100.0, op-cost: 0.0, total-cost: 1.0]
                                       -- STREAM_PROJECT  |PARTITIONED|
-                                        exchange [cardinality: 100.0, op-cost: 100.0, total-cost: 200.0]
+                                        exchange [cardinality: 100.0, op-cost: 4.0, total-cost: 5.0]
                                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                          data-scan []<-[$$40, $$d] <- test.DBLP1 condition (ends-with($$d.getField(1), "Blakeley95")) limit 1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                                          data-scan []<-[$$40, $$d] <- test.DBLP1 condition (ends-with($$d.getField(1), "Blakeley95")) limit 1 [cardinality: 100.0, op-cost: 1.0, total-cost: 1.0]
                                           -- DATASOURCE_SCAN  |PARTITIONED|
                                             exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan
index 9312e62..26ba31b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.6.plan
@@ -1,26 +1,26 @@
-distribute result [$$19] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+distribute result [$$19] [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+  exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+    limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+      exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        project ([$$19]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+        project ([$$19]) [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
         -- STREAM_PROJECT  |PARTITIONED|
-          assign [$$19] <- [{"$1": substring($$20, 0, 21)}] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+          assign [$$19] <- [{"$1": substring($$20, 0, 21)}] [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
           -- ASSIGN  |PARTITIONED|
-            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+            limit 1 [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
             -- STREAM_LIMIT  |PARTITIONED|
-              project ([$$20]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+              project ([$$20]) [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                assign [$$20] <- [$$DBLP1.getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                assign [$$20] <- [$$DBLP1.getField(1)] [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
                 -- ASSIGN  |PARTITIONED|
-                  project ([$$DBLP1]) [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                  project ([$$DBLP1]) [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
                   -- STREAM_PROJECT  |PARTITIONED|
-                    exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 100.0]
+                    exchange [cardinality: 100.0, op-cost: 0.0, total-cost: 2.0]
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      data-scan []<-[$$21, $$DBLP1] <- test.DBLP1 condition (gt($$DBLP1.getField(1), "series")) limit 1 [cardinality: 100.0, op-cost: 100.0, total-cost: 100.0]
+                      data-scan []<-[$$21, $$DBLP1] <- test.DBLP1 condition (gt($$DBLP1.getField(1), "series")) limit 1 [cardinality: 100.0, op-cost: 2.0, total-cost: 2.0]
                       -- DATASOURCE_SCAN  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan
index 190a98e..8c007cf 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cbo/limit/push-limit-to-primary-scan-select/push-limit-to-primary-scan-select.8.plan
@@ -1,26 +1,26 @@
-distribute result [$$22] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+distribute result [$$22] [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
 -- DISTRIBUTE_RESULT  |UNPARTITIONED|
-  exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+  exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
   -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
-    limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+    limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
     -- STREAM_LIMIT  |UNPARTITIONED|
-      exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+      exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
       -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
-        limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+        limit 2 [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
         -- STREAM_LIMIT  |PARTITIONED|
-          project ([$$22]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+          project ([$$22]) [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
           -- STREAM_PROJECT  |PARTITIONED|
-            assign [$$22] <- [$$26.getField("lang")] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+            assign [$$22] <- [$$26.getField("lang")] [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
             -- ASSIGN  |PARTITIONED|
-              project ([$$26]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+              project ([$$26]) [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
               -- STREAM_PROJECT  |PARTITIONED|
-                assign [$$26] <- [$$t.getField("user")] [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                assign [$$26] <- [$$t.getField("user")] [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
                 -- ASSIGN  |PARTITIONED|
-                  project ([$$t]) [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                  project ([$$t]) [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
                   -- STREAM_PROJECT  |PARTITIONED|
-                    exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 12.0]
+                    exchange [cardinality: 12.0, op-cost: 0.0, total-cost: 3.0]
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      data-scan []<-[$$25, $$t] <- test.TweetMessages condition (and(ge($$t.getField("user").getField("friends_count"), 0), le($$t.getField("user").getField("friends_count"), 150))) limit 2 [cardinality: 12.0, op-cost: 12.0, total-cost: 12.0]
+                      data-scan []<-[$$25, $$t] <- test.TweetMessages condition (and(ge($$t.getField("user").getField("friends_count"), 0), le($$t.getField("user").getField("friends_count"), 150))) limit 2 [cardinality: 12.0, op-cost: 3.0, total-cost: 3.0]
                       -- DATASOURCE_SCAN  |PARTITIONED|
                         exchange [cardinality: 0.0, op-cost: 0.0, total-cost: 0.0]
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|