Merge branch 'master' into westmann/scratch
diff --git a/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.1.ddl.aql b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.1.ddl.aql
new file mode 100644
index 0000000..b1910fb
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.1.ddl.aql
@@ -0,0 +1,29 @@
+/*
+ * Description    : Left-outer joins two datasets, DBLP and CSX, based on their authors and titles.
+ * Success        : Yes
+ */
+
+drop dataverse test if exists;
+create dataverse test;
+
+use dataverse test;
+
+create type DBLPType as closed {
+  id: int32, 
+  dblpid: string,
+  title: string,
+  authors: string,
+  misc: string
+}
+
+create type CSXType as closed {
+  id: int32, 
+  csxid: string,
+  title: string,
+  authors: string,
+  misc: string
+}
+
+create dataset DBLP(DBLPType) primary key id;
+create dataset CSX(CSXType) primary key id;
+
diff --git a/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.2.update.aql b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.2.update.aql
new file mode 100644
index 0000000..4e2123a
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.2.update.aql
@@ -0,0 +1,15 @@
+/*
+ * Description    : Left-outer joins two datasets, DBLP and CSX, based on their authors and titles.
+ * Success        : Yes
+ */
+
+use dataverse test;
+
+load dataset DBLP 
+using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
+(("path"="nc1://data/pub-small/dblp-small-id.txt"),("format"="delimited-text"),("delimiter"=":"));
+
+load dataset CSX
+using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
+(("path"="nc1://data/pub-small/csx-small-id.txt"),("format"="delimited-text"),("delimiter"=":"));
+
diff --git a/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.3.query.aql b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.3.query.aql
new file mode 100644
index 0000000..04816a7
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/leftouterjoin/query_issue658/query_issue658.3.query.aql
@@ -0,0 +1,16 @@
+/*
+ * Description    : Left-outer joins two datasets, DBLP and CSX, based on their authors and titles.
+ * Success        : Yes
+ */
+
+use dataverse test;
+
+for $a in dataset('DBLP')
+order by $a.id
+return {
+"aid": $a.id,
+"bids": for $b in dataset('CSX')
+where $a.authors = $b.authors and $a.title != $b.title
+order by $b.id
+return $b.id
+}
diff --git a/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.1.ddl.aql b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.1.ddl.aql
new file mode 100644
index 0000000..c74fd3c
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.1.ddl.aql
@@ -0,0 +1,19 @@
+drop dataverse TestVerse if exists;
+create dataverse TestVerse;
+use dataverse TestVerse;
+
+create type Int64TestType as open {
+        myint64: int64,
+        myoptint64: int64?,
+        myint32: int32,
+        myoptint32: int32?,
+        myint16: int16,
+        myoptint16: int16?,
+        mydouble: double,
+        myoptdouble: double?,
+        myfloat: float,
+        myoptfloat: float?
+};
+
+create dataset Int64Test(Int64TestType)
+   primary key myint64;
diff --git a/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.2.update.aql b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.2.update.aql
new file mode 100644
index 0000000..8505bff
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.2.update.aql
@@ -0,0 +1,13 @@
+use dataverse TestVerse;
+
+/* promotable type for optional field */
+insert into dataset Int64Test (
+       {"myint64": int64("13"), "myoptint64": 13, "myint32": int8("2"), "myoptint32": int16("3"), "myint16": int8("9"), "myoptint16": int8("10"), "mydouble": float("2.12"), "myoptdouble": int64("32"), "myfloat": int8("9"), "myoptfloat": int32("328")}
+);
+/* promotable type for non-optional field */
+insert into dataset Int64Test (
+       {"myint64": 12, "myoptint64": null, "myint32": int8("2"), "myoptint32": date(null), "myint16": int8("9"), "myoptint16": interval-starts(null, null), "mydouble": float("2.12"), "myoptdouble": time(null), "myfloat": int8("9"), "myoptfloat": datetime(null) }
+);
+insert into dataset Int64Test (
+       {"myint64": int16("11"), "myoptint64": int8("3"), "myint32": int8("2"), "myoptint32": int16("3"), "myint16": int8("9"), "myoptint16": int8("10"), "mydouble": int8("2"), "myoptdouble": int16("32"), "myfloat": int16("9"), "myoptfloat": datetime(null) }
+);
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.3.query.aql b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.3.query.aql
new file mode 100644
index 0000000..8948b49
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/types/type_promotion_0/type_promotion_0.3.query.aql
@@ -0,0 +1,4 @@
+use dataverse TestVerse;
+
+for $i in dataset Int64Test
+return $i
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/results/leftouterjoin/query_issue658/query_issue658.1.adm b/asterix-app/src/test/resources/runtimets/results/leftouterjoin/query_issue658/query_issue658.1.adm
new file mode 100644
index 0000000..62c427a
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/results/leftouterjoin/query_issue658/query_issue658.1.adm
@@ -0,0 +1,100 @@
+{ "aid": 1, "bids": [  ] }
+{ "aid": 2, "bids": [  ] }
+{ "aid": 3, "bids": [  ] }
+{ "aid": 4, "bids": [  ] }
+{ "aid": 5, "bids": [ 98 ] }
+{ "aid": 6, "bids": [  ] }
+{ "aid": 7, "bids": [  ] }
+{ "aid": 8, "bids": [  ] }
+{ "aid": 9, "bids": [  ] }
+{ "aid": 10, "bids": [  ] }
+{ "aid": 11, "bids": [  ] }
+{ "aid": 12, "bids": [  ] }
+{ "aid": 13, "bids": [  ] }
+{ "aid": 14, "bids": [  ] }
+{ "aid": 15, "bids": [  ] }
+{ "aid": 16, "bids": [  ] }
+{ "aid": 17, "bids": [  ] }
+{ "aid": 18, "bids": [  ] }
+{ "aid": 19, "bids": [  ] }
+{ "aid": 20, "bids": [  ] }
+{ "aid": 21, "bids": [  ] }
+{ "aid": 22, "bids": [  ] }
+{ "aid": 23, "bids": [  ] }
+{ "aid": 24, "bids": [  ] }
+{ "aid": 25, "bids": [  ] }
+{ "aid": 26, "bids": [  ] }
+{ "aid": 27, "bids": [  ] }
+{ "aid": 28, "bids": [  ] }
+{ "aid": 29, "bids": [  ] }
+{ "aid": 30, "bids": [  ] }
+{ "aid": 31, "bids": [  ] }
+{ "aid": 32, "bids": [  ] }
+{ "aid": 33, "bids": [  ] }
+{ "aid": 34, "bids": [ 57 ] }
+{ "aid": 35, "bids": [  ] }
+{ "aid": 36, "bids": [  ] }
+{ "aid": 37, "bids": [  ] }
+{ "aid": 38, "bids": [  ] }
+{ "aid": 39, "bids": [  ] }
+{ "aid": 40, "bids": [  ] }
+{ "aid": 41, "bids": [  ] }
+{ "aid": 42, "bids": [  ] }
+{ "aid": 43, "bids": [  ] }
+{ "aid": 44, "bids": [  ] }
+{ "aid": 45, "bids": [  ] }
+{ "aid": 46, "bids": [  ] }
+{ "aid": 47, "bids": [  ] }
+{ "aid": 48, "bids": [  ] }
+{ "aid": 49, "bids": [  ] }
+{ "aid": 50, "bids": [  ] }
+{ "aid": 51, "bids": [  ] }
+{ "aid": 52, "bids": [  ] }
+{ "aid": 53, "bids": [  ] }
+{ "aid": 54, "bids": [ 91 ] }
+{ "aid": 55, "bids": [  ] }
+{ "aid": 56, "bids": [  ] }
+{ "aid": 57, "bids": [  ] }
+{ "aid": 58, "bids": [  ] }
+{ "aid": 59, "bids": [  ] }
+{ "aid": 60, "bids": [  ] }
+{ "aid": 61, "bids": [  ] }
+{ "aid": 62, "bids": [  ] }
+{ "aid": 63, "bids": [  ] }
+{ "aid": 64, "bids": [  ] }
+{ "aid": 65, "bids": [  ] }
+{ "aid": 66, "bids": [  ] }
+{ "aid": 67, "bids": [  ] }
+{ "aid": 68, "bids": [ 57 ] }
+{ "aid": 69, "bids": [ 57 ] }
+{ "aid": 70, "bids": [  ] }
+{ "aid": 71, "bids": [  ] }
+{ "aid": 72, "bids": [  ] }
+{ "aid": 73, "bids": [  ] }
+{ "aid": 74, "bids": [  ] }
+{ "aid": 75, "bids": [  ] }
+{ "aid": 76, "bids": [  ] }
+{ "aid": 77, "bids": [  ] }
+{ "aid": 78, "bids": [  ] }
+{ "aid": 79, "bids": [  ] }
+{ "aid": 80, "bids": [  ] }
+{ "aid": 81, "bids": [  ] }
+{ "aid": 82, "bids": [  ] }
+{ "aid": 83, "bids": [  ] }
+{ "aid": 84, "bids": [  ] }
+{ "aid": 85, "bids": [  ] }
+{ "aid": 86, "bids": [  ] }
+{ "aid": 87, "bids": [  ] }
+{ "aid": 88, "bids": [  ] }
+{ "aid": 89, "bids": [  ] }
+{ "aid": 90, "bids": [  ] }
+{ "aid": 91, "bids": [  ] }
+{ "aid": 92, "bids": [  ] }
+{ "aid": 93, "bids": [  ] }
+{ "aid": 94, "bids": [  ] }
+{ "aid": 95, "bids": [  ] }
+{ "aid": 96, "bids": [  ] }
+{ "aid": 97, "bids": [  ] }
+{ "aid": 98, "bids": [  ] }
+{ "aid": 99, "bids": [  ] }
+{ "aid": 100, "bids": [  ] }
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/results/types/type_promotion_0/type_promotion_0.1.adm b/asterix-app/src/test/resources/runtimets/results/types/type_promotion_0/type_promotion_0.1.adm
new file mode 100644
index 0000000..3c356ed
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/results/types/type_promotion_0/type_promotion_0.1.adm
@@ -0,0 +1,3 @@
+{ "myint64": 11i64, "myoptint64": 3i64, "myint32": 2, "myoptint32": 3, "myint16": 9i16, "myoptint16": 10i16, "mydouble": 2.0d, "myoptdouble": 32.0d, "myfloat": 9.0f, "myoptfloat": null }
+{ "myint64": 12i64, "myoptint64": null, "myint32": 2, "myoptint32": null, "myint16": 9i16, "myoptint16": null, "mydouble": 2.119999885559082d, "myoptdouble": null, "myfloat": 9.0f, "myoptfloat": null }
+{ "myint64": 13i64, "myoptint64": 13i64, "myint32": 2, "myoptint32": 3, "myint16": 9i16, "myoptint16": 10i16, "mydouble": 2.119999885559082d, "myoptdouble": 32.0d, "myfloat": 9.0f, "myoptfloat": 328.0f }
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/testsuite.xml b/asterix-app/src/test/resources/runtimets/testsuite.xml
index d36fbd4..b80a5b2 100644
--- a/asterix-app/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-app/src/test/resources/runtimets/testsuite.xml
@@ -4533,6 +4533,11 @@
   </test-group>
   <test-group name="leftouterjoin">
     <test-case FilePath="leftouterjoin">
