in asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/typecast/StaticTypeCastUtil.java [296:489]
private static boolean staticRecordTypeCast(AbstractFunctionCallExpression func, ARecordType reqType,
ARecordType inputType, IVariableTypeEnvironment env) throws AlgebricksException {
if (!(func.getFunctionIdentifier() == BuiltinFunctions.OPEN_RECORD_CONSTRUCTOR
|| func.getFunctionIdentifier() == BuiltinFunctions.CLOSED_RECORD_CONSTRUCTOR)) {
return false;
}
List<Mutable<ILogicalExpression>> arguments = func.getArguments();
int fieldArgumentCount = arguments.size() / 2;
IAType[] reqFieldTypes = reqType.getFieldTypes();
String[] reqFieldNames = reqType.getFieldNames();
IAType[] inputFieldTypes = inputType.getFieldTypes();
String[] inputFieldNames = inputType.getFieldNames();
int[] fieldPermutation = new int[reqFieldTypes.length];
BitSet nullFields = new BitSet(reqFieldTypes.length);
BitSet openFields = new BitSet(fieldArgumentCount);
Arrays.fill(fieldPermutation, -1);
openFields.set(0, fieldArgumentCount);
// forward match: match from actual to required
for (int i = 0; i < inputFieldNames.length; i++) {
String fieldName = inputFieldNames[i];
IAType fieldType = inputFieldTypes[i];
int fieldNameArgumentIdx = findFieldNameArgumentIdx(arguments, fieldName);
if (fieldNameArgumentIdx < 0) {
return false;
}
int fieldValueArgumentIdx = fieldNameArgumentIdx + 1;
int fieldArgumentIdx = fieldNameArgumentIdx / 2;
ILogicalExpression arg = arguments.get(fieldValueArgumentIdx).getValue();
boolean matched = false;
for (int j = 0; j < reqFieldNames.length; j++) {
String reqFieldName = reqFieldNames[j];
IAType reqFieldType = reqFieldTypes[j];
if (fieldName.equals(reqFieldName)) {
//type matched
if (fieldType.equals(reqFieldType)) {
fieldPermutation[j] = fieldNameArgumentIdx;
openFields.clear(fieldArgumentIdx);
matched = true;
if (arg.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
ScalarFunctionCallExpression scalarFunc = (ScalarFunctionCallExpression) arg;
rewriteFuncExpr(scalarFunc, reqFieldType, fieldType, env);
}
break;
}
// match the optional field
if (NonTaggedFormatUtil.isOptional(reqFieldType)) {
IAType itemType = ((AUnionType) reqFieldType).getActualType();
reqFieldType = itemType;
if (fieldType.equals(BuiltinType.AMISSING) || fieldType.equals(itemType)) {
fieldPermutation[j] = fieldNameArgumentIdx;
openFields.clear(fieldArgumentIdx);
matched = true;
// rewrite record expr
if (arg.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
ScalarFunctionCallExpression scalarFunc = (ScalarFunctionCallExpression) arg;
rewriteFuncExpr(scalarFunc, reqFieldType, fieldType, env);
}
break;
}
}
// match the optional type input for a non-optional field
// delay that to runtime by calling the not-null function
if (NonTaggedFormatUtil.isOptional(fieldType)) {
IAType itemType = ((AUnionType) fieldType).getActualType();
if (reqFieldType.equals(itemType)) {
fieldPermutation[j] = fieldNameArgumentIdx;
openFields.clear(fieldArgumentIdx);
matched = true;
ScalarFunctionCallExpression notNullFunc = new ScalarFunctionCallExpression(
FunctionUtil.getFunctionInfo(BuiltinFunctions.CHECK_UNKNOWN));
notNullFunc.getArguments().add(new MutableObject<>(arg));
//wrap the not null function to the original function
arguments.get(fieldValueArgumentIdx).setValue(notNullFunc);
break;
}
}
// match the record field: need cast
if (arg.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
ScalarFunctionCallExpression scalarFunc = (ScalarFunctionCallExpression) arg;
rewriteFuncExpr(scalarFunc, reqFieldType, fieldType, env);
fieldPermutation[j] = fieldNameArgumentIdx;
openFields.clear(fieldArgumentIdx);
matched = true;
break;
}
}
}
// the input has extra fields
if (!matched && !reqType.isOpen()) {
throw new AlgebricksException("static type mismatch: the input record includes an extra closed field "
+ fieldName + ":" + fieldType + "! Please check the field name and type.");
}
}
// backward match: match from required to actual
for (int i = 0; i < reqFieldNames.length; i++) {
String reqFieldName = reqFieldNames[i];
IAType reqFieldType = reqFieldTypes[i];
boolean matched = false;
for (int j = 0; j < inputFieldNames.length; j++) {
String fieldName = inputFieldNames[j];
IAType fieldType = inputFieldTypes[j];
if (!fieldName.equals(reqFieldName)) {
continue;
}
// should check open field here
// because number of entries in fieldPermutations is the
// number of required schema fields
// here we want to check if an input field is matched
// the entry index of fieldPermutations is req field index
int fieldNameArgumentIdx = findFieldNameArgumentIdx(arguments, fieldName);
if (fieldNameArgumentIdx < 0) {
return false;
}
int fieldArgumentIdx = fieldNameArgumentIdx / 2;
if (!openFields.get(fieldArgumentIdx)) {
matched = true;
break;
}
// match the optional field
if (!NonTaggedFormatUtil.isOptional(reqFieldType)) {
continue;
}
IAType itemType = ((AUnionType) reqFieldType).getActualType();
if (fieldType.equals(BuiltinType.AMISSING) || fieldType.equals(itemType)) {
matched = true;
break;
}
}
if (matched) {
continue;
}
if (NonTaggedFormatUtil.isOptional(reqFieldType)) {
// add a null field
nullFields.set(i);
} else {
// no matched field in the input for a required closed field
if (inputType.isOpen()) {
//if the input type is open, return false, give that to dynamic type cast to defer the error to the runtime
return false;
} else {
throw new AlgebricksException(
"static type mismatch: the input record misses a required closed field " + reqFieldName
+ ":" + reqFieldType + "! Please check the field name and type.");
}
}
}
List<Mutable<ILogicalExpression>> newArguments = new ArrayList<>(arguments.size());
// re-order the closed part and fill in null fields
for (int i = 0; i < reqFieldTypes.length; i++) {
int pos = fieldPermutation[i];
if (pos >= 0) {
newArguments.add(arguments.get(pos));
newArguments.add(arguments.get(pos + 1));
}
if (nullFields.get(i)) {
// add a null field
newArguments.add(new MutableObject<>(
new ConstantExpression(new AsterixConstantValue(new AString(reqFieldNames[i])))));
newArguments.add(new MutableObject<>(new ConstantExpression(new AsterixConstantValue(ANull.NULL))));
}
}
// add the open part
for (int i = openFields.nextSetBit(0); i >= 0; i = openFields.nextSetBit(i + 1)) {
newArguments.add(arguments.get(i * 2));
Mutable<ILogicalExpression> expRef = arguments.get(i * 2 + 1);
IAType expType = (IAType) env.getType(expRef.getValue());
injectCastToRelaxType(expRef, expType, env);
newArguments.add(expRef);
}
arguments.clear();
arguments.addAll(newArguments);
return true;
}