[ASTERIXDB-3536][IDX] Add support for heterogeneous index

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

details:
Introducing the heterogeneous index which allows creating an
index on a field without specifying its type. This means the
field’s type no longer needs to be defined in the data type
or the index creation DDL.

Ext-ref: MB-27845

Change-Id: I03b5edfbc304cdb9f36e2b382050781811a14759
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/18522
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
Tested-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
index dd8f630..df8d4cd 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
@@ -860,7 +860,7 @@
 
                 AbstractFunctionCallExpression theFieldAccessFunc;
                 LogicalVariable fieldVar = context.newVar();
-                if (fieldType == null) {
+                if (fieldType == null && !ATypeTag.ANY.equals(skType.getTypeTag())) {
                     // Open field. must prevent inlining to maintain the cast before the primaryOp and
                     // make handling of records with incorrect value type for this field easier and cleaner
                     context.addNotToBeInlinedVar(fieldVar);
@@ -906,6 +906,9 @@
         FunctionIdentifier skFun = null;
         IAObject fmtArg = null;
         Pair<FunctionIdentifier, IAObject> castExpr;
+        if (ATypeTag.ANY.equals(skType.getTypeTag())) {
+            return new IndexFieldId(skSrc, skName, skType.getTypeTag(), skFun, fmtArg);
+        }
         if (fieldType == null) {
             // open field
             castExpr = getCastExpression(index, skType, srcLoc);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
index b5041f0..24ef2d7 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
@@ -485,7 +485,9 @@
 
                     for (int j = 0; j < matchedTypes.size(); j++) {
                         for (int k = j + 1; k < matchedTypes.size(); k++) {
-                            typeMatch &= isMatched(elementTypes.get(j), elementTypes.get(k), jaccardSimilarity);
+                            if (!ATypeTag.ANY.equals(elementTypes.get(k).getTypeTag())) {
+                                typeMatch &= isMatched(elementTypes.get(j), elementTypes.get(k), jaccardSimilarity);
+                            }
                         }
                     }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
index 1ff90c6..9b30dfb 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
@@ -1133,20 +1133,40 @@
         }
     }
 
-    private static AbstractUnnestMapOperator createFinalNonIndexOnlySearchPlan(Dataset dataset,
-            ILogicalOperator inputOp, IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput,
-            boolean retainMissing, boolean requiresBroadcast, boolean requiresDistinct,
-            List<LogicalVariable> primaryKeyVars, List<LogicalVariable> primaryIndexUnnestVars,
-            List<LogicalVariable> auxDistinctVars, List<Object> primaryIndexOutputTypes,
-            IAlgebricksConstantValue leftOuterMissingValue) throws AlgebricksException {
-        SourceLocation sourceLoc = inputOp.getSourceLocation();
+    private static AbstractUnnestMapOperator createFinalNonIndexOnlySearchPlan(
+            List<Mutable<ILogicalOperator>> afterTopOpRefs, Dataset dataset, ILogicalOperator inputOp,
+            IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput, boolean retainMissing,
+            boolean requiresBroadcast, boolean requiresDistinct, List<LogicalVariable> primaryKeyVars,
+            List<LogicalVariable> primaryIndexUnnestVars, List<LogicalVariable> auxDistinctVars,
+            List<Object> primaryIndexOutputTypes, IAlgebricksConstantValue leftOuterMissingValue,
+            ARecordType recordType, ARecordType metaRecordType, OptimizableOperatorSubTree subTree,
+            Index secondaryIndex, Mutable<ILogicalOperator> topOpRef,
+            List<Mutable<ILogicalOperator>> assignsBeforeTopOpRef, Mutable<ILogicalExpression> conditionRef,
+            LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
 
+        SourceLocation sourceLoc = inputOp.getSourceLocation();
         // Sanity check: requiresDistinct and sortPrimaryKeys are mutually exclusive.
         if (requiresDistinct && sortPrimaryKeys) {
             throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
                     "Non-index search plan " + "cannot include a DISTINCT and an ORDER.");
         }
+        ILogicalOperator op = null;
+        IndexType idxType = secondaryIndex.getIndexType();
 
+        // for now the additional selection is only added just for the value index.
+        if (Index.IndexCategory.of(idxType) == Index.IndexCategory.VALUE) {
+            Index.ValueIndexDetails secondaryIndexDetails = (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
+            List<List<String>> chosenIndexFieldNames = secondaryIndexDetails.getKeyFieldNames();
+            List<IAType> secodaryKeysType = secondaryIndexDetails.getKeyFieldTypes();
+            if (idxType == IndexType.BTREE && secodaryKeysType.contains(BuiltinType.ANY)) {
+                op = additionalSelectForHeterogeneousIndex(chosenIndexFieldNames, afterTopOpRefs, dataset, inputOp,
+                        context, retainMissing, leftOuterMissingValue, recordType, metaRecordType, subTree,
+                        secondaryIndex, topOpRef, assignsBeforeTopOpRef, conditionRef, newMissingPlaceHolderForLOJ);
+            }
+        }
+        if (op == null) {
+            op = inputOp;
+        }
         // If we have an array index, then we must only give unique keys to our primary-index scan.
         DistinctOperator distinct = null;
         if (requiresDistinct) {
@@ -1165,7 +1185,7 @@
             }
             distinct = new DistinctOperator(distinctExprs);
             distinct.setSourceLocation(sourceLoc);
-            distinct.getInputs().add(new MutableObject<>(inputOp));
+            distinct.getInputs().add(new MutableObject<>(op));
             distinct.setExecutionMode(ExecutionMode.LOCAL);
             context.computeAndSetTypeEnvironmentForOperator(distinct);
         }
@@ -1182,7 +1202,7 @@
                 order.getOrderExpressions().add(new Pair<>(OrderOperator.ASC_ORDER, vRef));
             }
             // The secondary-index search feeds into the sort.
-            order.getInputs().add(new MutableObject<>(inputOp));
+            order.getInputs().add(new MutableObject<>(op));
             order.setExecutionMode(ExecutionMode.LOCAL);
             context.computeAndSetTypeEnvironmentForOperator(order);
         }