+      <compilation-unit name="query_issue658">
+        <output-dir compare="Text">query_issue658</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="leftouterjoin">
       <compilation-unit name="query_issue285">
         <output-dir compare="Text">query_issue285</output-dir>
       </compilation-unit>
@@ -4568,5 +4573,10 @@
         <output-dir compare="Text">record01</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="types">
+      <compilation-unit name="type_promotion_0">
+        <output-dir compare="Text">type_promotion_0</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
 </test-suite>
diff --git a/asterix-external-data/src/main/java/edu/uci/ics/asterix/external/dataset/adapter/FileSystemBasedAdapter.java b/asterix-external-data/src/main/java/edu/uci/ics/asterix/external/dataset/adapter/FileSystemBasedAdapter.java
index 33ee11f..753f7d1 100644
--- a/asterix-external-data/src/main/java/edu/uci/ics/asterix/external/dataset/adapter/FileSystemBasedAdapter.java
+++ b/asterix-external-data/src/main/java/edu/uci/ics/asterix/external/dataset/adapter/FileSystemBasedAdapter.java
@@ -16,7 +16,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -29,6 +28,7 @@
 import edu.uci.ics.asterix.om.types.ATypeTag;
 import edu.uci.ics.asterix.om.types.AUnionType;
 import edu.uci.ics.asterix.om.types.IAType;
