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)