Add internal function for type casting in 'lax' mode

- Add internal function 'cast-lax' which performs type
  demotion in 'lax' mode and returns 'missing' if cast fails
- Fixed incorrect boundary check when converting from double to float

Change-Id: Id929f1e66853f0603d033cf0f824349296e83521
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1806
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
BAD: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Yingyi Bu <buyingyi@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/FunctionCollection.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/FunctionCollection.java
index 7431218..53efe2f 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/FunctionCollection.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/FunctionCollection.java
@@ -161,6 +161,7 @@
 import org.apache.asterix.runtime.evaluators.functions.IfMissingDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.IfMissingOrNullDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.IfNullDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.CastTypeLaxDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.InjectFailureDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.IsArrayDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.IsBooleanDescriptor;
@@ -697,6 +698,7 @@
 
         // Cast function
         functionsToInjectUnkownHandling.add(CastTypeDescriptor.FACTORY);
+        functionsToInjectUnkownHandling.add(CastTypeLaxDescriptor.FACTORY);
 
         // Record function
         functionsToInjectUnkownHandling.add(RecordPairsDescriptor.FACTORY);
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/runtime/CastTypeLaxTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/runtime/CastTypeLaxTest.java
new file mode 100644
index 0000000..e3117b3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/runtime/CastTypeLaxTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.asterix.runtime;
+
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.asterix.dataflow.data.nontagged.serde.AObjectSerializerDeserializer;
+import org.apache.asterix.om.base.ABoolean;
+import org.apache.asterix.om.base.ADouble;
+import org.apache.asterix.om.base.AFloat;
+import org.apache.asterix.om.base.AInt16;
+import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AInt64;
+import org.apache.asterix.om.base.AInt8;
+import org.apache.asterix.om.base.AMissing;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
+import org.apache.asterix.runtime.evaluators.functions.CastTypeLaxDescriptor;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.algebricks.runtime.evaluators.ConstantEvalFactory;
+import org.apache.hyracks.api.context.IHyracksTaskContext;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(value = Parameterized.class)
+public class CastTypeLaxTest {
+
+    private IAType inType;
+
+    private IAObject inValue;
+
+    private IAObject targetType;
+
+    private IAObject targetValue;
+
+    public CastTypeLaxTest(IAType inType, IAObject inValue, IAObject targetType, IAObject targetValue) {
+        this.inType = inType;
+        this.inValue = inValue;
+        this.targetType = targetType;
+        this.targetValue = targetValue;
+    }
+
+    @Test
+    public void testCastLax() throws Exception {
+        IFunctionDescriptor funcDesc = CastTypeLaxDescriptor.FACTORY.createFunctionDescriptor();
+
+        funcDesc.setImmutableStates(targetType, inType);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        AObjectSerializerDeserializer serDe = AObjectSerializerDeserializer.INSTANCE;
+        serDe.serialize(inValue, new DataOutputStream(baos));
+
+        ConstantEvalFactory argEvalFactory = new ConstantEvalFactory(baos.toByteArray());
+        IScalarEvaluatorFactory evalFactory =
+                funcDesc.createEvaluatorFactory(new IScalarEvaluatorFactory[] { argEvalFactory });
+        IHyracksTaskContext ctx = mock(IHyracksTaskContext.class);
+        IScalarEvaluator evaluator = evalFactory.createScalarEvaluator(ctx);
+        VoidPointable resultPointable = new VoidPointable();
+        evaluator.evaluate(null, resultPointable);
+
+        ByteArrayInputStream bais = new ByteArrayInputStream(resultPointable.getByteArray(),
+                resultPointable.getStartOffset(), resultPointable.getLength());
+        IAObject resultValue = serDe.deserialize(new DataInputStream(bais));
+
+        Assert.assertTrue(String.format("Expected: %s, actual: %s", targetValue, resultValue),
+                targetValue.deepEqual(resultValue));
+    }
+
+    @Parameterized.Parameters(name = "CastTypeLaxTest {index}: {0}({1})->{2}({3})")
+    public static Collection<Object[]> tests() throws Exception {
+        List<Object[]> tests = new ArrayList<>();
+
+        IAType[] numericTypes = new IAType[] { BuiltinType.AINT8, BuiltinType.AINT16, BuiltinType.AINT32,
+                BuiltinType.AINT64, BuiltinType.AFLOAT, BuiltinType.ADOUBLE };
+
+        // numeric -> numeric
+        for (IAType inType : numericTypes) {
+            for (IAType targetType : numericTypes) {
+                if (!inType.equals(targetType)) {
+                    generateTests(tests, inType, targetType);
+                }
+            }
+        }
+
+        // type mismatch -> missing
+        addTest(tests, BuiltinType.ABOOLEAN, ABoolean.TRUE, BuiltinType.ADATE, AMissing.MISSING);
+
+        return tests;
+    }
+
+    private static void generateTests(List<Object[]> outTests, IAType inType, IAType targetType) {
+        int value = inType.getTypeTag().ordinal();
+        addTest(outTests, inType, createValue(inType, value), targetType, createValue(targetType, value));
+
+        if (ATypeHierarchy.canDemote(inType.getTypeTag(), targetType.getTypeTag())) {
+            IAObject inMax = createValue(inType, getMax(inType));
+            IAObject inMin = createValue(inType, getMin(inType));
+            IAObject targetMax = createValue(targetType, getMax(targetType));
+            IAObject targetMin = createValue(targetType, getMin(targetType));
+
+            addTest(outTests, inType, inMax, targetType, targetMax);
+            addTest(outTests, inType, inMin, targetType, targetMin);
+
+            if (!isInteger(inType) && isInteger(targetType)) {
+                addTest(outTests, inType, createValue(inType, getPositiveInfinity(inType)), targetType, targetMax);
+                addTest(outTests, inType, createValue(inType, getNegativeInfinity(inType)), targetType, targetMin);
+                addTest(outTests, inType, createValue(inType, getNaN(inType)), targetType, createValue(targetType, 0));
+            }
+        }
+    }
+
+    private static void addTest(List<Object[]> outTests, IAType inType, IAObject inValue, IAObject targetType,
+            IAObject targetValue) {
+        outTests.add(new Object[] { inType, inValue, targetType, targetValue });
+    }
+
+    private static IAObject createValue(IAType type, Number value) {
+        switch (type.getTypeTag()) {
+            case TINYINT:
+                return new AInt8(value.byteValue());
+            case SMALLINT:
+                return new AInt16(value.shortValue());
+            case INTEGER:
+                return new AInt32(value.intValue());
+            case BIGINT:
+                return new AInt64(value.longValue());
+            case FLOAT:
+                return new AFloat(value.floatValue());
+            case DOUBLE:
+                return new ADouble(value.doubleValue());
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static Number getMax(IAType type) {
+        switch (type.getTypeTag()) {
+            case TINYINT:
+                return Byte.MAX_VALUE;
+            case SMALLINT:
+                return Short.MAX_VALUE;
+            case INTEGER:
+                return Integer.MAX_VALUE;
+            case BIGINT:
+                return Long.MAX_VALUE;
+            case FLOAT:
+                return Float.MAX_VALUE;
+            case DOUBLE:
+                return Double.MAX_VALUE;
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static Number getMin(IAType type) {
+        switch (type.getTypeTag()) {
+            case TINYINT:
+                return Byte.MIN_VALUE;
+            case SMALLINT:
+                return Short.MIN_VALUE;
+            case INTEGER:
+                return Integer.MIN_VALUE;
+            case BIGINT:
+                return Long.MIN_VALUE;
+            case FLOAT:
+                return -Float.MAX_VALUE;
+            case DOUBLE:
+                return -Double.MAX_VALUE;
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static Number getPositiveInfinity(IAType type) {
+        switch (type.getTypeTag()) {
+            case FLOAT:
+                return Float.POSITIVE_INFINITY;
+            case DOUBLE:
+                return Double.POSITIVE_INFINITY;
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static Number getNegativeInfinity(IAType type) {
+        switch (type.getTypeTag()) {
+            case FLOAT:
+                return Float.NEGATIVE_INFINITY;
+            case DOUBLE:
+                return Double.NEGATIVE_INFINITY;
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static Number getNaN(IAType type) {
+        switch (type.getTypeTag()) {
+            case FLOAT:
+                return Float.NaN;
+            case DOUBLE:
+                return Double.NaN;
+            default:
+                throw new IllegalStateException(type.toString());
+        }
+    }
+
+    private static boolean isInteger(IAType type) {
+        switch (type.getTypeTag()) {
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+            case BIGINT:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index 6fc3ed9..f4fb36a 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -57,6 +57,7 @@
 import org.apache.asterix.om.typecomputer.impl.BooleanOnlyTypeComputer;
 import org.apache.asterix.om.typecomputer.impl.BooleanOrMissingTypeComputer;
 import org.apache.asterix.om.typecomputer.impl.CastTypeComputer;
+import org.apache.asterix.om.typecomputer.impl.CastTypeLaxComputer;
 import org.apache.asterix.om.typecomputer.impl.ClosedRecordConstructorResultType;
 import org.apache.asterix.om.typecomputer.impl.CollectionMemberResultType;
 import org.apache.asterix.om.typecomputer.impl.CollectionToSequenceTypeComputer;
@@ -663,6 +664,8 @@
             "flow-object", 1);
     public static final FunctionIdentifier CAST_TYPE = new FunctionIdentifier(FunctionConstants.ASTERIX_NS,
             "cast", 1);
+    public static final FunctionIdentifier CAST_TYPE_LAX = new FunctionIdentifier(FunctionConstants.ASTERIX_NS,
+            "cast-lax", 1);
 
     public static final FunctionIdentifier CREATE_UUID = new FunctionIdentifier(FunctionConstants.ASTERIX_NS,
             "create-uuid", 0);
@@ -1077,6 +1080,7 @@
         addFunction(SLEEP, SleepTypeComputer.INSTANCE, false);
         addPrivateFunction(INJECT_FAILURE, InjectFailureTypeComputer.INSTANCE, true);
         addPrivateFunction(CAST_TYPE, CastTypeComputer.INSTANCE, true);
+        addPrivateFunction(CAST_TYPE_LAX, CastTypeLaxComputer.INSTANCE, true);
 
         addFunction(TID, AInt64TypeComputer.INSTANCE, true);
         addFunction(TIME_CONSTRUCTOR, ATimeTypeComputer.INSTANCE, true);
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/pointables/cast/ACastVisitor.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/pointables/cast/ACastVisitor.java
index b161365..72e3072 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/pointables/cast/ACastVisitor.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/pointables/cast/ACastVisitor.java
@@ -56,6 +56,16 @@
     private final Map<IVisitablePointable, AListCaster> laccessorToCaster = new HashMap<>();
     private final ArrayBackedValueStorage castBuffer = new ArrayBackedValueStorage();
 
+    private final boolean strictDemote;
+
+    public ACastVisitor() {
+        this(true);
+    }
+
+    public ACastVisitor(boolean strictDemote) {
+        this.strictDemote = strictDemote;
+    }
+
     @Override
     public Void visit(AListVisitablePointable accessor, Triple<IVisitablePointable, IAType, Boolean> arg)
             throws HyracksDataException {
@@ -110,7 +120,7 @@
             try {
                 castBuffer.reset();
                 ATypeHierarchy.convertNumericTypeByteArray(accessor.getByteArray(), accessor.getStartOffset(),
-                        accessor.getLength(), reqTypeTag, castBuffer.getDataOutput(), true);
+                        accessor.getLength(), reqTypeTag, castBuffer.getDataOutput(), strictDemote);
                 arg.first.set(castBuffer);
             } catch (IOException e1) {
                 throw new HyracksDataException(
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/CastTypeLaxComputer.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/CastTypeLaxComputer.java
new file mode 100644
index 0000000..7aab27f
--- /dev/null
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/typecomputer/impl/CastTypeLaxComputer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.asterix.om.typecomputer.impl;
+
+import org.apache.asterix.om.typecomputer.base.AbstractResultTypeComputer;
+import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
+import org.apache.asterix.om.typecomputer.base.TypeCastUtils;
+import org.apache.asterix.om.types.AUnionType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+
+public class CastTypeLaxComputer extends AbstractResultTypeComputer {
+    public static final IResultTypeComputer INSTANCE = new CastTypeLaxComputer();
+
+    @Override
+    protected IAType getResultType(ILogicalExpression expr, IAType... strippedInputTypes) throws AlgebricksException {
+        return AUnionType.createMissableType(TypeCastUtils.getRequiredType((AbstractFunctionCallExpression) expr));
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/DoubleToFloatTypeConvertComputer.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/DoubleToFloatTypeConvertComputer.java
index 1792ce5..70aa283 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/DoubleToFloatTypeConvertComputer.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/types/hierachy/DoubleToFloatTypeConvertComputer.java
@@ -69,11 +69,11 @@
             } else {
                 return Float.MAX_VALUE;
             }
-        } else if (sourceValue < Float.MIN_VALUE) {
+        } else if (sourceValue < -Float.MAX_VALUE) {
             if (strict) {
                 raiseBoundaryCheckException(sourceValue);
             } else {
-                return Float.MIN_VALUE;
+                return -Float.MAX_VALUE;
             }
         }
 
@@ -82,6 +82,6 @@
 
     private void raiseBoundaryCheckException(double sourceValue) throws HyracksDataException {
         throw new RuntimeDataException(ErrorCode.TYPE_CONVERT_OUT_OF_BOUND, sourceValue, ATypeTag.FLOAT,
-                Float.MAX_VALUE, Float.MIN_VALUE);
+                Float.MAX_VALUE, -Float.MAX_VALUE);
     }
 }
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeDescriptor.java
index 22f1e5b..7f5c58d 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeDescriptor.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeDescriptor.java
@@ -18,28 +18,17 @@
  */
 package org.apache.asterix.runtime.evaluators.functions;
 
-import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.functions.IFunctionDescriptor;
 import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
-import org.apache.asterix.om.pointables.PointableAllocator;
-import org.apache.asterix.om.pointables.base.DefaultOpenFieldType;
-import org.apache.asterix.om.pointables.base.IVisitablePointable;
-import org.apache.asterix.om.pointables.cast.ACastVisitor;
 import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
-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.base.AbstractScalarFunctionDynamicDescriptor;
-import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
 import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
 import org.apache.hyracks.api.context.IHyracksTaskContext;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.api.IPointable;
-import org.apache.hyracks.data.std.primitive.VoidPointable;
-import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
 
 /**
  * This runtime function casts an input ADM instance of a certain type into the form
@@ -96,64 +85,3 @@
     }
 }
 
-class CastTypeEvaluator implements IScalarEvaluator {
-
-    private final IScalarEvaluator argEvaluator;
-    private final IPointable argPointable = new VoidPointable();
-
-    private final PointableAllocator allocator = new PointableAllocator();
-    private final IVisitablePointable inputPointable;
-    private final IVisitablePointable resultPointable;
-
-    private final ACastVisitor castVisitor = new ACastVisitor();
-    private final Triple<IVisitablePointable, IAType, Boolean> arg;
-
-    public CastTypeEvaluator(IAType reqType, IAType inputType, IScalarEvaluator argEvaluator)
-            throws HyracksDataException {
-        try {
-            this.argEvaluator = argEvaluator;
-            this.inputPointable = allocatePointable(inputType, reqType);
-            this.resultPointable = allocatePointable(reqType, inputType);
-            this.arg = new Triple<>(resultPointable, reqType, Boolean.FALSE);
-        } catch (AsterixException e) {
-            throw new HyracksDataException(e);
-        }
-    }
-
-    @Override
-    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
-        try {
-            argEvaluator.evaluate(tuple, argPointable);
-            inputPointable.set(argPointable);
-            inputPointable.accept(castVisitor, arg);
-            result.set(resultPointable);
-        } catch (Exception ioe) {
-            throw new HyracksDataException(ioe);
-        }
-    }
-
-    // Allocates the result pointable.
-    private final IVisitablePointable allocatePointable(IAType typeForPointable, IAType typeForOtherSide)
-            throws AsterixException {
-        if (!typeForPointable.equals(BuiltinType.ANY)) {
-            return allocator.allocateFieldValue(typeForPointable);
-        }
-        return allocatePointableForAny(typeForOtherSide);
-    }
-
-    // Allocates an input or result pointable if the input or required type is ANY.
-    private IVisitablePointable allocatePointableForAny(IAType type) {
-        ATypeTag tag = type.getTypeTag();
-        switch (tag) {
-            case OBJECT:
-                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_RECORD_TYPE);
-            case ARRAY:
-                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_AORDERED_LIST_TYPE);
-            case MULTISET:
-                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_AUNORDERED_LIST_TYPE);
-            default:
-                return allocator.allocateFieldValue(null);
-        }
-    }
-
-}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeEvaluator.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeEvaluator.java
new file mode 100644
index 0000000..524b2ed
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeEvaluator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.asterix.runtime.evaluators.functions;
+
+import org.apache.asterix.om.pointables.PointableAllocator;
+import org.apache.asterix.om.pointables.base.DefaultOpenFieldType;
+import org.apache.asterix.om.pointables.base.IVisitablePointable;
+import org.apache.asterix.om.pointables.cast.ACastVisitor;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.common.utils.Triple;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+class CastTypeEvaluator implements IScalarEvaluator {
+
+    private final IScalarEvaluator argEvaluator;
+    private final IPointable argPointable = new VoidPointable();
+
+    private final PointableAllocator allocator = new PointableAllocator();
+    private final IVisitablePointable inputPointable;
+    private final IVisitablePointable resultPointable;
+
+    private final ACastVisitor castVisitor;
+    private final Triple<IVisitablePointable, IAType, Boolean> arg;
+
+    public CastTypeEvaluator(IAType reqType, IAType inputType, IScalarEvaluator argEvaluator) {
+        this.argEvaluator = argEvaluator;
+        this.inputPointable = allocatePointable(inputType, reqType);
+        this.resultPointable = allocatePointable(reqType, inputType);
+        this.arg = new Triple<>(resultPointable, reqType, Boolean.FALSE);
+        this.castVisitor = createCastVisitor();
+    }
+
+    protected ACastVisitor createCastVisitor() {
+        return new ACastVisitor();
+    }
+
+    @Override
+    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+        argEvaluator.evaluate(tuple, argPointable);
+        inputPointable.set(argPointable);
+        cast(result);
+    }
+
+    protected void cast(IPointable result) throws HyracksDataException {
+        inputPointable.accept(castVisitor, arg);
+        result.set(resultPointable);
+    }
+
+    // Allocates the result pointable.
+    private final IVisitablePointable allocatePointable(IAType typeForPointable, IAType typeForOtherSide) {
+        if (!typeForPointable.equals(BuiltinType.ANY)) {
+            return allocator.allocateFieldValue(typeForPointable);
+        }
+        return allocatePointableForAny(typeForOtherSide);
+    }
+
+    // Allocates an input or result pointable if the input or required type is ANY.
+    private IVisitablePointable allocatePointableForAny(IAType type) {
+        ATypeTag tag = type.getTypeTag();
+        switch (tag) {
+            case OBJECT:
+                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_RECORD_TYPE);
+            case ARRAY:
+                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_AORDERED_LIST_TYPE);
+            case MULTISET:
+                return allocator.allocateFieldValue(DefaultOpenFieldType.NESTED_OPEN_AUNORDERED_LIST_TYPE);
+            default:
+                return allocator.allocateFieldValue(null);
+        }
+    }
+}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxDescriptor.java
new file mode 100644
index 0000000..eea1b6e
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxDescriptor.java
@@ -0,0 +1,94 @@
+/*
+ * 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.asterix.runtime.evaluators.functions;
+
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.context.IHyracksTaskContext;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+
+/**
+ * Implements 'lax' cast. It differs from the regular cast as follows:
+ * <ul>
+ * <li>Numeric type demotion does not fail if the input value is not within bounds for the target type.
+ * Instead it returns min/max value of the target type.
+ * </li>
+ * <li>
+ * If there's an error during casting then 'MISSING' is returned.
+ * Note that errors from argument evaluation are still propagated.
+ * </li>
+ * </ul>
+ */
+public class CastTypeLaxDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+
+    public static final IFunctionDescriptorFactory FACTORY = new IFunctionDescriptorFactory() {
+        @Override
+        public IFunctionDescriptor createFunctionDescriptor() {
+            return new CastTypeLaxDescriptor();
+        }
+    };
+
+    private static final long serialVersionUID = 1L;
+    private IAType reqType;
+    private IAType inputType;
+
+    private CastTypeLaxDescriptor() {
+    }
+
+    @Override
+    public void setImmutableStates(Object... states) {
+        reqType = (IAType) states[0];
+        inputType = (IAType) states[1];
+        // If reqType or inputType is null, or they are the same, it indicates there is a bug in the compiler.
+        if (reqType == null || inputType == null || reqType.equals(inputType)) {
+            throw new IllegalStateException(
+                    "Invalid types for casting, required type " + reqType + ", input type " + inputType);
+        }
+        // NULLs and MISSINGs are handled by the generated code, therefore we only need to handle actual types here.
+        this.reqType = TypeComputeUtils.getActualType(reqType);
+        this.inputType = TypeComputeUtils.getActualType(inputType);
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return BuiltinFunctions.CAST_TYPE_LAX;
+    }
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(final IScalarEvaluatorFactory[] args) {
+        final IScalarEvaluatorFactory recordEvalFactory = args[0];
+
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IHyracksTaskContext ctx) throws HyracksDataException {
+                return new CastTypeLaxEvaluator(reqType, inputType, recordEvalFactory.createScalarEvaluator(ctx));
+            }
+        };
+    }
+}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxEvaluator.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxEvaluator.java
new file mode 100644
index 0000000..cbe04e2
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/CastTypeLaxEvaluator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.asterix.runtime.evaluators.functions;
+
+import org.apache.asterix.om.pointables.cast.ACastVisitor;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+class CastTypeLaxEvaluator extends CastTypeEvaluator {
+
+    private static final Logger LOGGER = Logger.getLogger(CastTypeLaxEvaluator.class.getName());
+
+    private static final byte[] MISSING_BYTES = new byte[] { ATypeTag.SERIALIZED_MISSING_TYPE_TAG };
+
+    CastTypeLaxEvaluator(IAType reqType, IAType inputType, IScalarEvaluator argEvaluator) {
+        super(reqType, inputType, argEvaluator);
+    }
+
+    @Override
+    protected ACastVisitor createCastVisitor() {
+        return new ACastVisitor(false);
+    }
+
+    @Override
+    protected void cast(IPointable result) {
+        try {
+            super.cast(result);
+        } catch (HyracksDataException e) {
+            if (LOGGER.isLoggable(Level.FINEST)) {
+                LOGGER.log(Level.FINEST, e.toString(), e);
+            }
+            result.set(MISSING_BYTES, 0, MISSING_BYTES.length);
+        }
+    }
+}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/formats/NonTaggedDataFormat.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/formats/NonTaggedDataFormat.java
index b0e1788..7f4b9c4 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/formats/NonTaggedDataFormat.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/formats/NonTaggedDataFormat.java
@@ -440,6 +440,16 @@
                 fd.setImmutableStates(rt, it);
             }
         });
+        functionTypeInferers.put(BuiltinFunctions.CAST_TYPE_LAX, new FunctionTypeInferer() {
+            @Override
+            public void infer(ILogicalExpression expr, IFunctionDescriptor fd, IVariableTypeEnvironment context)
+                    throws AlgebricksException {
+                AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expr;
+                IAType rt = TypeCastUtils.getRequiredType(funcExpr);
+                IAType it = (IAType) context.getType(funcExpr.getArguments().get(0).getValue());
+                fd.setImmutableStates(rt, it);
+            }
+        });
         functionTypeInferers.put(BuiltinFunctions.OPEN_RECORD_CONSTRUCTOR, new FunctionTypeInferer() {
             @Override
             public void infer(ILogicalExpression expr, IFunctionDescriptor fd, IVariableTypeEnvironment context)