You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by al...@apache.org on 2021/08/12 18:46:23 UTC

[asterixdb] branch master updated: [ASTERIXDB-2791][IDX] Use OptionalBoolean for unknown key option

This is an automated email from the ASF dual-hosted git repository.

alsuliman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 738ec5d  [ASTERIXDB-2791][IDX] Use OptionalBoolean for unknown key option
738ec5d is described below

commit 738ec5db3224ac571bfca374e4090b1f9100908a
Author: Ali Alsuliman <al...@gmail.com>
AuthorDate: Thu Aug 12 00:38:15 2021 +0300

    [ASTERIXDB-2791][IDX] Use OptionalBoolean for unknown key option
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    Change-Id: I1abfa1ce1ac6d3848a684a78c1da992cc2fd6ca1
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12784
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Ali Alsuliman <al...@gmail.com>
    Reviewed-by: Murtadha Hubail <mh...@apache.org>
---
 .../IntroduceSecondaryIndexInsertDeleteRule.java   |   6 +-
 .../asterix/app/translator/QueryTranslator.java    |   3 +-
 .../dataflow/CheckpointInSecondaryIndexTest.java   |   3 +-
 .../test/dataflow/ConcurrentInsertTest.java        |   5 +-
 .../test/dataflow/LSMFlushRecoveryTest.java        |   3 +-
 .../test/dataflow/MultiPartitionLSMIndexTest.java  |   3 +-
 .../common/statement/CreateIndexStatement.java     |   9 +-
 .../lang/sqlpp/util/SqlppStatementUtil.java        |   7 +-
 .../apache/asterix/metadata/entities/Index.java    |  22 ++--
 .../IndexTupleTranslator.java                      |  19 ++--
 .../utils/SecondaryBTreeOperationsHelper.java      |   5 +-
 .../IndexTupleTranslatorTest.java                  |   3 +-
 .../org/apache/hyracks/util/OptionalBoolean.java   | 115 +++++++++++++++++++++
 13 files changed, 167 insertions(+), 36 deletions(-)

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 7567bbd..98c3edc 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
@@ -92,6 +92,7 @@ import org.apache.hyracks.algebricks.core.algebra.plan.ALogicalPlanImpl;
 import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
 import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
 import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.util.OptionalBoolean;
 
 /**
  * This rule matches the pattern:
@@ -951,8 +952,9 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
             if (index.isPrimaryKeyIndex()) {
                 return createAnyUnknownFilterExpression(secondaryKeyVars, typeEnv, forceFilter);
             } else {
-                Boolean excludeUnknownKey = ((Index.ValueIndexDetails) index.getIndexDetails()).isExcludeUnknownKey();
-                boolean excludeUnknown = excludeUnknownKey != null && excludeUnknownKey;
+                OptionalBoolean excludeUnknownKey =
+                        ((Index.ValueIndexDetails) index.getIndexDetails()).isExcludeUnknownKey();
+                boolean excludeUnknown = excludeUnknownKey.isPresent() && excludeUnknownKey.get();
                 return createAllUnknownFilterExpression(secondaryKeyVars, typeEnv, forceFilter, excludeUnknown);
             }
         } else {
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 173b6d4..3e0cf79 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
@@ -253,6 +253,7 @@ import org.apache.hyracks.control.common.controllers.CCConfig;
 import org.apache.hyracks.storage.am.common.dataflow.IndexDropOperatorDescriptor.DropOption;
 import org.apache.hyracks.storage.am.lsm.common.api.ILSMMergePolicyFactory;
 import org.apache.hyracks.storage.am.lsm.invertedindex.fulltext.TokenizerCategory;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -1494,7 +1495,7 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                     externalFilesSnapshot = ExternalIndexingOperations.getSnapshotFromExternalFileSystem(ds);
                     // Add an entry for the files index
                     Index.IndexCategory indexCategory = Index.IndexCategory.of(index.getIndexType());
-                    Boolean excludeUnknownKey = null;
+                    OptionalBoolean excludeUnknownKey = OptionalBoolean.empty();
                     if (indexCategory == Index.IndexCategory.VALUE) {
                         excludeUnknownKey = ((Index.ValueIndexDetails) index.getIndexDetails()).isExcludeUnknownKey();
                     }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/CheckpointInSecondaryIndexTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/CheckpointInSecondaryIndexTest.java
index caab0b8..3973dee 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/CheckpointInSecondaryIndexTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/CheckpointInSecondaryIndexTest.java
@@ -76,6 +76,7 @@ import org.apache.hyracks.storage.am.lsm.common.api.ILSMIndexFileManager;
 import org.apache.hyracks.storage.am.lsm.common.impls.AbstractLSMIndex;
 import org.apache.hyracks.storage.am.lsm.common.impls.LSMComponentId;
 import org.apache.hyracks.storage.am.lsm.common.impls.NoMergePolicyFactory;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -171,7 +172,7 @@ public class CheckpointInSecondaryIndexTest {
                         partitioningKeys, null, null, null, false, null, null),
                 null, DatasetType.INTERNAL, DATASET_ID, 0);
         secondaryIndex = new Index(dvName, DATASET_NAME, INDEX_NAME, INDEX_TYPE, INDEX_FIELD_NAMES,
-                INDEX_FIELD_INDICATORS, INDEX_FIELD_TYPES, false, false, false, 0, false);
+                INDEX_FIELD_INDICATORS, INDEX_FIELD_TYPES, false, false, false, 0, OptionalBoolean.of(false));
         taskCtx = null;
         primaryIndexDataflowHelper = null;
         secondaryIndexDataflowHelper = null;
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/ConcurrentInsertTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/ConcurrentInsertTest.java
index bea1791..e99e5d6 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/ConcurrentInsertTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/ConcurrentInsertTest.java
@@ -51,6 +51,7 @@ import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
 import org.apache.hyracks.storage.am.common.api.IIndexDataflowHelper;
 import org.apache.hyracks.storage.am.common.dataflow.IndexDataflowHelperFactory;
 import org.apache.hyracks.storage.am.lsm.btree.impl.TestLsmBtree;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -129,7 +130,7 @@ public class ConcurrentInsertTest {
                 StorageTestUtils.DATASET.getDatasetName(), "TestIndex", IndexType.BTREE,
                 Arrays.asList(Arrays.asList(StorageTestUtils.RECORD_TYPE.getFieldNames()[1])),
                 Arrays.asList(Index.RECORD_INDICATOR), Arrays.asList(BuiltinType.AINT64), false, false, false, 0,
-                false);
+                OptionalBoolean.of(false));
 
         SecondaryIndexInfo secondaryIndexInfo =
                 nc.createSecondaryIndex(primaryIndexInfo, secondaryIndexEntity, StorageTestUtils.STORAGE_MANAGER, 0);
@@ -143,7 +144,7 @@ public class ConcurrentInsertTest {
 
         Index primaryKeyIndexEntity = new Index(StorageTestUtils.DATASET.getDataverseName(),
                 StorageTestUtils.DATASET.getDatasetName(), "PrimaryKeyIndex", IndexType.BTREE, Arrays.asList(),
-                Arrays.asList(), Arrays.asList(), false, false, false, 0, null);
+                Arrays.asList(), Arrays.asList(), false, false, false, 0, OptionalBoolean.empty());
 
         SecondaryIndexInfo primaryKeyIndexInfo =
                 nc.createSecondaryIndex(primaryIndexInfo, primaryKeyIndexEntity, StorageTestUtils.STORAGE_MANAGER, 0);
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/LSMFlushRecoveryTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/LSMFlushRecoveryTest.java
index f408758..d24d081 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/LSMFlushRecoveryTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/LSMFlushRecoveryTest.java
@@ -70,6 +70,7 @@ import org.apache.hyracks.storage.am.lsm.common.api.ILSMIOOperation;
 import org.apache.hyracks.storage.am.lsm.common.api.ILSMIOOperationScheduler;
 import org.apache.hyracks.storage.am.lsm.common.api.ILSMMemoryComponent;
 import org.apache.hyracks.storage.am.lsm.common.impls.AsynchronousScheduler;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.junit.After;
@@ -187,7 +188,7 @@ public class LSMFlushRecoveryTest {
         dataset = StorageTestUtils.DATASET;
         secondaryIndexEntity = new Index(dataset.getDataverseName(), dataset.getDatasetName(), SECONDARY_INDEX_NAME,
                 SECONDARY_INDEX_TYPE, SECONDARY_INDEX_FIELD_NAMES, SECONDARY_INDEX_FIELD_INDICATORS,
-                SECONDARY_INDEX_FIELD_TYPES, false, false, false, 0, false);
+                SECONDARY_INDEX_FIELD_TYPES, false, false, false, 0, OptionalBoolean.of(false));
 
         primaryIndexInfos = new PrimaryIndexInfo[NUM_PARTITIONS];
         secondaryIndexInfo = new SecondaryIndexInfo[NUM_PARTITIONS];
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/MultiPartitionLSMIndexTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/MultiPartitionLSMIndexTest.java
index 42ddd95..6f9bed4 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/MultiPartitionLSMIndexTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/MultiPartitionLSMIndexTest.java
@@ -70,6 +70,7 @@ import org.apache.hyracks.storage.am.lsm.common.api.ILSMDiskComponent;
 import org.apache.hyracks.storage.am.lsm.common.api.ILSMMemoryComponent;
 import org.apache.hyracks.storage.am.lsm.common.api.IVirtualBufferCache;
 import org.apache.hyracks.storage.am.lsm.common.impls.NoMergePolicyFactory;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -161,7 +162,7 @@ public class MultiPartitionLSMIndexTest {
                         partitioningKeys, null, null, null, false, null, null),
                 null, DatasetType.INTERNAL, DATASET_ID, 0);
         secondaryIndex = new Index(dvName, DATASET_NAME, INDEX_NAME, INDEX_TYPE, INDEX_FIELD_NAMES,
-                INDEX_FIELD_INDICATORS, INDEX_FIELD_TYPES, false, false, false, 0, false);
+                INDEX_FIELD_INDICATORS, INDEX_FIELD_TYPES, false, false, false, 0, OptionalBoolean.of(false));
         taskCtxs = new IHyracksTaskContext[NUM_PARTITIONS];
         primaryIndexDataflowHelpers = new IIndexDataflowHelper[NUM_PARTITIONS];
         secondaryIndexDataflowHelpers = new IIndexDataflowHelper[NUM_PARTITIONS];
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateIndexStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateIndexStatement.java
index 2c02812..a9f3a0b 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateIndexStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateIndexStatement.java
@@ -34,6 +34,7 @@ import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.util.OptionalBoolean;
 
 public class CreateIndexStatement extends AbstractStatement {
 
@@ -48,7 +49,7 @@ public class CreateIndexStatement extends AbstractStatement {
     private final int gramLength;
     // Specific to FullText indexes.
     private final String fullTextConfigName;
-    private final Boolean excludeUnknownKey;
+    private final OptionalBoolean excludeUnknownKey;
 
     public CreateIndexStatement(DataverseName dataverseName, Identifier datasetName, Identifier indexName,
             IndexType indexType, List<IndexedElement> indexedElements, boolean enforced, int gramLength,
@@ -62,7 +63,7 @@ public class CreateIndexStatement extends AbstractStatement {
         this.gramLength = gramLength;
         this.ifNotExists = ifNotExists;
         this.fullTextConfigName = fullTextConfigName;
-        this.excludeUnknownKey = excludeUnknownKey;
+        this.excludeUnknownKey = OptionalBoolean.ofNullable(excludeUnknownKey);
     }
 
     public String getFullTextConfigName() {
@@ -94,10 +95,10 @@ public class CreateIndexStatement extends AbstractStatement {
     }
 
     public boolean hasExcludeUnknownKey() {
-        return excludeUnknownKey != null;
+        return excludeUnknownKey.isPresent();
     }
 
-    public Boolean isExcludeUnknownKey() {
+    public OptionalBoolean isExcludeUnknownKey() {
         return excludeUnknownKey;
     }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
index 7e1783b..437c299 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
@@ -21,6 +21,7 @@ package org.apache.asterix.lang.sqlpp.util;
 import java.util.List;
 
 import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.hyracks.util.OptionalBoolean;
 
 public class SqlppStatementUtil {
 
@@ -71,12 +72,12 @@ public class SqlppStatementUtil {
 
     @SuppressWarnings("squid:S1172") // unused variable
     public static StringBuilder getCreateIndexStatement(StringBuilder stringBuilder, DataverseName dataverseName,
-            String datasetName, String indexName, String fields, Boolean excludeUnknown, int version) {
+            String datasetName, String indexName, String fields, OptionalBoolean excludeUnknown, int version) {
         stringBuilder.append(CREATE_INDEX);
         enclose(stringBuilder, indexName).append(ON);
         StringBuilder appender = enclose(stringBuilder, dataverseName, datasetName).append(fields);
-        if (excludeUnknown != null) {
-            if (excludeUnknown) {
+        if (excludeUnknown.isPresent()) {
+            if (excludeUnknown.get()) {
                 appender.append(EXCLUDE_UNKNOWN_KEY);
             } else {
                 appender.append(INCLUDE_UNKNOWN_KEY);
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Index.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Index.java
index 685dc85..6ca0c10 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Index.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Index.java
@@ -38,6 +38,7 @@ import org.apache.asterix.om.types.IAType;
 import org.apache.asterix.om.utils.NonTaggedFormatUtil;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.util.OptionalBoolean;
 
 /**
  * Metadata describing an index.
@@ -82,7 +83,7 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
     public Index(DataverseName dataverseName, String datasetName, String indexName, IndexType indexType,
             List<List<String>> keyFieldNames, List<Integer> keyFieldSourceIndicators, List<IAType> keyFieldTypes,
             boolean overrideKeyFieldTypes, boolean isEnforced, boolean isPrimaryIndex, int pendingOp,
-            Boolean excludeUnknownKey) {
+            OptionalBoolean excludeUnknownKey) {
         this(dataverseName, datasetName,
                 indexName, indexType, createSimpleIndexDetails(indexType, keyFieldNames, keyFieldSourceIndicators,
                         keyFieldTypes, overrideKeyFieldTypes, excludeUnknownKey),
@@ -92,9 +93,9 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
     public static Index createPrimaryIndex(DataverseName dataverseName, String datasetName,
             List<List<String>> keyFieldNames, List<Integer> keyFieldSourceIndicators, List<IAType> keyFieldTypes,
             int pendingOp) {
-        return new Index(dataverseName, datasetName, datasetName, IndexType.BTREE,
-                createSimpleIndexDetails(IndexType.BTREE, keyFieldNames, keyFieldSourceIndicators, keyFieldTypes, false,
-                        null),
+        return new Index(dataverseName,
+                datasetName, datasetName, IndexType.BTREE, new ValueIndexDetails(keyFieldNames,
+                        keyFieldSourceIndicators, keyFieldTypes, false, OptionalBoolean.empty()),
                 false, true, pendingOp);
     }
 
@@ -335,12 +336,12 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
         private final Boolean excludeUnknownKey;
 
         public ValueIndexDetails(List<List<String>> keyFieldNames, List<Integer> keyFieldSourceIndicators,
-                List<IAType> keyFieldTypes, boolean overrideKeyFieldTypes, Boolean excludeUnknownKey) {
+                List<IAType> keyFieldTypes, boolean overrideKeyFieldTypes, OptionalBoolean excludeUnknownKey) {
             this.keyFieldNames = keyFieldNames;
             this.keyFieldSourceIndicators = keyFieldSourceIndicators;
             this.keyFieldTypes = keyFieldTypes;
             this.overrideKeyFieldTypes = overrideKeyFieldTypes;
-            this.excludeUnknownKey = excludeUnknownKey;
+            this.excludeUnknownKey = excludeUnknownKey.isEmpty() ? null : excludeUnknownKey.get();
         }
 
         @Override
@@ -360,8 +361,8 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
             return keyFieldTypes;
         }
 
-        public Boolean isExcludeUnknownKey() {
-            return excludeUnknownKey;
+        public OptionalBoolean isExcludeUnknownKey() {
+            return OptionalBoolean.ofNullable(excludeUnknownKey);
         }
 
         @Override
@@ -499,7 +500,7 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
     @Deprecated
     private static Index.IIndexDetails createSimpleIndexDetails(IndexType indexType, List<List<String>> keyFieldNames,
             List<Integer> keyFieldSourceIndicators, List<IAType> keyFieldTypes, boolean overrideKeyFieldTypes,
-            Boolean excludeUnknownKey) {
+            OptionalBoolean excludeUnknownKey) {
         if (indexType == null) {
             return null;
         }
@@ -508,6 +509,9 @@ public class Index implements IMetadataEntity<Index>, Comparable<Index> {
                 return new ValueIndexDetails(keyFieldNames, keyFieldSourceIndicators, keyFieldTypes,
                         overrideKeyFieldTypes, excludeUnknownKey);
             case TEXT:
+                if (excludeUnknownKey.isPresent()) {
+                    throw new IllegalArgumentException("excludeUnknownKey");
+                }
                 return new TextIndexDetails(keyFieldNames, keyFieldSourceIndicators, keyFieldTypes,
                         overrideKeyFieldTypes, -1, null);
             default:
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 3c322a4..9171937 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
@@ -65,6 +65,7 @@ import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+import org.apache.hyracks.util.OptionalBoolean;
 
 import com.google.common.base.Strings;
 
@@ -139,7 +140,7 @@ public class IndexTupleTranslator extends AbstractTupleTranslator<Index> {
         IndexType indexType = IndexType.valueOf(
                 ((AString) indexRecord.getValueByPos(MetadataRecordTypes.INDEX_ARECORD_INDEXSTRUCTURE_FIELD_INDEX))
                         .getStringValue());
-        Boolean isPrimaryIndex =
+        boolean isPrimaryIndex =
                 ((ABoolean) indexRecord.getValueByPos(MetadataRecordTypes.INDEX_ARECORD_ISPRIMARY_FIELD_INDEX))
                         .getBoolean();
 
@@ -383,15 +384,16 @@ public class IndexTupleTranslator extends AbstractTupleTranslator<Index> {
                 List<IAType> keyFieldTypes = searchKeyType.stream().map(l -> l.get(0)).collect(Collectors.toList());
 
                 // Read the exclude unknown key option if applicable for an index
-                Boolean excludeUnknownKey = null;
+                OptionalBoolean excludeUnknownKey = OptionalBoolean.empty();
                 boolean unknownKeyOptionAllowed =
                         indexType == IndexType.BTREE && !isPrimaryIndex && !keyFieldNames.isEmpty();
                 if (unknownKeyOptionAllowed) {
                     // default to always include unknowns for normal b-trees
-                    excludeUnknownKey = false;
+                    excludeUnknownKey = OptionalBoolean.FALSE();
                     int excludeUnknownKeyPos = indexRecord.getType().getFieldIndex(INDEX_EXCLUDE_UNKNOWN_FIELD_NAME);
                     if (excludeUnknownKeyPos >= 0) {
-                        excludeUnknownKey = ((ABoolean) indexRecord.getValueByPos(excludeUnknownKeyPos)).getBoolean();
+                        excludeUnknownKey = OptionalBoolean
+                                .of(((ABoolean) indexRecord.getValueByPos(excludeUnknownKeyPos)).getBoolean());
                     }
                 }
                 indexDetails = new Index.ValueIndexDetails(keyFieldNames, keyFieldSourceIndicator, keyFieldTypes,
@@ -768,15 +770,14 @@ public class IndexTupleTranslator extends AbstractTupleTranslator<Index> {
         boolean unknownKeyOptionAllowed =
                 index.getIndexType() == IndexType.BTREE && !index.isPrimaryIndex() && !index.isPrimaryKeyIndex();
         if (unknownKeyOptionAllowed) {
-            Boolean excludeUnknownKey = ((Index.ValueIndexDetails) index.getIndexDetails()).isExcludeUnknownKey();
-            if (excludeUnknownKey == null) {
-                excludeUnknownKey = false;
-            }
+            OptionalBoolean excludeUnknownKey =
+                    ((Index.ValueIndexDetails) index.getIndexDetails()).isExcludeUnknownKey();
+            ABoolean bVal = excludeUnknownKey.isEmpty() ? ABoolean.FALSE : ABoolean.valueOf(excludeUnknownKey.get());
             fieldValue.reset();
             nameValue.reset();
             aString.setValue(INDEX_EXCLUDE_UNKNOWN_FIELD_NAME);
             stringSerde.serialize(aString, nameValue.getDataOutput());
-            booleanSerde.serialize(ABoolean.valueOf(excludeUnknownKey), fieldValue.getDataOutput());
+            booleanSerde.serialize(bVal, fieldValue.getDataOutput());
             recordBuilder.addField(nameValue, fieldValue);
         }
     }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryBTreeOperationsHelper.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryBTreeOperationsHelper.java
index 5a1a06e..bcd18a7 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryBTreeOperationsHelper.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/SecondaryBTreeOperationsHelper.java
@@ -54,6 +54,7 @@ import org.apache.hyracks.dataflow.std.connectors.OneToOneConnectorDescriptor;
 import org.apache.hyracks.dataflow.std.sort.ExternalSortOperatorDescriptor;
 import org.apache.hyracks.storage.am.common.dataflow.IIndexDataflowHelperFactory;
 import org.apache.hyracks.storage.am.common.dataflow.IndexDataflowHelperFactory;
+import org.apache.hyracks.util.OptionalBoolean;
 
 public class SecondaryBTreeOperationsHelper extends SecondaryTreeIndexOperationsHelper {
 
@@ -313,8 +314,8 @@ public class SecondaryBTreeOperationsHelper extends SecondaryTreeIndexOperations
         if (index.isPrimaryKeyIndex()) {
             return true;
         } else {
-            Boolean excludeUnknownKey = details.isExcludeUnknownKey();
-            return excludeUnknownKey != null && excludeUnknownKey;
+            OptionalBoolean excludeUnknownKey = details.isExcludeUnknownKey();
+            return excludeUnknownKey.isPresent() && excludeUnknownKey.get();
         }
     }
 }
diff --git a/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslatorTest.java b/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslatorTest.java
index ad04bbe..edb360f 100644
--- a/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslatorTest.java
+++ b/asterixdb/asterix-metadata/src/test/java/org/apache/asterix/metadata/entitytupletranslators/IndexTupleTranslatorTest.java
@@ -44,6 +44,7 @@ import org.apache.asterix.om.types.IAType;
 import org.apache.asterix.runtime.compression.CompressionManager;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+import org.apache.hyracks.util.OptionalBoolean;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -73,7 +74,7 @@ public class IndexTupleTranslatorTest {
             Index index = new Index(dvTest, "d1", "i1", IndexType.BTREE,
                     Collections.singletonList(Collections.singletonList("row_id")),
                     indicator == null ? null : Collections.singletonList(indicator),
-                    Collections.singletonList(BuiltinType.AINT64), false, false, false, 0, false);
+                    Collections.singletonList(BuiltinType.AINT64), false, false, false, 0, OptionalBoolean.of(false));
 
             MetadataNode mockMetadataNode = mock(MetadataNode.class);
             when(mockMetadataNode.getDatatype(any(), any(DataverseName.class), anyString())).thenReturn(new Datatype(
diff --git a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/OptionalBoolean.java b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/OptionalBoolean.java
new file mode 100644
index 0000000..b64a315
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/OptionalBoolean.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.util;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Provides functionality similar to {@link java.util.Optional} for the primitive boolean type
+ */
+public class OptionalBoolean {
+    private static final OptionalBoolean EMPTY = new OptionalBoolean(false) {
+        @Override
+        public boolean isPresent() {
+            return false;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == this;
+        }
+
+        @Override
+        public int hashCode() {
+            return -1;
+        }
+
+        @Override
+        public boolean get() {
+            throw new NoSuchElementException();
+        }
+    };
+
+    private static final OptionalBoolean TRUE = new OptionalBoolean(true);
+    private static final OptionalBoolean FALSE = new OptionalBoolean(false);
+
+    private final boolean value;
+
+    public static OptionalBoolean of(boolean value) {
+        return value ? TRUE : FALSE;
+    }
+
+    public static OptionalBoolean ofNullable(Boolean value) {
+        return value == null ? EMPTY : value ? TRUE : FALSE;
+    }
+
+    public static OptionalBoolean empty() {
+        return EMPTY;
+    }
+
+    public static OptionalBoolean TRUE() {
+        return TRUE;
+    }
+
+    public static OptionalBoolean FALSE() {
+        return FALSE;
+    }
+
+    private OptionalBoolean(boolean value) {
+        this.value = value;
+    }
+
+    public boolean isPresent() {
+        return true;
+    }
+
+    public boolean isEmpty() {
+        return false;
+    }
+
+    public boolean get() {
+        return value;
+    }
+
+    public boolean getOrElse(boolean alternate) {
+        return isPresent() ? get() : alternate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (this == EMPTY || o == EMPTY)
+            return false;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        OptionalBoolean that = (OptionalBoolean) o;
+        return value == that.value;
+    }
+
+    @Override
+    public int hashCode() {
+        return value ? 1 : 0;
+    }
+}