private static boolean staticRecordTypeCast()

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;
    }