@@ -1196,7 +1216,7 @@
         } else if (sortPrimaryKeys) {
             primaryIndexUnnestMapOp.getInputs().add(new MutableObject<>(order));
         } else {
-            primaryIndexUnnestMapOp.getInputs().add(new MutableObject<>(inputOp));
+            primaryIndexUnnestMapOp.getInputs().add(new MutableObject<>(op));
         }
         context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestMapOp);
         primaryIndexUnnestMapOp.setExecutionMode(ExecutionMode.PARTITIONED);
@@ -1831,9 +1851,12 @@
                 joinPKVars = Collections.emptyList();
             }
 
-            return createFinalNonIndexOnlySearchPlan(dataset, inputOp, context, !isArrayIndex && sortPrimaryKeys,
-                    retainInput, retainMissing, requiresBroadcast, isArrayIndex, pkVarsFromSIdxUnnestMapOp,
-                    primaryIndexUnnestVars, joinPKVars, primaryIndexOutputTypes, leftOuterMissingValue);
+            return createFinalNonIndexOnlySearchPlan(afterTopOpRefs, dataset, inputOp, context,
+                    !isArrayIndex && sortPrimaryKeys, retainInput, retainMissing, requiresBroadcast, isArrayIndex,
+                    pkVarsFromSIdxUnnestMapOp, primaryIndexUnnestVars, joinPKVars, primaryIndexOutputTypes,
+                    leftOuterMissingValue, recordType, metaRecordType, indexSubTree, secondaryIndex, topOpRef,
+                    assignsBeforeTopOpRef, conditionRef, newMissingPlaceHolderForLOJ);
+
         } else if (!isArrayIndex) {
             // Index-only plan case: creates a UNIONALL operator that has two paths after the secondary unnest-map op,
             // and returns it.
@@ -3086,7 +3109,6 @@
                     }
                 }
 
-                // Otherwise... recurse.
                 Pair<List<String>, Integer> parentFieldNames =
                         !isIndexOnFunction
                                 ? getFieldNameAndStepsFromSubTree(optFuncExpr, subTree, assignAndExpressionIndexes[0],
@@ -3337,4 +3359,155 @@
         }
         return isDataSourceVariableFound;
     }
