private static ILogicalOperator createFinalIndexOnlySearchPlan()

in asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java [1226:1739]


    private static ILogicalOperator createFinalIndexOnlySearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs,
            Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef,
            List<Mutable<ILogicalOperator>> assignsBeforeTopOpRef, Dataset dataset, ARecordType recordType,
            ARecordType metaRecordType, ILogicalOperator inputOp, IOptimizationContext context, boolean retainInput,
            boolean retainMissing, boolean requiresBroadcast, Index secondaryIndex,
            AccessMethodAnalysisContext analysisCtx, OptimizableOperatorSubTree subTree,
            LogicalVariable newMissingPlaceHolderForLOJ, IAlgebricksConstantValue leftOuterMissingValue,
            List<LogicalVariable> pkVarsFromSIdxUnnestMapOp, List<LogicalVariable> primaryIndexUnnestVars,
            List<Object> primaryIndexOutputTypes, boolean anyRealTypeConvertedToIntegerType)
            throws AlgebricksException {
        SourceLocation sourceLoc = inputOp.getSourceLocation();
        Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo = analysisCtx.getIndexOnlyPlanInfo();
        // From now on, we deal with the index-only plan.
        // Initializes the information required for the index-only plan optimization.
        // Fetches SK variable(s) from the secondary-index search operator.
        List<LogicalVariable> skVarsFromSIdxUnnestMap = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset,
                recordType, metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.SECONDARY_KEY);
        boolean skFieldUsedAfterTopOp = indexOnlyPlanInfo.getSecond();
        boolean requireVerificationAfterSIdxSearch = indexOnlyPlanInfo.getThird();
        ILogicalOperator assignBeforeTopOp;
        UnionAllOperator unionAllOp;
        SelectOperator newSelectOpInLeftPath;
        SelectOperator newSelectOpInRightPath;
        SplitOperator splitOp = null;
        // This variable map will be used as input to UNIONALL operator. The form is <left, right, output>.
        // In our case, left: instantTryLock fail path, right: instantTryLock success path
        List<Triple<LogicalVariable, LogicalVariable, LogicalVariable>> unionVarMap = new ArrayList<>();
        List<LogicalVariable> condSplitVars;
        List<LogicalVariable> liveVarsAfterTopOp = new ArrayList<>();

        // Constructs the variable mapping between newly constructed secondary
        // key search (SK, PK) and those in the original plan (datasource scan).
        LinkedHashMap<LogicalVariable, LogicalVariable> origVarToSIdxUnnestMapOpVarMap = new LinkedHashMap<>();

        Index.ValueIndexDetails secondaryIndexDetails = (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
        List<List<String>> chosenIndexFieldNames = secondaryIndexDetails.getKeyFieldNames();
        IndexType idxType = secondaryIndex.getIndexType();

        // variables used in SELECT or JOIN operator
        List<LogicalVariable> usedVarsInTopOp = new ArrayList<>();
        List<LogicalVariable> uniqueUsedVarsInTopOp = new ArrayList<>();

        // variables used in ASSIGN before SELECT operator
        List<LogicalVariable> producedVarsInAssignsBeforeTopOp = new ArrayList<>();

        // For the index-nested-loop join case, we need to exclude the variables from the left (outer) branch
        // when deciding which variables should be propagated via UNIONALL operator.
        // This is because these variables are already generated and is not related to the decision
        // whether the plan is an index-only plan or not. Only the right (inner) branch matters.
        List<LogicalVariable> liveVarsInSubTreeRootOp = new ArrayList<>();

        // variables used after SELECT or JOIN operator
        List<LogicalVariable> usedVarsAfterTopOp = new ArrayList<>();
        List<LogicalVariable> varsTmpList = new ArrayList<>();

        // If the secondary key field is used after SELECT or JOIN operator (e.g., returning the field value),
        // then we need to keep these secondary keys. In case of R-tree index, the result of an R-tree
        // index search is an MBR. So, we need to reconstruct original field values from the result if that index
        // is on a rectangle or point.
        AssignOperator skVarAssignOpInRightPath = null;
        List<LogicalVariable> restoredSKVarFromRTree = null;
        // Original SK field variable to restored SK field variable in the right path mapping
        LinkedHashMap<LogicalVariable, LogicalVariable> origSKFieldVarToNewSKFieldVarMap = new LinkedHashMap<>();
        // Index-only plan consideration for the R-Tree index only:
        // Constructs an additional ASSIGN to restore the original secondary key field(s) from
        // the results of the secondary index search in case the field is used after SELECT or JOIN operator or
        // a verification is required since the given query shape is not RECTANGLE or POINT even though the type of
        // index is RECTANGLE or POINT (in this case only, removing false-positive is possible.).
        if (idxType == IndexType.RTREE && (skFieldUsedAfterTopOp || requireVerificationAfterSIdxSearch)) {
            IOptimizableFuncExpr optFuncExpr = AccessMethodUtils.chooseFirstOptFuncExpr(secondaryIndex, analysisCtx);
            int optFieldIdx = AccessMethodUtils.chooseFirstOptFuncVar(secondaryIndex, analysisCtx);
            Pair<IAType, Boolean> keyPairType = Index.getNonNullableOpenFieldType(secondaryIndex,
                    optFuncExpr.getFieldType(optFieldIdx), optFuncExpr.getFieldName(optFieldIdx), recordType);
            if (keyPairType == null) {
                return null;
            }
            // Gets the number of dimensions corresponding to the field indexed by chosenIndex.
            IAType spatialType = keyPairType.first;
            ArrayList<Mutable<ILogicalExpression>> restoredSKFromRTreeExprs = new ArrayList<>();
            restoredSKVarFromRTree = new ArrayList<>();
            switch (spatialType.getTypeTag()) {
                case POINT:
                    // Reconstructs a POINT value.
                    AbstractFunctionCallExpression createPointExpr =
                            createPointExpression(skVarsFromSIdxUnnestMap, sourceLoc);
                    restoredSKVarFromRTree.add(context.newVar());
                    restoredSKFromRTreeExprs.add(new MutableObject<ILogicalExpression>(createPointExpr));
                    skVarAssignOpInRightPath = new AssignOperator(restoredSKVarFromRTree, restoredSKFromRTreeExprs);
                    skVarAssignOpInRightPath.setSourceLocation(sourceLoc);
                    break;
                case RECTANGLE:
                    // Reconstructs a RECTANGLE value.
                    AbstractFunctionCallExpression expr1 =
                            createPointExpression(skVarsFromSIdxUnnestMap.subList(0, 2), sourceLoc);
                    AbstractFunctionCallExpression expr2 =
                            createPointExpression(skVarsFromSIdxUnnestMap.subList(2, 4), sourceLoc);
                    AbstractFunctionCallExpression createRectangleExpr = createRectangleExpression(expr1, expr2);
                    restoredSKVarFromRTree.add(context.newVar());
                    restoredSKFromRTreeExprs.add(new MutableObject<ILogicalExpression>(createRectangleExpr));
                    skVarAssignOpInRightPath = new AssignOperator(restoredSKVarFromRTree, restoredSKFromRTreeExprs);
                    skVarAssignOpInRightPath.setSourceLocation(sourceLoc);
                    break;
                default:
                    break;
            }
        }

        // Gets all variables from the right (inner) branch.
        VariableUtilities.getLiveVariables(subTree.getRootRef().getValue(), liveVarsInSubTreeRootOp);
        // Gets the used variables from the SELECT or JOIN operator.
        VariableUtilities.getUsedVariables(topOpRef.getValue(), usedVarsInTopOp);
        // Excludes the variables in the condition from the outer branch - in join case.
        for (Iterator<LogicalVariable> iterator = usedVarsInTopOp.iterator(); iterator.hasNext();) {
            LogicalVariable v = iterator.next();
            if (!liveVarsInSubTreeRootOp.contains(v)) {
                iterator.remove();
            }
        }
        // Keeps the unique used variables in the SELECT or JOIN operator.
        copyVarsToAnotherList(usedVarsInTopOp, uniqueUsedVarsInTopOp);

        // If there are ASSIGN operators (usually secondary key field) before the given SELECT or JOIN operator,
        // we may need to propagate these produced variables via the UNIONALL operator if they are used afterwards.
        if (assignsBeforeTopOpRef != null && !assignsBeforeTopOpRef.isEmpty()) {
            for (int i = 0; i < assignsBeforeTopOpRef.size(); i++) {
                assignBeforeTopOp = assignsBeforeTopOpRef.get(i).getValue();
                varsTmpList.clear();
                VariableUtilities.getProducedVariables(assignBeforeTopOp, varsTmpList);
                copyVarsToAnotherList(varsTmpList, producedVarsInAssignsBeforeTopOp);
            }
        }

        // Adds an optional ASSIGN operator that sits right after the SELECT or JOIN operator.
        // This assign operator keeps any constant expression(s) extracted from the original ASSIGN operators
        // in the subtree and are used after the SELECT or JOIN operator. In usual case,
        // this constant value would be used in a group-by after a left-outer-join and will be removed by the optimizer.
        // We need to conduct this since this variable does not have to be in the both branch of an index-only plan.
        AssignOperator constAssignOp = null;
        ILogicalOperator currentOpAfterTopOp = null;
        List<LogicalVariable> constAssignVars = new ArrayList<>();
        List<Mutable<ILogicalExpression>> constAssignExprs = new ArrayList<>();
        ILogicalOperator currentOp = inputOp;

        boolean constantAssignVarUsedInTopOp = false;
        if (assignsBeforeTopOpRef != null && !assignsBeforeTopOpRef.isEmpty()) {
            // From the first ASSIGN (earliest in the plan) to the last ASSGIN (latest)
            for (int i = assignsBeforeTopOpRef.size() - 1; i >= 0; i--) {
                AssignOperator tmpOp = (AssignOperator) assignsBeforeTopOpRef.get(i).getValue();
                List<LogicalVariable> tmpAssignVars = tmpOp.getVariables();
                List<Mutable<ILogicalExpression>> tmpAsssignExprs = tmpOp.getExpressions();
                Iterator<LogicalVariable> varIt = tmpAssignVars.iterator();
                Iterator<Mutable<ILogicalExpression>> exprIt = tmpAsssignExprs.iterator();
                boolean changed = false;
                while (exprIt.hasNext()) {
                    Mutable<ILogicalExpression> tmpExpr = exprIt.next();
                    LogicalVariable tmpVar = varIt.next();
                    if (tmpExpr.getValue().getExpressionTag() == LogicalExpressionTag.CONSTANT) {
                        constAssignVars.add(tmpVar);
                        constAssignExprs.add(tmpExpr);
                        varIt.remove();
                        exprIt.remove();
                        changed = true;
                    }
                }
                if (changed) {
                    context.computeAndSetTypeEnvironmentForOperator(tmpOp);
                }
            }

            if (!constAssignVars.isEmpty()) {
                // These constants should not be used in the SELECT or JOIN operator.
                for (LogicalVariable v : constAssignVars) {
                    if (usedVarsInTopOp.contains(v)) {
                        constantAssignVarUsedInTopOp = true;
                        break;
                    }
                }
                // If this assign operator is not used in the SELECT or JOIN operator,
                // we will add this operator after creating UNION operator in the last part of this method.
                constAssignOp = new AssignOperator(constAssignVars, constAssignExprs);
                constAssignOp.setSourceLocation(sourceLoc);
                if (constantAssignVarUsedInTopOp) {
                    // Places this assign after the secondary index-search op.
                    constAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(inputOp));
                    constAssignOp.setExecutionMode(ExecutionMode.PARTITIONED);
                    context.computeAndSetTypeEnvironmentForOperator(constAssignOp);
                    currentOp = constAssignOp;
                }
            }
        }

        // variables used after SELECT or JOIN operator
        HashSet<LogicalVariable> varsTmpSet = new HashSet<>();
        if (afterTopOpRefs != null) {
            for (Mutable<ILogicalOperator> afterTopOpRef : afterTopOpRefs) {
                varsTmpSet.clear();
                OperatorPropertiesUtil.getFreeVariablesInOp(afterTopOpRef.getValue(), varsTmpSet);
                copyVarsToAnotherList(varsTmpSet, usedVarsAfterTopOp);
            }
        }

        // Now, adds a SPLIT operator to propagate <SK, PK> pair from the secondary-index search to the two paths.
        // And constructs the path from the secondary index search to the SPLIT operator.

        // Fetches the conditional split variable from the secondary-index search
        condSplitVars = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset, recordType, metaRecordType, inputOp,
                secondaryIndex, SecondaryUnnestMapOutputVarType.CONDITIONAL_SPLIT_VAR);

        // Adds a SPLIT operator after the given secondary index-search unnest-map operator.
        splitOp = new SplitOperator(2,
                new MutableObject<ILogicalExpression>(new VariableReferenceExpression(condSplitVars.get(0))));
        splitOp.setSourceLocation(sourceLoc);
        splitOp.getInputs().add(new MutableObject<ILogicalOperator>(currentOp));
        splitOp.setExecutionMode(ExecutionMode.PARTITIONED);
        context.computeAndSetTypeEnvironmentForOperator(splitOp);

        // To maintain SSA, we assign new variables for the incoming variables in the left branch
        // since the most tuples go to the right branch (instantTryLock success path). Also, the output of
        // UNIONALL should be a new variable. (it cannot be the same to the left or right variable.)

        // Original variables (before SPLIT) to the variables in the left path mapping
        LinkedHashMap<LogicalVariable, LogicalVariable> liveVarAfterSplitToLeftPathMap = new LinkedHashMap<>();
        // output variables to the variables generated in the left branch mapping
        LinkedHashMap<LogicalVariable, LogicalVariable> origPKRecAndSKVarToleftPathMap = new LinkedHashMap<>();
        // Original variables (before SPLIT) to the output variables mapping (mainly for join case)
        LinkedHashMap<LogicalVariable, LogicalVariable> origVarToOutputVarMap = new LinkedHashMap<>();
        List<LogicalVariable> liveVarsAfterSplitOp = new ArrayList<>();
        VariableUtilities.getLiveVariables(splitOp, liveVarsAfterSplitOp);

        ArrayList<LogicalVariable> assignVars = new ArrayList<>();
        ArrayList<Mutable<ILogicalExpression>> assignExprs = new ArrayList<>();
        for (LogicalVariable v : liveVarsAfterSplitOp) {
            LogicalVariable newVar = context.newVar();
            liveVarAfterSplitToLeftPathMap.put(v, newVar);
            assignVars.add(newVar);
            VariableReferenceExpression vRef = new VariableReferenceExpression(v);
            vRef.setSourceLocation(sourceLoc);
            assignExprs.add(new MutableObject<ILogicalExpression>(vRef));
        }
        AssignOperator origVarsToLeftPathVarsAssignOp = new AssignOperator(assignVars, assignExprs);
        origVarsToLeftPathVarsAssignOp.setSourceLocation(sourceLoc);
        origVarsToLeftPathVarsAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(splitOp));
        context.computeAndSetTypeEnvironmentForOperator(origVarsToLeftPathVarsAssignOp);
        origVarsToLeftPathVarsAssignOp.setExecutionMode(ExecutionMode.PARTITIONED);

        // Creates the variable mapping for the UNIONALL operator.

        // PK Variable(s) that will be fed into the primary index-search has been re-assigned in the left path.
        List<LogicalVariable> pkVarsInLeftPathFromSIdxSearchBeforeSplit = new ArrayList<>();
        for (LogicalVariable v : pkVarsFromSIdxUnnestMapOp) {
            pkVarsInLeftPathFromSIdxSearchBeforeSplit.add(liveVarAfterSplitToLeftPathMap.get(v));
        }
        // PK and Record variable(s) from the primary-index search will be reassigned in the left path
        // to make the output of the UNIONALL the original variables from the data-scan.
        List<LogicalVariable> pkVarsFromPIdxSearchInLeftPath = new ArrayList<>();
        for (int i = 0; i < primaryIndexUnnestVars.size(); i++) {
            LogicalVariable replacedVar = context.newVar();
            pkVarsFromPIdxSearchInLeftPath.add(replacedVar);
            origPKRecAndSKVarToleftPathMap.put(primaryIndexUnnestVars.get(i), replacedVar);
        }

        // Are the used variables after SELECT or JOIN operator from the primary index?
        // Then, creates the variable mapping between two paths.
        for (LogicalVariable tVar : usedVarsAfterTopOp) {
            // Checks whether this variable is already added to the union variable map.
            // It should be also a part of the primary key variables.
            if (findVarInTripleVarList(unionVarMap, tVar, false) || !primaryIndexUnnestVars.contains(tVar)) {
                continue;
            }
            int pIndexPKIdx = primaryIndexUnnestVars.indexOf(tVar);
            // If the above value is -1, either it is a secondary key variable or a variable
            // from different branch (join case). These cases will be dealt with later.
            if (pIndexPKIdx == -1) {
                continue;
            }
            unionVarMap.add(new Triple<>(pkVarsFromPIdxSearchInLeftPath.get(pIndexPKIdx),
                    pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx), tVar));
            origVarToOutputVarMap.put(pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx), tVar);

            // Constructs the mapping between the PK from the original data-scan to the PK
            // from the secondary index search since they are different logical variables.
            origVarToSIdxUnnestMapOpVarMap.put(tVar, pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx));
        }

        // Are the used variables after SELECT or JOIN operator from the given secondary index?
        for (LogicalVariable tVar : usedVarsAfterTopOp) {
            // Checks whether this variable is already added to the union variable map.
            if (findVarInTripleVarList(unionVarMap, tVar, false)) {
                continue;
            }
            // Should be either used in the condition or a composite index field that is not used in the condition.
            if (!usedVarsInTopOp.contains(tVar) && !producedVarsInAssignsBeforeTopOp.contains(tVar)) {
                continue;
            }
            int sIndexIdx = chosenIndexFieldNames.indexOf(subTree.getVarsToFieldNameMap().get(tVar));
            // For the join-case, the match might not exist.
            // In this case, we just propagate the variables later.
            if (sIndexIdx == -1) {
                continue;
            }
            if (idxType == IndexType.RTREE) {
                // R-Tree case: we need this variable in case if we need to do an additional verification,
                // or the secondary key field is used after SELECT or JOIN operator.
                // We need to use the re-constructed secondary key from the result (an MBR) of R-Tree search.
                // For the join case, the match might not exist.
                // In this case, we just propagate the variables later.
                if (!skFieldUsedAfterTopOp && !requireVerificationAfterSIdxSearch) {
                    continue;
                }
                LogicalVariable replacedVar = context.newVar();
                origPKRecAndSKVarToleftPathMap.put(tVar, replacedVar);
                origSKFieldVarToNewSKFieldVarMap.put(tVar, restoredSKVarFromRTree.get(sIndexIdx));
                unionVarMap.add(new Triple<>(replacedVar, restoredSKVarFromRTree.get(sIndexIdx), tVar));
                continue;
            }
            // B-Tree case:
            LogicalVariable replacedVar = context.newVar();
            origPKRecAndSKVarToleftPathMap.put(tVar, replacedVar);
            origVarToOutputVarMap.put(skVarsFromSIdxUnnestMap.get(sIndexIdx), tVar);
            unionVarMap.add(new Triple<LogicalVariable, LogicalVariable, LogicalVariable>(replacedVar,
                    skVarsFromSIdxUnnestMap.get(sIndexIdx), tVar));
            // Constructs the mapping between the SK from the original data-scan
            // and the SK from the secondary index search since they are different logical variables.
            origVarToSIdxUnnestMapOpVarMap.put(tVar, skVarsFromSIdxUnnestMap.get(sIndexIdx));
        }

        // For B-Tree case: if the given secondary key field variable is used only in the select or
        // join condition, we were not able to catch the mapping between the the SK from the original
        // data-scan and the SK from the secondary index search since they are different logical variables.
        // (E.g., we are sending a query on a composite index but returns only one field.)
        List<LogicalVariable> varsUsedInTopOpButNotAfterwards = new ArrayList<>();
        copyVarsToAnotherList(uniqueUsedVarsInTopOp, varsUsedInTopOpButNotAfterwards);
        varsUsedInTopOpButNotAfterwards.removeAll(usedVarsAfterTopOp);
        if (idxType == IndexType.BTREE) {
            for (LogicalVariable v : varsUsedInTopOpButNotAfterwards) {
                int sIndexIdx = chosenIndexFieldNames.indexOf(subTree.getVarsToFieldNameMap().get(v));
                // For the join-case, the match might not exist.
                // In this case, we just propagate the variables later.
                if (sIndexIdx == -1) {
                    continue;
                }
                LogicalVariable replacedVar = context.newVar();
                origPKRecAndSKVarToleftPathMap.put(v, replacedVar);
                origVarToOutputVarMap.put(skVarsFromSIdxUnnestMap.get(sIndexIdx), v);
                // Constructs the mapping between the SK from the original data-scan
                // and the SK from the secondary index search since they are different logical variables.
                origVarToSIdxUnnestMapOpVarMap.put(v, skVarsFromSIdxUnnestMap.get(sIndexIdx));
            }
        }

        // For R-Tree case: if the given secondary key field variable is used only in the select or join condition,
        // we were not able to catch the mapping between the original secondary key field and the newly restored
        // secondary key field in the assign operator in the right path.
        if (idxType == IndexType.RTREE && (skFieldUsedAfterTopOp || requireVerificationAfterSIdxSearch)) {
            for (LogicalVariable v : uniqueUsedVarsInTopOp) {
                if (!primaryIndexUnnestVars.contains(v)) {
                    origSKFieldVarToNewSKFieldVarMap.put(v, restoredSKVarFromRTree.get(0));
                }
            }
        }

        // For the index-nested-loop join case,
        // we propagate all variables that come from the outer relation and are used after join operator.
        // Adds the variables that are both live after JOIN and used after the JOIN operator.
        VariableUtilities.getLiveVariables(topOpRef.getValue(), liveVarsAfterTopOp);
        for (LogicalVariable v : usedVarsAfterTopOp) {
            if (!liveVarsAfterTopOp.contains(v) || findVarInTripleVarList(unionVarMap, v, false)) {
                continue;
            }
            LogicalVariable outputVar = context.newVar();
            origVarToOutputVarMap.put(v, outputVar);
            unionVarMap.add(new Triple<>(liveVarAfterSplitToLeftPathMap.get(v), v, outputVar));
        }

        // Replaces the original variables in the operators after the SELECT or JOIN operator to satisfy SSA.
        if (afterTopOpRefs != null) {
            for (Mutable<ILogicalOperator> afterTopOpRef : afterTopOpRefs) {
                VariableUtilities.substituteVariables(afterTopOpRef.getValue(), origVarToOutputVarMap, context);
            }
        }

        // Creates the primary index lookup operator.
        // The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments.
        AbstractUnnestMapOperator primaryIndexUnnestMapOp = createPrimaryIndexUnnestMapOp(dataset, retainInput,
                retainMissing, requiresBroadcast, pkVarsInLeftPathFromSIdxSearchBeforeSplit,
                pkVarsFromPIdxSearchInLeftPath, primaryIndexOutputTypes, sourceLoc, leftOuterMissingValue);
        primaryIndexUnnestMapOp.setSourceLocation(sourceLoc);
        primaryIndexUnnestMapOp.getInputs().add(new MutableObject<ILogicalOperator>(origVarsToLeftPathVarsAssignOp));
        context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestMapOp);
        primaryIndexUnnestMapOp.setExecutionMode(ExecutionMode.PARTITIONED);

        // Now, generates the UnionAllOperator to merge the left and right paths.
        // If we are transforming a join, in the instantTryLock on PK fail path, a SELECT operator should be
        // constructed from the join condition and placed after the primary index lookup
        // to do the final verification. If this is a select plan, we just need to use the original
        // SELECT operator after the primary index lookup to do the final verification.
        LinkedHashMap<LogicalVariable, LogicalVariable> origVarToNewVarInLeftPathMap = new LinkedHashMap<>();
        origVarToNewVarInLeftPathMap.putAll(liveVarAfterSplitToLeftPathMap);
        origVarToNewVarInLeftPathMap.putAll(origPKRecAndSKVarToleftPathMap);
        ILogicalExpression conditionRefExpr = conditionRef.getValue().cloneExpression();
        // The retainMissing variable contains the information whether we are optimizing a left-outer join or not.
        LogicalVariable newMissingPlaceHolderVar = retainMissing ? newMissingPlaceHolderForLOJ : null;
        newSelectOpInLeftPath =
                retainMissing ? new SelectOperator(new MutableObject<>(conditionRefExpr), leftOuterMissingValue,
                        newMissingPlaceHolderVar) : new SelectOperator(new MutableObject<>(conditionRefExpr));
        newSelectOpInLeftPath.setSourceLocation(conditionRefExpr.getSourceLocation());
        VariableUtilities.substituteVariables(newSelectOpInLeftPath, origVarToNewVarInLeftPathMap, context);

        // If there are ASSIGN operators before the SELECT or JOIN operator,
        // we need to put these operators between the SELECT or JOIN and the primary index lookup in the left path.
        if (assignsBeforeTopOpRef != null && !assignsBeforeTopOpRef.isEmpty()) {
            // Makes the primary unnest-map as the child of the last ASSIGN (from top) in the path.
            assignBeforeTopOp = assignsBeforeTopOpRef.get(assignsBeforeTopOpRef.size() - 1).getValue();
            assignBeforeTopOp.getInputs().clear();
            assignBeforeTopOp.getInputs().add(new MutableObject<ILogicalOperator>(primaryIndexUnnestMapOp));

            // Makes the first ASSIGN (from top) as the child of the SELECT operator.
            for (int i = assignsBeforeTopOpRef.size() - 1; i >= 0; i--) {
                if (assignsBeforeTopOpRef.get(i) != null) {
                    AbstractLogicalOperator assignTmpOp =
                            (AbstractLogicalOperator) assignsBeforeTopOpRef.get(i).getValue();
                    assignTmpOp.setExecutionMode(ExecutionMode.PARTITIONED);
                    VariableUtilities.substituteVariables(assignTmpOp, origVarToNewVarInLeftPathMap, context);
                    context.computeAndSetTypeEnvironmentForOperator(assignTmpOp);
                }
            }
            newSelectOpInLeftPath.getInputs().clear();
            newSelectOpInLeftPath.getInputs()
                    .add(new MutableObject<ILogicalOperator>(assignsBeforeTopOpRef.get(0).getValue()));
        } else {
            newSelectOpInLeftPath.getInputs().add(new MutableObject<ILogicalOperator>(primaryIndexUnnestMapOp));
        }
        newSelectOpInLeftPath.setExecutionMode(ExecutionMode.PARTITIONED);
        context.computeAndSetTypeEnvironmentForOperator(newSelectOpInLeftPath);

        // Now, we take care of the right path (instantTryLock on PK success path).
        ILogicalOperator currentTopOpInRightPath = splitOp;
        // For an R-Tree index, if there are operators that are using the secondary key field value,
        // we need to reconstruct that field value from the result of R-Tree search.
        // This is done by adding the following assign operator that we have made in the beginning of this method.
        if (skVarAssignOpInRightPath != null) {
            skVarAssignOpInRightPath.getInputs().add(new MutableObject<ILogicalOperator>(splitOp));
            skVarAssignOpInRightPath.setExecutionMode(ExecutionMode.PARTITIONED);
            context.computeAndSetTypeEnvironmentForOperator(skVarAssignOpInRightPath);
            currentTopOpInRightPath = skVarAssignOpInRightPath;
        }

        // For an R-Tree index, if the given query shape is not RECTANGLE or POINT,
        // we need to add the original SELECT operator to filter out the false positive results.
        // (e.g., spatial-intersect($o.pointfield, create-circle(create-point(30.0,70.0), 5.0)) )
        //
        // Also, for a B-Tree composite index, we need to apply SELECT operators in the right path
        // to remove any false positive results from the secondary composite index search.
        //
        // Lastly, if there is an index-nested-loop-join and the join contains more conditions
        // other than joining fields, then those conditions need to be applied to filter out
        // false positive results in the right path.
        // (e.g., where $a.authors /*+ indexnl */ = $b.authors and $a.id = $b.id   <- authors:SK, id:PK)
        if (((idxType == IndexType.RTREE || uniqueUsedVarsInTopOp.size() > 1) && requireVerificationAfterSIdxSearch)
                || anyRealTypeConvertedToIntegerType || IndexUtil.includesUnknowns(secondaryIndex)) {
            // Creates a new SELECT operator by deep-copying the SELECT operator in the left path
            // since we need to change the variable reference in the SELECT operator.
            // For the index-nested-loop join case, we copy the condition of the join operator.
            ILogicalExpression conditionRefExpr2 = conditionRef.getValue().cloneExpression();
            newSelectOpInRightPath =
                    retainMissing
                            ? new SelectOperator(new MutableObject<>(conditionRefExpr2), leftOuterMissingValue,
                                    newMissingPlaceHolderVar)
                            : new SelectOperator(new MutableObject<>(conditionRefExpr2));
            newSelectOpInRightPath.setSourceLocation(conditionRefExpr2.getSourceLocation());
            newSelectOpInRightPath.getInputs().add(new MutableObject<ILogicalOperator>(currentTopOpInRightPath));
            VariableUtilities.substituteVariables(newSelectOpInRightPath, origVarToSIdxUnnestMapOpVarMap, context);
            VariableUtilities.substituteVariables(newSelectOpInRightPath, origSKFieldVarToNewSKFieldVarMap, context);
            newSelectOpInRightPath.setExecutionMode(ExecutionMode.PARTITIONED);
            context.computeAndSetTypeEnvironmentForOperator(newSelectOpInRightPath);
            currentTopOpInRightPath = newSelectOpInRightPath;
        }

        // Adds the new missing place holder in case of a left-outer-join if it's not been added yet.
        // The assumption here is that this variable is the first PK variable that was set.
        if (retainMissing && newMissingPlaceHolderForLOJ == primaryIndexUnnestVars.get(0)
                && !findVarInTripleVarList(unionVarMap, newMissingPlaceHolderForLOJ, false)) {
            unionVarMap.add(new Triple<>(origPKRecAndSKVarToleftPathMap.get(newMissingPlaceHolderForLOJ),
                    pkVarsFromSIdxUnnestMapOp.get(0), newMissingPlaceHolderForLOJ));
        }

        // UNIONALL operator that combines both paths.
        unionAllOp = new UnionAllOperator(unionVarMap);
        unionAllOp.setSourceLocation(sourceLoc);
        unionAllOp.getInputs().add(new MutableObject<ILogicalOperator>(newSelectOpInLeftPath));
        unionAllOp.getInputs().add(new MutableObject<ILogicalOperator>(currentTopOpInRightPath));

        unionAllOp.setExecutionMode(ExecutionMode.PARTITIONED);
        context.computeAndSetTypeEnvironmentForOperator(unionAllOp);

        // If an assign operator that keeps constant values was added, set the UNIONALL operator as its child.
        if (!constAssignVars.isEmpty() && !constantAssignVarUsedInTopOp) {
            constAssignOp.getInputs().clear();
            constAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(unionAllOp));
            constAssignOp.setExecutionMode(ExecutionMode.PARTITIONED);
            context.computeAndSetTypeEnvironmentForOperator(constAssignOp);

            // This constant assign operator is the new child of the first operator after the original
            // SELECT or JOIN operator.
            currentOpAfterTopOp = afterTopOpRefs.get(afterTopOpRefs.size() - 1).getValue();
            currentOpAfterTopOp.getInputs().clear();
            currentOpAfterTopOp.getInputs().add(new MutableObject<ILogicalOperator>(constAssignOp));
            context.computeAndSetTypeEnvironmentForOperator(currentOpAfterTopOp);
            afterTopOpRefs.add(new MutableObject<ILogicalOperator>(constAssignOp));
        }

        // Index-only plan is now constructed. Return this operator to the caller.
        return unionAllOp;
    }