[ASTERIXDB-3377][COMP]: CBO choosing wrong index

Change-Id: I81a6dcca634d3d30f0b101baabe4093fb01123d0
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/18236
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: <murali.krishna@couchbase.com>
Reviewed-by: Vijay Sarathy <vijay.sarathy@couchbase.com>
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 e419eea..951648a 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,6 +30,7 @@
 
 import org.apache.asterix.common.annotations.IndexedNLJoinExpressionAnnotation;
 import org.apache.asterix.common.annotations.SecondaryIndexSearchPreferenceAnnotation;
+import org.apache.asterix.common.annotations.SkipSecondaryIndexSearchExpressionAnnotation;
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.metadata.declared.DataSource;
@@ -384,6 +385,29 @@
         return false;
     }
 
+    public SkipSecondaryIndexSearchExpressionAnnotation findSkipIndexHint(AbstractFunctionCallExpression condition) {
+        if (condition.getFunctionIdentifier().equals(AlgebricksBuiltinFunctions.AND)) {
+            for (int i = 0; i < condition.getArguments().size(); i++) {
+                ILogicalExpression expr = condition.getArguments().get(i).getValue();
+                if (expr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+                    AbstractFunctionCallExpression AFCexpr = (AbstractFunctionCallExpression) expr;
+                    SkipSecondaryIndexSearchExpressionAnnotation skipAnno =
+                            AFCexpr.getAnnotation(SkipSecondaryIndexSearchExpressionAnnotation.class);
+                    if (skipAnno != null) {
+                        return skipAnno;
+                    }
+                }
+            }
+        } else if (condition.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+            SkipSecondaryIndexSearchExpressionAnnotation skipAnno =
+                    condition.getAnnotation(SkipSecondaryIndexSearchExpressionAnnotation.class);
+            if (skipAnno != null) {
+                return skipAnno;
+            }
+        }
+        return null;
+    }
+
     protected int findJoinNodeIndexByName(String name) {
         for (int i = 1; i <= this.numberOfTerms; i++) {
             if (name.equals(jnArray[i].datasetNames.get(0))) {
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 4543190..37212ba 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
@@ -21,8 +21,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -546,9 +548,20 @@
         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
-                EnumerateJoinsRule.setAnnotation(afce, SkipSecondaryIndexSearchExpressionAnnotation
-                        .newInstance(Collections.singleton(IndexCostInfo.get(i).first.getIndexName())));
+                SkipSecondaryIndexSearchExpressionAnnotation skipAnno = joinEnum.findSkipIndexHint(afce);
+                Collection<String> indexNames = new HashSet<>();
+                if (skipAnno != null && skipAnno.getIndexNames() != null) {
+                    indexNames.addAll(skipAnno.getIndexNames());
+                }
+                if (indexNames.isEmpty()) {
+                    // this index has to be skipped, so find the corresponding expression
+                    EnumerateJoinsRule.setAnnotation(afce, SkipSecondaryIndexSearchExpressionAnnotation
+                            .newInstance(Collections.singleton(IndexCostInfo.get(i).first.getIndexName())));
+                } else {
+                    indexNames.add(IndexCostInfo.get(i).first.getIndexName());
+                    EnumerateJoinsRule.setAnnotation(afce,
+                            SkipSecondaryIndexSearchExpressionAnnotation.newInstance(indexNames));
+                }
             }
         }
     }
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 254d364..785da69 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
@@ -446,33 +446,41 @@
 
         List<List<IAObject>> result;
         parent.getInputs().get(0).setValue(deepCopyofScan);