+import edu.uci.ics.asterix.om.util.NonTaggedFormatUtil;
 import edu.uci.ics.asterix.runtime.operators.file.AdmSchemafullRecordParserFactory;
 import edu.uci.ics.asterix.runtime.operators.file.NtDelimitedDataTupleParserFactory;
 import edu.uci.ics.hyracks.algebricks.common.constraints.AlgebricksPartitionConstraint;
@@ -108,11 +108,11 @@
         for (int i = 0; i < n; i++) {
             ATypeTag tag = null;
             if (recordType.getFieldTypes()[i].getTypeTag() == ATypeTag.UNION) {
-                List<IAType> unionTypes = ((AUnionType) recordType.getFieldTypes()[i]).getUnionList();
-                if (unionTypes.size() != 2 && unionTypes.get(0).getTypeTag() != ATypeTag.NULL) {
+                if (!NonTaggedFormatUtil.isOptionalField(((AUnionType) recordType.getFieldTypes()[i]))) {
                     throw new NotImplementedException("Non-optional UNION type is not supported.");
                 }
-                tag = unionTypes.get(1).getTypeTag();
+                tag = ((AUnionType) recordType.getFieldTypes()[i]).getUnionList()
+                        .get(NonTaggedFormatUtil.OPTIONAL_TYPE_INDEX_IN_UNION_LIST).getTypeTag();
             } else {
                 tag = recordType.getFieldTypes()[i].getTypeTag();
             }
diff --git a/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ACastVisitor.java b/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ACastVisitor.java
index 4e939ce..a711eb9 100644
--- a/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ACastVisitor.java
+++ b/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ACastVisitor.java
@@ -15,6 +15,7 @@
 
 package edu.uci.ics.asterix.om.pointables.cast;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -28,8 +29,12 @@
 import edu.uci.ics.asterix.om.types.ARecordType;
 import edu.uci.ics.asterix.om.types.ATypeTag;
 import edu.uci.ics.asterix.om.types.AbstractCollectionType;
+import edu.uci.ics.asterix.om.types.EnumDeserializer;
 import edu.uci.ics.asterix.om.types.IAType;
+import edu.uci.ics.asterix.om.types.hierachy.ATypeHierarchy;
+import edu.uci.ics.asterix.om.types.hierachy.ITypePromoteComputer;
 import edu.uci.ics.hyracks.algebricks.common.utils.Triple;
+import edu.uci.ics.hyracks.data.std.util.ArrayBackedValueStorage;
 
 /**
  * This class is a IVisitablePointableVisitor implementation which recursively
@@ -86,10 +91,48 @@
     }
 
     @Override
-    public Void visit(AFlatValuePointable accessor, Triple<IVisitablePointable, IAType, Boolean> arg) {
+    public Void visit(AFlatValuePointable accessor, Triple<IVisitablePointable, IAType, Boolean> arg)
+            throws AsterixException {
+        if (arg.second == null) {
+            // for open type case
+            arg.first.set(accessor);
+            return null;
+        }
         // set the pointer for result
-        arg.first.set(accessor);
+        ATypeTag reqTypeTag = ((IAType) (arg.second)).getTypeTag();
+        ATypeTag inputTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(accessor.getByteArray()[accessor
+                .getStartOffset()]);
+        if (!needPromote(inputTypeTag, reqTypeTag)) {
+            arg.first.set(accessor);
+        } else {
+            ArrayBackedValueStorage castBuffer = new ArrayBackedValueStorage();
+            ITypePromoteComputer promoteComputer = ATypeHierarchy.getTypePromoteComputer(inputTypeTag, reqTypeTag);
+            if (promoteComputer != null) {
+
+                try {
+                    // do the promotion; note that the type tag field should be skipped
+                    promoteComputer.promote(accessor.getByteArray(), accessor.getStartOffset() + 1,
+                            accessor.getLength() - 1, castBuffer);
+                    arg.first.set(castBuffer);
+                } catch (IOException e) {
+                    throw new AsterixException(e);
+                }
+            } else {
+                throw new AsterixException("Type mismatch: cannot cast type " + inputTypeTag + " to " + reqTypeTag);
+            }
+        }
+
         return null;
     }
 
+    private boolean needPromote(ATypeTag tag0, ATypeTag tag1) {
+        if (tag0 == tag1) {
+            return false;
+        }
+        if (tag0 == ATypeTag.NULL) {
+            return false;
+        }
+        return true;
+    }
+
 }
diff --git a/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ARecordCaster.java b/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ARecordCaster.java
index 507b845..e7e0ef2 100644
--- a/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ARecordCaster.java
+++ b/asterix-om/src/main/java/edu/uci/ics/asterix/om/pointables/cast/ARecordCaster.java
@@ -36,6 +36,7 @@
 import edu.uci.ics.asterix.om.types.AUnionType;
 import edu.uci.ics.asterix.om.types.EnumDeserializer;
 import edu.uci.ics.asterix.om.types.IAType;
+import edu.uci.ics.asterix.om.types.hierachy.ATypeHierarchy;
 import edu.uci.ics.asterix.om.util.NonTaggedFormatUtil;
 import edu.uci.ics.asterix.om.util.ResettableByteArrayOutputStream;
 import edu.uci.ics.hyracks.algebricks.common.utils.Pair;
@@ -205,6 +206,17 @@
                         optionalFields[reqFnPos] && fieldTypeTag.equals(nullTypeTag))) {
                     fieldPermutation[reqFnPos] = fnPos;
                     openFields[fnPos] = false;
+                } else {
+                    // if mismatch, check whether input type can be promoted to the required type
+                    ATypeTag inputTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(fieldTypeTag
+                            .getByteArray()[fieldTypeTag.getStartOffset()]);
+                    ATypeTag requiredTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(reqFieldTypeTag
+                            .getByteArray()[reqFieldTypeTag.getStartOffset()]);
+
+                    if (ATypeHierarchy.canPromote(inputTypeTag, requiredTypeTag)) {
+                        fieldPermutation[reqFnPos] = fnPos;
+                        openFields[fnPos] = false;
+                    }
                 }
                 fnStart++;
                 reqFnStart++;
diff --git a/asterix-om/src/main/java/edu/uci/ics/asterix/om/types/ARecordType.java b/asterix-om/src/main/java/edu/uci/ics/asterix/om/types/ARecordType.java
index 0319c7c..28a41d1 100644
--- a/asterix-om/src/main/java/edu/uci/ics/asterix/om/types/ARecordType.java
+++ b/asterix-om/src/main/java/edu/uci/ics/asterix/om/types/ARecordType.java
@@ -66,6 +66,7 @@
      * @throws AsterixException
      *             if there are duplicate field names or if there is an error serializing the field names
      */
+    @SuppressWarnings("resource")
     public ARecordType(String typeName, String[] fieldNames, IAType[] fieldTypes, boolean isOpen)
             throws AsterixException {
         super(typeName);
@@ -96,6 +97,11 @@
             hashCodeIndexPairs[i] = hashCodeIndexPairs[i] << 32;
             hashCodeIndexPairs[i] = hashCodeIndexPairs[i] | i;
         }
+        try {
+            dos.close();
+        } catch (IOException e) {
+            throw new AsterixException(e);
+        }
         serializedFieldNames = baaos.getByteArray();
 
         Arrays.sort(hashCodeIndexPairs);
@@ -336,7 +342,8 @@
                             break;
                         default:
                             throw new AlgebricksException("The field \"" + fieldName + "\" which is of type "
-                                    + fieldType.getTypeTag() + " cannot be indexed using the Length Partitioned N-Gram index.");
+                                    + fieldType.getTypeTag()
+                                    + " cannot be indexed using the Length Partitioned N-Gram index.");
                     }
                     break;
                 case LENGTH_PARTITIONED_WORD_INVIX:
