[ASTERIXDB-3399][COMP] Adding support for Primary index nested loop joins

Change-Id: I832db938f9bb704b13a0dd76d71d2c3edb12adf1
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/18290
Reviewed-by: Vijay Sarathy <vijay.sarathy@couchbase.com>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
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 0ff630d..d5743e4 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
@@ -87,6 +87,7 @@
     protected List<String> datasetNames;
     protected List<String> aliases;
     protected int cheapestPlanIndex;
+    protected PlanNode cheapestPlanNode;
     private ICost cheapestPlanCost;
     protected double origCardinality; // without any selections
     protected double cardinality;
@@ -119,6 +120,7 @@
         this.jnArrayIndex = i;
         planIndexesArray = new ArrayList<>();
         cheapestPlanIndex = PlanNode.NO_PLAN;
+        cheapestPlanNode = null;
         size = 1; // for now, will be the size of the doc for this joinNode
     }
 
@@ -149,6 +151,10 @@
         return origCardinality;
     }
 
+    public PlanNode getCheapestPlanNode() {
+        return cheapestPlanNode;
+    }
+
     protected void setOrigCardinality(double card, boolean setMinCard) {
         // Minimum cardinality for operators is MIN_CARD to prevent bad plans due to cardinality under estimation errors.
         origCardinality = setMinCard ? Math.max(card, Cost.MIN_CARD) : card;
@@ -707,7 +713,7 @@
         boolean forceEnum = mandatoryIndexesInfo.size() > 0 || level <= joinEnum.cboFullEnumLevel;
         if (opCost.costLT(this.cheapestPlanCost) || forceEnum) {
             pn = new PlanNode(allPlans.size(), joinEnum, this, datasetNames.get(0), leafInput);
-            pn.setScanAndHintInfo(PlanNode.ScanMethod.INDEX_SCAN, mandatoryIndexesInfo);
+            pn.setScanAndHintInfo(PlanNode.ScanMethod.INDEX_SCAN, mandatoryIndexesInfo, optionalIndexesInfo);
             pn.setScanCosts(totalCost);
             planIndexesArray.add(pn.allPlansIndex);
             allPlans.add(pn);
@@ -1443,6 +1449,7 @@
         PlanNode cheapestPlan = forceEnum ? findCheapestPlan() : pn;
         cheapestPlanCost = cheapestPlan.totalCost;
         cheapestPlanIndex = cheapestPlan.allPlansIndex;
+        cheapestPlanNode = cheapestPlan;
     }
 
     @Override
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 e51990e..2cf9130 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
@@ -49,6 +49,7 @@
 
     protected ScanMethod scanOp;
     protected boolean indexHint;
+    Index indexUsed;
 
     protected JoinMethod joinOp;
 
@@ -81,6 +82,10 @@
         return allPlansIndex;
     }
 
+    public Index getSoleAccessIndex() {
+        return indexUsed;
+    }
+
     private int[] getPlanIndexes() {
         return planIndexes;
     }
@@ -143,7 +148,7 @@
         return getLeftPlanIndex() == NO_PLAN && getRightPlanIndex() == NO_PLAN;
     }
 
-    protected boolean IsJoinNode() {
+    public boolean IsJoinNode() {
         return getLeftPlanIndex() != NO_PLAN && getRightPlanIndex() != NO_PLAN;
     }
 
@@ -231,6 +236,7 @@
         jn = null;
         planIndexes = new int[2]; // 0 is for left, 1 is for right
         jnIndexes = new int[2]; // join node index(es)
+        indexUsed = null;
         setLeftJoinIndex(JoinNode.NO_JN);
         setRightJoinIndex(JoinNode.NO_JN);
         setLeftPlanIndex(PlanNode.NO_PLAN);
@@ -251,6 +257,7 @@
         this.leafInput = leafInput;
         planIndexes = new int[2]; // 0 is for left, 1 is for right
         jnIndexes = new int[2]; // join node index(es)
+        indexUsed = null;
         setLeftJoinIndex(jn.jnArrayIndex);
         setRightJoinIndex(JoinNode.NO_JN);
         setLeftPlanIndex(PlanNode.NO_PLAN); // There ane no plans below this plan.
@@ -269,6 +276,7 @@
         this.outerJoin = outerJoin;
         planIndexes = new int[2]; // 0 is for left, 1 is for right
         jnIndexes = new int[2]; // join node index(es)
+        indexUsed = null; // used for NL costing
         setLeftJoinIndex(leftPlan.jn.jnArrayIndex);
         setRightJoinIndex(rightPlan.jn.jnArrayIndex);
         setLeftPlanIndex(leftPlan.allPlansIndex);
@@ -279,12 +287,22 @@
     }
 
     protected void setScanAndHintInfo(ScanMethod scanMethod,
-            List<Triple<Index, Double, AbstractFunctionCallExpression>> mandatoryIndexesInfo) {
+            List<Triple<Index, Double, AbstractFunctionCallExpression>> mandatoryIndexesInfo,
+            List<Triple<Index, Double, AbstractFunctionCallExpression>> optionalIndexesInfo) {
         setScanMethod(scanMethod);
         if (mandatoryIndexesInfo.size() > 0) {
             indexHint = true;
             numHintsUsed = 1;
         }
+        // keeping things simple. When multiple indexes are used, we cannot be sure of the order.
+        // So seeing if only index is used.
+        if (optionalIndexesInfo.size() + mandatoryIndexesInfo.size() == 1) {
+            if (optionalIndexesInfo.size() == 1) {
+                indexUsed = optionalIndexesInfo.get(0).first;
+            } else {
+                indexUsed = mandatoryIndexesInfo.get(0).first;
+            }
+        }
     }
 
     protected void setScanCosts(ICost opCost) {