-        if (numSubplans == 1 && nonSubplanSelects == 0) {
-            AggregateOperator aggOp = findAggOp(selOp, exp);
-            if (aggOp.getExpressions().size() > 1) {
-                // ANY and EVERY IN query; for selectivity purposes, we need to transform this into a ANY IN query
-                SelectOperator newSelOp = (SelectOperator) OperatorManipulationUtil.bottomUpCopyOperators(selOp);
-                aggOp = findAggOp(newSelOp, exp);
-                ILogicalOperator input = aggOp.getInputs().get(0).getValue();
-                SelectOperator condition = (SelectOperator) OperatorManipulationUtil
-                        .bottomUpCopyOperators(AbstractOperatorFromSubplanRewrite.getSelectFromPlan(aggOp));
-                //push this condition below aggOp.
-                aggOp.getInputs().get(0).setValue(condition);
-                condition.getInputs().get(0).setValue(input);
-                ILogicalExpression newExp2 = newSelOp.getCondition().getValue();
-                if (newExp2.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
-                    AbstractFunctionCallExpression afce = (AbstractFunctionCallExpression) newExp2;
-                    afce.getArguments().get(1).setValue(ConstantExpression.TRUE);
-                }
-                result = runSamplingQuery(optCtx, newSelOp); // no need to switch anything
-            } else {
-                result = runSamplingQuery(optCtx, selOp);
-            }
-        } else {
-            SelectOperator selOp2 = findSelectOpWithExpr(selOp, exp);
-            List<ILogicalExpression> selExprs;
-            selExprs = storeSelectConditionsAndMakeThemTrue(selOp, selOp2); // all these will be marked true and will be resorted later.
+
+        if (numSelects == 1 && 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);
-            restoreAllSelectConditions(selOp, selExprs, selOp2);
+            selOp.getCondition().setValue(saveExprs);
+        } else {
+            if (numSubplans == 1 && nonSubplanSelects == 0) {
+                AggregateOperator aggOp = findAggOp(selOp, exp);
+                if (aggOp.getExpressions().size() > 1) {
+                    // ANY and EVERY IN query; for selectivity purposes, we need to transform this into a ANY IN query
+                    SelectOperator newSelOp = (SelectOperator) OperatorManipulationUtil.bottomUpCopyOperators(selOp);
+                    aggOp = findAggOp(newSelOp, exp);
+                    ILogicalOperator input = aggOp.getInputs().get(0).getValue();
+                    SelectOperator condition = (SelectOperator) OperatorManipulationUtil
+                            .bottomUpCopyOperators(AbstractOperatorFromSubplanRewrite.getSelectFromPlan(aggOp));
+                    //push this condition below aggOp.
+                    aggOp.getInputs().get(0).setValue(condition);
+                    condition.getInputs().get(0).setValue(input);
+                    ILogicalExpression newExp2 = newSelOp.getCondition().getValue();
+                    if (newExp2.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+                        AbstractFunctionCallExpression afce = (AbstractFunctionCallExpression) newExp2;
+                        afce.getArguments().get(1).setValue(ConstantExpression.TRUE);
+                    }
+                    result = runSamplingQuery(optCtx, newSelOp); // no need to switch anything
+                } else {
+                    result = runSamplingQuery(optCtx, selOp);
+                }
+            } else {
+                SelectOperator selOp2 = findSelectOpWithExpr(selOp, exp);
+                List<ILogicalExpression> selExprs;
+                selExprs = storeSelectConditionsAndMakeThemTrue(selOp, selOp2); // all these will be marked true and will be resorted later.
+                result = runSamplingQuery(optCtx, selOp);
+                restoreAllSelectConditions(selOp, selExprs, selOp2);
+            }
         }
 
         double predicateCardinality = findPredicateCardinality(result, false);
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
index 00ba0d7..725e81e 100644
--- 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
@@ -9,11 +9,11 @@
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                   -- BTREE_SEARCH (test.tenk.tenk)  |PARTITIONED|
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      -- STABLE_SORT [$$31(ASC)]  |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_2k)  |PARTITIONED|
+                              -- BTREE_SEARCH (test.tenk.idx_2k)  |PARTITIONED|
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                                   -- ASSIGN  |PARTITIONED|
                                     -- EMPTY_TUPLE_SOURCE  |PARTITIONED|