+
+    private static void splitIntoConjuncts(ILogicalExpression condition, List<Mutable<ILogicalExpression>> conjuncts) {
+        if (condition.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+            return;
+        }
+        AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) condition;
+        if (funcExpr.getFunctionIdentifier() == BuiltinFunctions.AND) {
+            for (Mutable<ILogicalExpression> arg : funcExpr.getArguments()) {
+                splitIntoConjuncts(arg.getValue(), conjuncts);
+            }
+        } else {
+            conjuncts.add(new MutableObject<>(condition));
+        }
+    }
+
+    protected static ILogicalExpression filterCondition(ILogicalExpression condition,
+            Set<LogicalVariable> listOfAcceptableVAR) {
+        List<Mutable<ILogicalExpression>> conjuncts = new ArrayList<>();
+        List<Mutable<ILogicalExpression>> filteredConjuncts = new ArrayList<>();
+        splitIntoConjuncts(condition, conjuncts);
+        for (Mutable<ILogicalExpression> conjunct : conjuncts) {
+            if (containsOnlyAllowedVariables(conjunct.getValue(), listOfAcceptableVAR)) {
+                filteredConjuncts.add(conjunct);
+            }
+        }
+
+        if (filteredConjuncts.size() == 1) {
+            return filteredConjuncts.get(0).getValue();
+        } else if (!filteredConjuncts.isEmpty()) {
+            ScalarFunctionCallExpression combinedCondition =
+                    new ScalarFunctionCallExpression(BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.AND));
+            combinedCondition.getArguments().addAll(filteredConjuncts);
+            combinedCondition.setSourceLocation(condition.getSourceLocation());
+            return combinedCondition;
+        } else {
+            return null;
+        }
+    }
+
+    private static boolean containsOnlyAllowedVariables(ILogicalExpression expression,
+            Set<LogicalVariable> allowedVars) {
+
+        Set<LogicalVariable> usedVars = new HashSet<>();
+        expression.getUsedVariables(usedVars);
+        for (LogicalVariable var : usedVars) {
+            if (!allowedVars.contains(var)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static ILogicalOperator additionalSelectForHeterogeneousIndex(List<List<String>> chosenIndexFieldNames,
+            List<Mutable<ILogicalOperator>> afterTopOpRefs, Dataset dataset, ILogicalOperator inputOp,
+            IOptimizationContext context, boolean retainMissing, IAlgebricksConstantValue leftOuterMissingValue,
+            ARecordType recordType, ARecordType metaRecordType, OptimizableOperatorSubTree subTree,
+            Index secondaryIndex, Mutable<ILogicalOperator> topOpRef,
+            List<Mutable<ILogicalOperator>> assignsBeforeTopOpRef, Mutable<ILogicalExpression> conditionRef,
+            LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
+        List<LogicalVariable> skVarsFromSIdxUnnestMap = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset,
+                recordType, metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.SECONDARY_KEY);
+
+        List<LogicalVariable> usedVarsInConditionOp = 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<>();
+        VariableUtilities.getUsedVariables(topOpRef.getValue(), usedVarsInConditionOp);
+        // Gets all variables from the right (inner) branch.
+        List<LogicalVariable> liveVarsInInnerBranch = new ArrayList<>();
+        VariableUtilities.getLiveVariables(subTree.getRootRef().getValue(), liveVarsInInnerBranch);
+        for (Iterator<LogicalVariable> iterator = usedVarsInConditionOp.iterator(); iterator.hasNext();) {
+            LogicalVariable v = iterator.next();
+            if (!liveVarsInInnerBranch.contains(v)) {
+                iterator.remove();
+            }
+        }
+        List<LogicalVariable> uniqueUsedVarsInConditionOp = new ArrayList<>();
+        copyVarsToAnotherList(usedVarsInConditionOp, uniqueUsedVarsInConditionOp);
+
+        List<LogicalVariable> producedVarsInAssignsBeforeCondtionOp = new ArrayList<>();
+        List<LogicalVariable> varsTmpList = new ArrayList<>();
+        if (assignsBeforeTopOpRef != null && !assignsBeforeTopOpRef.isEmpty()) {
+            for (int i = 0; i < assignsBeforeTopOpRef.size(); i++) {
+                ILogicalOperator assignBeforeTopOp = assignsBeforeTopOpRef.get(i).getValue();
+                varsTmpList.clear();
+                VariableUtilities.getProducedVariables(assignBeforeTopOp, varsTmpList);
+                copyVarsToAnotherList(varsTmpList, producedVarsInAssignsBeforeCondtionOp);
+            }
+        }
+        List<LogicalVariable> usedVarsAfterSelect = new ArrayList<>();
+        HashSet<LogicalVariable> varsTmpSet = new HashSet<>();
+        if (afterTopOpRefs != null) {
+            for (Mutable<ILogicalOperator> afterTopOpRef : afterTopOpRefs) {
+                varsTmpSet.clear();
+                OperatorPropertiesUtil.getFreeVariablesInOp(afterTopOpRef.getValue(), varsTmpSet);
+                copyVarsToAnotherList(varsTmpSet, usedVarsAfterSelect);
+            }
+        }
+
+        for (LogicalVariable tVar : usedVarsAfterSelect) {
+
+            int sIndexIdx = chosenIndexFieldNames.indexOf(subTree.getVarsToFieldNameMap().get(tVar));
+            if (sIndexIdx == -1) {
+                continue;
+            }
+            // 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, skVarsFromSIdxUnnestMap.get(sIndexIdx));
+        }
+
+        List<LogicalVariable> varsUsedInTopOpButNotAfterwards = new ArrayList<>();
+        copyVarsToAnotherList(uniqueUsedVarsInConditionOp, varsUsedInTopOpButNotAfterwards);
+        varsUsedInTopOpButNotAfterwards.removeAll(usedVarsAfterSelect);
+        // 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 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.)
+        for (LogicalVariable v : varsUsedInTopOpButNotAfterwards) {
+            int sIndexIdx = chosenIndexFieldNames.indexOf(subTree.getVarsToFieldNameMap().get(v));
+            if (sIndexIdx == -1) {
+                continue;
+            }
+            origVarToSIdxUnnestMapOpVarMap.put(v, skVarsFromSIdxUnnestMap.get(sIndexIdx));
+        }
+
+        // The additional select which will be added after the secondary index search
+        // only the conditions which include the variables used before or in secondary index search
+        // any other predicates which includes other variables should not be added here.
+        List<LogicalVariable> usedVarInInput = new ArrayList<>();
+        VariableUtilities.getUsedVariables(inputOp, usedVarInInput);
+        Set<LogicalVariable> skAcceptableVars = new HashSet<>();
+        skAcceptableVars.addAll(origVarToSIdxUnnestMapOpVarMap.keySet());
+        skAcceptableVars.addAll(usedVarInInput);
+        ILogicalExpression conditionRefExpr =
+                filterCondition(conditionRef.getValue().cloneExpression(), skAcceptableVars);
+        if (conditionRefExpr != null) {
+            LogicalVariable newMissingPlaceHolderVar = null;
+            SelectOperator newSelectOp =
+                    retainMissing
+                            ? new SelectOperator(new MutableObject<>(conditionRefExpr), leftOuterMissingValue,
+                                    newMissingPlaceHolderVar)
+                            : new SelectOperator(new MutableObject<>(conditionRefExpr));
+            newSelectOp.setSourceLocation(conditionRefExpr.getSourceLocation());
+            newSelectOp.getInputs().add(new MutableObject(inputOp));
+            VariableUtilities.substituteVariables(newSelectOp, origVarToSIdxUnnestMapOpVarMap, context);
+            newSelectOp.setExecutionMode(ExecutionMode.PARTITIONED);
+            context.computeAndSetTypeEnvironmentForOperator(newSelectOp);
+            return newSelectOp;
+        }
+        return null;
+    }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
index 34976b6..579ad51 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
@@ -276,6 +276,7 @@
                     case UUID:
                     case YEARMONTHDURATION:
                     case DAYTIMEDURATION:
+                    case ANY:
                         break;
                     default:
                         throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
diff --git a/asterixdb/asterix-app/data/heterogeneousData.adm b/asterixdb/asterix-app/data/heterogeneousData.adm
new file mode 100644
index 0000000..0d96209
--- /dev/null
+++ b/asterixdb/asterix-app/data/heterogeneousData.adm
@@ -0,0 +1,8 @@
+{ "userId": 1, "email": "john.doe@example.com", "username": "johndoe", "isActive": true, "occupation": "teacher","age": 23}
+{ "userId": 2, "email": "jane.smith@example.com", "username": "janesmith", "isActive": false, "occupation": "firefighter" }
+{ "userId": 3, "email": "michael.brown@example.com", "username": "michaelb", "isActive": true, "age": "five", "occupation":4 }
+{ "userId": 4, "email": "emily.davis@example.com", "username": "emilyd", "isActive": true, "age": 23.1 }
+{ "userId": 5, "email": "chris.jones@example.com", "username": "chrisj", "isActive": false, "age": "old" }
+{ "userId": 6, "email": "patricia.garcia@example.com", "username": "patriciag", "isActive": true, "age": "45" }
+{ "userId": 7, "email": "linda.martinez@example.com", "username": "lindam", "isActive": false }
+{ "userId": 8, "email": "robert.lee@example.com", "username": "robertl", "isActive": true, "age": 31 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 1d21ce2..ed93838 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -1374,6 +1374,7 @@
             List<List<IAType>> indexFieldTypes = new ArrayList<>(indexedElementsCount);
             boolean hadUnnest = false;
             boolean overridesFieldTypes = false;
+            boolean isHeterogeneousIndex = false;
 
             // this set is used to detect duplicates in the specified keys in the create
             // index statement
@@ -1447,7 +1448,7 @@
                         projectTypeNullable = inputTypeNullable;
                         projectTypeMissable = inputTypeMissable;
                     } else if (inputTypePrime == null) {
-                        projectTypePrime = null; // ANY
+                        projectTypePrime = null;
                         projectTypeNullable = projectTypeMissable = true;
                     } else {
                         if (inputTypePrime.getTypeTag() != ATypeTag.OBJECT) {
@@ -1528,38 +1529,50 @@
                     }
 
                     if (fieldTypePrime == null) {
-                        if (projectPath != null) {
-                            String fieldName = LogRedactionUtil.userData(RecordUtil.toFullyQualifiedName(projectPath));
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR,
-                                    indexedElement.getSourceLocation(),
-                                    "cannot find type of field '" + fieldName + "'");
+                        if (indexType != IndexType.BTREE) {
+                            if (projectPath != null) {
+                                String fieldName =
+                                        LogRedactionUtil.userData(RecordUtil.toFullyQualifiedName(projectPath));
+                                throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                                        indexedElement.getSourceLocation(),
+                                        "cannot find type of field '" + fieldName + "'");
+                            }
+                            // projectPath == null should only be the case with array index having UNNESTs only
+                            if (indexedElement.hasUnnest()) {
+                                List<List<String>> unnestList = indexedElement.getUnnestList();
+                                List<String> arrayField = unnestList.get(unnestList.size() - 1);
+                                String fieldName =
+                                        LogRedactionUtil.userData(RecordUtil.toFullyQualifiedName(arrayField));
+                                throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                                        indexedElement.getSourceLocation(),
+                                        "cannot find type of elements of field '" + fieldName + "'");
+                            }
+                        } else {
+                            fieldTypePrime = BuiltinType.ANY;
+                            isHeterogeneousIndex = true;
+                            fieldTypeNullable = fieldTypeMissable = false;
                         }
-                        // projectPath == null should only be the case with array index having UNNESTs only
-                        if (indexedElement.hasUnnest()) {
-                            List<List<String>> unnestList = indexedElement.getUnnestList();
-                            List<String> arrayField = unnestList.get(unnestList.size() - 1);
-                            String fieldName = LogRedactionUtil.userData(RecordUtil.toFullyQualifiedName(arrayField));
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR,
-                                    indexedElement.getSourceLocation(),
-                                    "cannot find type of elements of field '" + fieldName + "'");
-                        }
-                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                                indexedElement.getSourceLocation(), "cannot find type of field");
+                    }
+                    if (ATypeTag.ANY.equals(fieldTypePrime.getTypeTag()) && stmtCreateIndex.hasExcludeUnknownKey()) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, indexedElement.getSourceLocation(),
+                                "Cannot specify exclude/include unknown for untyped keys in the index definition.");
                     }
                     validateIndexFieldType(indexType, fieldTypePrime, projectPath, indexedElement.getSourceLocation());
 
                     IAType fieldType =
                             KeyFieldTypeUtil.makeUnknownableType(fieldTypePrime, fieldTypeNullable, fieldTypeMissable);