@@ -348,7 +355,8 @@
                             break;
                         default:
                             throw new AlgebricksException("The field \"" + fieldName + "\" which is of type "
-                                    + fieldType.getTypeTag() + " cannot be indexed using the Length Partitioned Keyword index.");
+                                    + fieldType.getTypeTag()
+                                    + " cannot be indexed using the Length Partitioned Keyword index.");
                     }
                     break;
                 case SINGLE_PARTITION_NGRAM_INVIX:
diff --git a/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java b/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
index 5a639dc..23f7aab 100644
--- a/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
+++ b/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
@@ -29,6 +29,8 @@
 import edu.uci.ics.asterix.om.base.ANull;
 import edu.uci.ics.asterix.om.types.ARecordType;
 import edu.uci.ics.asterix.om.types.ATypeTag;
+import edu.uci.ics.asterix.om.types.AUnionType;
+import edu.uci.ics.asterix.om.util.NonTaggedFormatUtil;
 import edu.uci.ics.hyracks.api.exceptions.HyracksDataException;
 import edu.uci.ics.hyracks.data.std.util.ArrayBackedValueStorage;
 import edu.uci.ics.hyracks.dataflow.common.data.parsers.IValueParser;
@@ -115,8 +117,9 @@
                         && recordType.getFieldTypes()[i].getTypeTag() != ATypeTag.NULL) {
                     // if the field is empty and the type is optional, insert NULL
                     // note that string type can also process empty field as an empty string
-                    if (recordType.getFieldTypes()[i].getTypeTag() != ATypeTag.UNION) {
-                        throw new AsterixException("Field " + i + " cannot be NULL. ");
+                    if (recordType.getFieldTypes()[i].getTypeTag() != ATypeTag.UNION
+                            || !NonTaggedFormatUtil.isOptionalField((AUnionType) recordType.getFieldTypes()[i])) {
+                        throw new AsterixException("Field " + i + " is not an optional type so it cannot accept null value. ");
                     }
                     fieldValueBufferOutput.writeByte(ATypeTag.NULL.serialize());
                     ANullSerializerDeserializer.INSTANCE.serialize(ANull.NULL, out);