+                    if (isHeterogeneousIndex && !ATypeTag.ANY.equals(fieldType.getTypeTag())) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "Typed keys cannot be combined with untyped keys in the index definition.");
+                    }
                     fieldTypes.add(fieldType);
                 }
-
                 // Try to add the key & its source to the set of keys for duplicate detection.
                 if (!indexKeysSet.add(indexedElement.toIdentifier())) {
                     throw new AsterixException(ErrorCode.INDEX_ILLEGAL_REPETITIVE_FIELD,
                             indexedElement.getSourceLocation(),
                             LogRedactionUtil.userData(indexedElement.getProjectListDisplayForm()));
                 }
-
                 indexFieldTypes.add(fieldTypes);
             }
 
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.1.ddl.sqlpp
new file mode 100644
index 0000000..040968e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.1.ddl.sqlpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk1(tenkType) primary key unique2;
+
+create index idx_tenk1_1k on tenk1(thousand);
+
+create dataset tenk2(tenkType) primary key unique2;
+
+create index idx_tenk2_1k on tenk2(thousand);
+
+create index idx_tenk2_1k_2k on tenk2(thousand, twothousand);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.2.update.sqlpp
new file mode 100644
index 0000000..c6189ce
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.2.update.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+use test;
+
+
+Upsert INTO tenk1 (
+    [{ "unique1": 5057, "unique2": 6, "two": 1, "four": 1, "ten": 7, "twenty": 17, "hundred": 57, "thousand": 57, "twothousand": 1057, "fivethous": 57, "tenthous": 5057, "odd100": 114, "even100": 115, "stringu1": "NMAAAA", "stringu2": "GAAAAA", "string4": "OOOOxx" }
+, { "unique1": 1891, "unique2": 1, "two": 1, "four": 3, "ten": 1, "twenty": 11, "hundred": 91, "thousand": 891, "twothousand": 1891, "fivethous": 1891, "tenthous": 1891, "odd100": 182, "even100": 183, "stringu1": "TUAAAA", "stringu2": "BAAAAA", "string4": "HHHHxx" }
+, { "unique1": 3420, "unique2": 2, "two": 0, "four": 0, "ten": 0, "twenty": 0, "hundred": 20, "thousand": 420, "twothousand": 1420, "fivethous": 3420, "tenthous": 3420, "odd100": 40, "even100": 41, "stringu1": "OBAAAA", "stringu2": "CAAAAA", "string4": "OOOOxx" }
+, { "unique1": 7164, "unique2": 4, "two": 0, "four": 0, "ten": 4, "twenty": 4, "hundred": 64, "thousand": 164, "twothousand": 1164, "fivethous": 2164, "tenthous": 7164, "odd100": 128, "even100": 129, "stringu1": "OPAAAA", "stringu2": "EAAAAA", "string4": "AAAAxx" }
+, { "unique1": 8800, "unique2": 0, "two": 0, "four": 0, "ten": 0, "twenty": 0, "hundred": 0, "thousand": 800, "twothousand": 800, "fivethous": 3800, "tenthous": 8800, "odd100": 0, "even100": 1, "stringu1": "MAAAAA", "stringu2": "AAAAAA", "string4": "AAAAxx" }
+, { "unique1": 4321, "unique2": 8, "two": 1, "four": 1, "ten": 1, "twenty": 1, "hundred": 21, "thousand": 321, "twothousand": 321, "fivethous": 4321, "tenthous": 4321, "odd100": 42, "even100": 43, "stringu1": "FKAAAA", "stringu2": "IAAAAA", "string4": "AAAAxx" }
+, { "unique1": 3043, "unique2": 9, "two": 1, "four": 3, "ten": 3, "twenty": 3, "hundred": 43, "thousand": 43, "twothousand": 1043, "fivethous": 3043, "tenthous": 3043, "odd100": 86, "even100": 87, "stringu1": "BNAAAA", "stringu2": "JAAAAA", "string4": "HHHHxx" }
+, { "unique1": 9850, "unique2": 3, "two": 0, "four": 2, "ten": 0, "twenty": 10, "hundred": 50, "thousand": 850, "twothousand": 1850, "fivethous": 4850, "tenthous": 9850, "odd100": 100, "even100": 101, "stringu1": "WOAAAA", "stringu2": "DAAAAA", "string4": "VVVVxx" }
+, { "unique1": 8009, "unique2": 5, "two": 1, "four": 1, "ten": 9, "twenty": 9, "hundred": 9, "thousand": 9, "twothousand": 9, "fivethous": 3009, "tenthous": 8009, "odd100": 18, "even100": 19, "stringu1": "BWAAAA", "stringu2": "FAAAAA", "string4": "HHHHxx" }
+, { "unique1": 6701, "unique2": 7, "two": 1, "four": 1, "ten": 1, "twenty": 1, "hundred": 1, "thousand": 701, "twothousand": 701, "fivethous": 1701, "tenthous": 6701, "odd100": 2, "even100": 3, "stringu1": "TXAAAA", "stringu2": "HAAAAA", "string4": "VVVVxx" }]);
+
+
+
+Upsert INTO tenk2 (
+    [{ "unique1": 5057, "unique2": 6, "two": 1, "four": 1, "ten": 7, "twenty": 17, "hundred": 57, "thousand": 57, "twothousand": 1057, "fivethous": 57, "tenthous": 5057, "odd100": 114, "even100": 115, "stringu1": "NMAAAA", "stringu2": "GAAAAA", "string4": "OOOOxx" }
+, { "unique1": 1891, "unique2": 1, "two": 1, "four": 3, "ten": 1, "twenty": 11, "hundred": 91, "thousand": 891, "twothousand": 1891, "fivethous": 1891, "tenthous": 1891, "odd100": 182, "even100": 183, "stringu1": "TUAAAA", "stringu2": "BAAAAA", "string4": "HHHHxx" }
+, { "unique1": 3420, "unique2": 2, "two": 0, "four": 0, "ten": 0, "twenty": 0, "hundred": 20, "thousand": 420, "twothousand": 1420, "fivethous": 3420, "tenthous": 3420, "odd100": 40, "even100": 41, "stringu1": "OBAAAA", "stringu2": "CAAAAA", "string4": "OOOOxx" }
+, { "unique1": 7164, "unique2": 4, "two": 0, "four": 0, "ten": 4, "twenty": 4, "hundred": 64, "thousand": 164, "twothousand": 1164, "fivethous": 2164, "tenthous": 7164, "odd100": 128, "even100": 129, "stringu1": "OPAAAA", "stringu2": "EAAAAA", "string4": "AAAAxx" }
+, { "unique1": 8800, "unique2": 0, "two": 0, "four": 0, "ten": 0, "twenty": 0, "hundred": 0, "thousand": 800, "twothousand": 800, "fivethous": 3800, "tenthous": 8800, "odd100": 0, "even100": 1, "stringu1": "MAAAAA", "stringu2": "AAAAAA", "string4": "AAAAxx" }
+, { "unique1": 4321, "unique2": 8, "two": 1, "four": 1, "ten": 1, "twenty": 1, "hundred": 21, "thousand": 321, "twothousand": 321, "fivethous": 4321, "tenthous": 4321, "odd100": 42, "even100": 43, "stringu1": "FKAAAA", "stringu2": "IAAAAA", "string4": "AAAAxx" }
+, { "unique1": 3043, "unique2": 9, "two": 1, "four": 3, "ten": 3, "twenty": 3, "hundred": 43, "thousand": 43, "twothousand": 1043, "fivethous": 3043, "tenthous": 3043, "odd100": 86, "even100": 87, "stringu1": "BNAAAA", "stringu2": "JAAAAA", "string4": "HHHHxx" }
+, { "unique1": 9850, "unique2": 3, "two": 0, "four": 2, "ten": 0, "twenty": 10, "hundred": 50, "thousand": 850, "twothousand": 1850, "fivethous": 4850, "tenthous": 9850, "odd100": 100, "even100": 101, "stringu1": "WOAAAA", "stringu2": "DAAAAA", "string4": "VVVVxx" }
+, { "unique1": 8009, "unique2": 5, "two": 1, "four": 1, "ten": 9, "twenty": 9, "hundred": 9, "thousand": 9, "twothousand": 9, "fivethous": 3009, "tenthous": 8009, "odd100": 18, "even100": 19, "stringu1": "BWAAAA", "stringu2": "FAAAAA", "string4": "HHHHxx" }
+, { "unique1": 6701, "unique2": 7, "two": 1, "four": 1, "ten": 1, "twenty": 1, "hundred": 1, "thousand": 701, "twothousand": 701, "fivethous": 1701, "tenthous": 6701, "odd100": 2, "even100": 3, "stringu1": "TXAAAA", "stringu2": "HAAAAA", "string4": "VVVVxx" }]);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.query.sqlpp
new file mode 100644
index 0000000..c07004d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+use test;
+
+/*
+ * indexnl hint. use specified index (idx_tenk2_1k_2k)
+ */
+
+select value tenk2.unique1
+from tenk1, tenk2
+where
+  tenk1.unique2 < 1
+and
+  tenk1.thousand /* +indexnl(idx_tenk2_1k_2k) */ = tenk2.thousand
+order by tenk2.unique1;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.query.sqlpp
new file mode 100644
index 0000000..b5ac9ad
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select value tenk2.unique1
+from tenk1, tenk2
+where
+  tenk1.unique2 < 2
+and
+  tenk1.thousand /* +indexnl() */ = tenk2.thousand
+order by tenk2.unique1;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.1.ddl.sqlpp
new file mode 100644
index 0000000..200dba3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.1.ddl.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+
+DROP DATAVERSE test IF EXISTS;
+CREATE DATAVERSE test;
+USE test;
+
+CREATE TYPE untyped AS {id: string};
+CREATE TYPE typed AS {id: string};
+
+CREATE DATASET ds_outer_untyped(untyped) primary key id;
+CREATE DATASET ds_outer_typed(typed) primary key id;
+
+CREATE DATASET ds_inner_untyped(untyped) primary key id;
+CREATE DATASET ds_inner_typed(typed) primary key id;
+
+CREATE INDEX idx_c_int32 ON ds_inner_untyped(c_int32);
+CREATE INDEX idx_c_int32 ON ds_inner_typed(c_int32);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.2.update.sqlpp
new file mode 100644
index 0000000..5b41d9b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.2.update.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+
+USE test;
+
+UPSERT INTO ds_outer_untyped [
+{'id': "o_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}];
+
+UPSERT INTO ds_outer_typed [
+{'id': "o_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}];
+
+UPSERT INTO ds_inner_untyped [
+{ 'id': "i_untyped:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_untyped:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_untyped:03", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
+
+UPSERT INTO ds_inner_typed [
+{ 'id': "i_typed:01", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_typed:02", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null},
+{ 'id': "i_typed:03", 'c_any':null, 'c_int8':null, 'c_int16':null, 'c_int32':null, 'c_int64':null, 'c_float':null, 'c_double':null}
+];
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.query.sqlpp
new file mode 100644
index 0000000..25d7c9d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'true';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32) ;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.query.sqlpp
new file mode 100644
index 0000000..065273c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+USE test;
+
+SET `compiler.sort.parallel` 'false';
+SET `compiler.indexonly` 'false';
+SELECT t1.id AS t1_id, t2.id AS t2_id
+FROM ds_outer_untyped t1 LEFT JOIN ds_inner_typed t2 ON int32(t1.c_int32) /* +indexnl */ = (t2.c_int32);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.1.ddl.sqlpp
new file mode 100644
index 0000000..d425059
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.1.ddl.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+
+
+drop  dataverse test if exists;
+create  dataverse test;
+use test;
+
+CREATE TYPE UserType AS {
+  userId : int,
+  email : string,
+  username:string,
+  isActive : boolean };
+
+create dataset UserTypes(UserType) primary key userId;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.2.update.sqlpp
new file mode 100644
index 0000000..05a21fa
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.2.update.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+use test;
+
+load  dataset UserTypes using localfs ((`path`=`asterix_nc1://data/heterogeneousData.adm`),(`format`=`adm`));
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.3.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.3.ddl.sqlpp
new file mode 100644
index 0000000..027d408
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.3.ddl.sqlpp
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+use test;
+
+create index idx_user_age  on UserTypes(age);
+create index idx_user_age_occupation1  on UserTypes(age, occupation);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.4.query.sqlpp
new file mode 100644
index 0000000..944054a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-bulkLoad.4.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+USE test;
+
+select *
+from UserTypes u
+where u.age > 20
+order by userId;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.1.ddl.sqlpp
new file mode 100644
index 0000000..e0e6961
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.1.ddl.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+
+drop  dataverse test if exists;
+create  dataverse test;
+use test;
+
+CREATE TYPE UserType AS {
+  userId : int,
+  email : string,
+  username:string,
+  isActive : boolean };
+
+create dataset UserTypes(UserType) primary key userId;
+create dataset UserTypes2(UserType) primary key userId;
+
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.2.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.2.ddl.sqlpp
new file mode 100644
index 0000000..2d84c34
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.2.ddl.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+
+use test;
+
+create index idx_user_age  on UserTypes(age);
+create index idx_user_age_occupation1  on UserTypes(age, occupation);
+create index idx_user_age_occupation2  on UserTypes2(age, occupation);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.3.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.3.update.sqlpp
new file mode 100644
index 0000000..17fb100
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.3.update.sqlpp
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+Use test;
+
+
+INSERT INTO UserTypes (
+    [
+        {
+            "userId": 1,
+            "email": "john.doe@example.com",
+            "username": "johndoe",
+            "isActive": true,
+            "occupation": "teacher",
+            "age": 23
+        },
+        {
+            "userId": 2,
+            "email": "jane.smith@example.com",
+            "username": "janesmith",
+            "isActive": false,
+            "occupation": "firefighter"
+
+
+        },
+        {
+            "userId": 3,
+            "email": "michael.brown@example.com",
+            "username": "michaelb",
+            "isActive": true,
+            "age": "five",
+            "occupation":4
+
+        },
+        {
+            "userId": 4,
+            "email": "emily.davis@example.com",
+            "username": "emilyd",
+            "isActive": true,
+            "age": 23.1
+
+        },
+        {
+            "userId": 5,
+            "email": "chris.jones@example.com",
+            "username": "chrisj",
+            "isActive": false,
+            "age": "old"
+
+        },
+        {
+            "userId": 6,
+            "email": "patricia.garcia@example.com",
+            "username": "patriciag",
+            "isActive": true,
+            "age": "45"
+
+        },
+        {
+            "userId": 7,
+            "email": "linda.martinez@example.com",
+            "username": "lindam",
+            "isActive": false
+        },
+        {
+            "userId": 8,
+            "email": "robert.lee@example.com",
+            "username": "robertl",
+            "isActive": true,
+            "age": 31
+
+        },
+        {
+            "userId": 9,
+            "email": "james.wilson@example.com",
+            "username": "jamesw",
+            "isActive": true,
+            "age": "65"
+
+        },
+        {
+            "userId": 10,
+            "email": "mary.moore@example.com",
+            "username": "marym",
+            "isActive": false,
+            "age": ""
+
+        },
+        {
+            "userId": 11,
+            "email": "jack.moore@example.com",
+            "username": "jk",
+            "isActive": true,
+            "age": "20",
+            "occupation": "firefighter"
+
+
+        }
+    ]
+);
+
+
+
+
+INSERT INTO UserTypes2 (
+    [
+        {
+            "userId": 1,
+            "email": "john.doe@example.com",
+            "username": "johndoe",
+            "isActive": true,
+            "occupation": "teacher",
+            "age": 23
+        },
+        {
+            "userId": 2,
+            "email": "jane.smith@example.com",
+            "username": "janesmith",
+            "isActive": false,
+            "occupation": "firefighter"
+
+
+        },
+        {
+            "userId": 3,
+            "email": "michael.brown@example.com",
+            "username": "michaelb",
+            "isActive": true,
+            "age": "five",
+            "occupation":4
+
+        },
+        {
+            "userId": 4,
+            "email": "emily.davis@example.com",
+            "username": "emilyd",
+            "isActive": true,
+            "age": 23.1
+
+        },
+        {
+            "userId": 5,
+            "email": "chris.jones@example.com",
+            "username": "chrisj",
+            "isActive": false,
+            "age": "old"
+
+        },
+        {
+            "userId": 6,
+            "email": "patricia.garcia@example.com",
+            "username": "patriciag",
+            "isActive": true,
+            "age": "45"
+
+        },
+        {
+            "userId": 7,
+            "email": "linda.martinez@example.com",
+            "username": "lindam",
+            "isActive": false
+        },
+        {
+            "userId": 8,
+            "email": "robert.lee@example.com",
+            "username": "robertl",
+            "isActive": true,
+            "age": 31
+
+        },
+        {
+            "userId": 9,
+            "email": "james.wilson@example.com",
+            "username": "jamesw",
+            "isActive": true,
+            "age": "65"
+
+        },
+        {
+            "userId": 10,
+            "email": "mary.moore@example.com",
+            "username": "marym",
+            "isActive": false,
+            "age": ""
+
+        },
+        {
+            "userId": 11,
+            "email": "jack.moore@example.com",
+            "username": "jk",
+            "isActive": true,
+            "age": "20",
+            "occupation": "firefighter"
+
+
+        }
+    ]
+);
+
+
+
+
+
+
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.query.sqlpp
new file mode 100644
index 0000000..40e8c99
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+Use test;
+
+select *
+from UserTypes u
+where u.age > 20
+order by userId;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.query.sqlpp
new file mode 100644
index 0000000..cf04d96
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+Use test;
+
+
+
+select *
+from UserTypes u
+where u.age > 20 and u.isActive = true
+order by userId;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.query.sqlpp
new file mode 100644
index 0000000..4d6d0a2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.query.sqlpp
@@ -0,0 +1,26 @@
+
+/*
+ * 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.
+ */
+
+Use test;
+
+select *
+from UserTypes u
+where u.age = "20" and u.occupation = "firefighter"
+order by userId;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.query.sqlpp
new file mode 100644
index 0000000..45fa384
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.query.sqlpp
@@ -0,0 +1,26 @@
+
+/*
+ * 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.
+ */
+
+Use test;
+
+select *
+from UserTypes2 u
+where u.age = "20" and u.occupation = "firefighter"
+order by userId;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.1.ddl.sqlpp
new file mode 100644
index 0000000..9c4994a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.1.ddl.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+drop  dataverse test if exists;
+create  dataverse test;
+use test;
+
+
+CREATE TYPE t1 AS {
+  id: int,
+  f2: int};
+
+CREATE DATASET ds(t1) primary key id;
+CREATE INDEX i2 on ds(f1,f2);
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.2.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.2.ddl.sqlpp
new file mode 100644
index 0000000..5f364b1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/index-selection/heterogeneous-index-type-and-heterogeneous-key/heterogeneous-index-type-and-heterogeneous-key.2.ddl.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+drop  dataverse test if exists;
+create  dataverse test;
+use test;
+
+
+CREATE TYPE t1 AS {
+  id: int,
+  f2: int};
+
+CREATE DATASET ds(t1) primary key id;
+CREATE INDEX i2 on ds(f1,f3: int);
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.adm
new file mode 100644
index 0000000..0290fae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.3.adm
@@ -0,0 +1 @@
+8800
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.adm
new file mode 100644
index 0000000..adffc32
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-join/secondary-heterogeneous-indexnl-params/secondary-heterogeneous-indexnl-params.4.adm
@@ -0,0 +1,2 @@
+1891
+8800
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.adm
new file mode 100644
index 0000000..5237c63
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.3.adm
@@ -0,0 +1 @@
+{ "t1_id": "o_untyped:01" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.adm
new file mode 100644
index 0000000..5237c63
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-Leftouterjoin/heterogeneous-index-Leftouterjoin.4.adm
@@ -0,0 +1 @@
+{ "t1_id": "o_untyped:01" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-select.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-select.4.adm
new file mode 100644
index 0000000..cf8c3b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-bulkLoad/heterogeneous-index-select.4.adm
@@ -0,0 +1,3 @@
+{ "u": { "userId": 1, "email": "john.doe@example.com", "username": "johndoe", "isActive": true, "occupation": "teacher", "age": 23 } }
+{ "u": { "userId": 4, "email": "emily.davis@example.com", "username": "emilyd", "isActive": true, "age": 23.1 } }
+{ "u": { "userId": 8, "email": "robert.lee@example.com", "username": "robertl", "isActive": true, "age": 31 } }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.adm
new file mode 100644
index 0000000..cf8c3b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.4.adm
@@ -0,0 +1,3 @@
+{ "u": { "userId": 1, "email": "john.doe@example.com", "username": "johndoe", "isActive": true, "occupation": "teacher", "age": 23 } }
+{ "u": { "userId": 4, "email": "emily.davis@example.com", "username": "emilyd", "isActive": true, "age": 23.1 } }
+{ "u": { "userId": 8, "email": "robert.lee@example.com", "username": "robertl", "isActive": true, "age": 31 } }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.adm
new file mode 100644
index 0000000..049b69e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.5.adm
@@ -0,0 +1,3 @@
+{ "u": { "userId": 1, "email": "john.doe@example.com", "username": "johndoe", "isActive": true, "occupation": "teacher", "age": 23 } }
+{ "u": { "userId": 4, "email": "emily.davis@example.com", "username": "emilyd", "isActive": true, "age": 23.1 } }
+{ "u": { "userId": 8, "email": "robert.lee@example.com", "username": "robertl", "isActive": true, "age": 31 } }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.adm
new file mode 100644
index 0000000..5f0696b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.6.adm
@@ -0,0 +1 @@
+{ "u": { "userId": 11, "email": "jack.moore@example.com", "username": "jk", "isActive": true, "age": "20", "occupation": "firefighter" } }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.adm
new file mode 100644
index 0000000..5f0696b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/index-selection/heterogeneous-index-select/heterogeneous-index-select.7.adm
@@ -0,0 +1 @@
+{ "u": { "userId": 11, "email": "jack.moore@example.com", "username": "jk", "isActive": true, "age": "20", "occupation": "firefighter" } }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
index 71ba960..bbeb517 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/sqlpp_queries.xml
@@ -6366,6 +6366,11 @@
         <expected-warn><![CDATA[ASX1132: Invalid specification for hint indexnl. ASX1001: Syntax error: In line 1 >>(8, idx_tenk2_1k_2k)<< Encountered <INTEGER_LITERAL> "8" at column 2.  (in line 35, at column 21)]]></expected-warn>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="index-join" check-warnings="true">
+      <compilation-unit name="secondary-heterogeneous-indexnl-params">
+        <output-dir compare="Text">secondary-heterogeneous-indexnl-params</output-dir>
+      </compilation-unit>
+    </test-case>
     <test-case FilePath="index-join">
       <compilation-unit name="rtree-spatial-intersect-point_01">
         <output-dir compare="Text">rtree-spatial-intersect-point_01</output-dir>
@@ -6691,6 +6696,28 @@
         <output-dir compare="Text">verify</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="index-selection">
+      <compilation-unit name="heterogeneous-index-select">
+        <output-dir compare="Text">heterogeneous-index-select</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="index-selection">
+      <compilation-unit name="heterogeneous-index-type-and-heterogeneous-key">
+        <output-dir compare="Text">none</output-dir>
+        <expected-error>ASX1079: Compilation error: Typed keys cannot be combined with untyped keys in the index definition. (in line 29, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: Typed keys cannot be combined with untyped keys in the index definition. (in line 29, at column 1)</expected-error>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="index-selection">
+      <compilation-unit name="heterogeneous-index-bulkLoad">
+        <output-dir compare="Text">heterogeneous-index-bulkLoad</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="index-selection">
+      <compilation-unit name="heterogeneous-index-Leftouterjoin">
+        <output-dir compare="Text">heterogeneous-index-Leftouterjoin</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="inverted-index-join">
     <test-case FilePath="inverted-index-join">
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslator.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslator.java
index deca260..e726db7 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslator.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslator.java
@@ -385,11 +385,18 @@
                         Triple<IAType, Boolean, Boolean> projectTypeResult =
                                 KeyFieldTypeUtil.getKeyProjectType((ARecordType) inputTypePrime, projectPath, null);
                         if (projectTypeResult == null) {
-                            throw new AsterixException(ErrorCode.METADATA_ERROR, projectPath.toString());
+                            if (indexType != IndexType.BTREE) {
+                                throw new AsterixException(ErrorCode.METADATA_ERROR, projectPath.toString());
+                            }
+                            projectTypePrime = BuiltinType.ANY;
+                            // We do not want the type to be union of Any, null and missing. Any will cover it all.
+                            projectTypeNullable = false;
+                            projectTypeMissable = false;
+                        } else {
+                            projectTypePrime = projectTypeResult.first;
+                            projectTypeNullable = inputTypeNullable || projectTypeResult.second;
+                            projectTypeMissable = inputTypeMissable || projectTypeResult.third;
                         }
-                        projectTypePrime = projectTypeResult.first;
-                        projectTypeNullable = inputTypeNullable || projectTypeResult.second;
-                        projectTypeMissable = inputTypeMissable || projectTypeResult.third;
                     }
                     IAType projectType = projectTypePrime == null ? null
                             : KeyFieldTypeUtil.makeUnknownableType(projectTypePrime, projectTypeNullable,
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryIndexOperationsHelper.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryIndexOperationsHelper.java
index 276f22b..cf4f8e0 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryIndexOperationsHelper.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryIndexOperationsHelper.java
@@ -49,6 +49,7 @@
 import org.apache.asterix.om.functions.IFunctionManager;
 import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
 import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.om.types.IAType;
 import org.apache.asterix.runtime.evaluators.functions.AndDescriptor;
@@ -328,6 +329,10 @@
             throws AlgebricksException {
         IFunctionManager funManger = metadataProvider.getFunctionManager();
         IDataFormat dataFormat = metadataProvider.getDataFormat();
+        //if the target type is "BuiltinType.ANY" there is no need to cast. If not we have to cast.
+        if (ATypeTag.ANY.equals(targetType.getTypeTag())) {
+            return fieldEvalFactory;
+        }
 
         // check IndexUtil.castDefaultNull(index), too, because we always want to cast even if the overriding type is
         // the same as the overridden type (this is for the case where overriding the type of closed field is allowed)
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/ATypeHierarchy.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/ATypeHierarchy.java
index 82de905..c94a5ae 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/ATypeHierarchy.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/ATypeHierarchy.java
@@ -177,6 +177,13 @@
         return null;
     }
 
+    /*
+    * Checks whether a type can be promoted (or converted) from one type (type1)
+    * to another (type2). This promotion typically follows certain rules defined
+    * within the class to allow safe type conversions,
+    * such as promoting a smaller numeric type (e.g., TINYINT) to a larger numeric type
+    * (e.g., INTEGER or DOUBLE), where no precision or range is lost.
+    */
     public static boolean canPromote(ATypeTag type1, ATypeTag type2) {
         return typePromotionHierachyMap.get(type1.ordinal() * ATypeTag.TYPE_COUNT + type2.ordinal());
     }