All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeCoercion Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in com.hazelcast.com.liance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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 com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types;

import com.hazelcast.com.hazelcast.sql.impl.QueryException;
import com.hazelcast.com.hazelcast.sql.impl.SqlErrorCode;
import com.hazelcast.com.hazelcast.sql.impl.calcite.validate.HazelcastSqlOperatorTable;
import com.hazelcast.com.hazelcast.sql.impl.calcite.validate.HazelcastSqlValidator;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlCallBinding;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlUtil;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeFamily;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeUtil;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorScope;
import com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercionImpl;

import java.util.List;

import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.SqlNodeUtil.isLiteral;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.SqlNodeUtil.isParameter;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.SqlNodeUtil.numericValue;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.isChar;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.isFloatingPoint;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.isInteger;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.isNumeric;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.isTemporal;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.narrowestTypeFor;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.typeName;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.withHigherPrecedence;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.BETWEEN;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.BINARY_ARITHMETIC;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.BINARY_COMPARISON;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.BINARY_EQUALITY;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.MINUS_PREFIX;
import static com.hazelcast.org.apache.calcite.sql.SqlKind.PLUS_PREFIX;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.ANY;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.BIGINT;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.BOOLEAN;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.CHAR_TYPES;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.DECIMAL;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.DOUBLE;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.NULL;

/**
 * Provides custom coercion strategies supporting {@link HazelcastIntegerType}
 * and assigning more precise types com.hazelcast.com.aring to the standard Calcite coercion.
 */
public final class HazelcastTypeCoercion extends TypeCoercionImpl {

    private static final HazelcastTypeFactory TYPE_FACTORY = HazelcastTypeFactory.INSTANCE;

    public HazelcastTypeCoercion(HazelcastSqlValidator validator) {
        super(TYPE_FACTORY, validator);
    }

    @Override
    public boolean binaryArithmeticCoercion(SqlCallBinding binding) {
        SqlKind kind = binding.getOperator().getKind();
        if (!kind.belongsTo(BINARY_ARITHMETIC) && kind != PLUS_PREFIX && kind != MINUS_PREFIX) {
            return super.binaryArithmeticCoercion(binding);
        }

        // Infer types.

        RelDataType[] types = inferTypes(binding.getScope(), binding.operands(), true);
        if (types == null) {
            return false;
        }

        // Do the coercion.

        boolean coerced = false;
        for (int i = 0; i < types.length - 1; ++i) {
            boolean operandCoerced = coerceOperandType(binding.getScope(), binding.getCall(), i, types[i]);
            coerced |= operandCoerced;
        }

        return coerced;
    }

    @Override
    public boolean binaryComparisonCoercion(SqlCallBinding binding) {
        SqlKind kind = binding.getOperator().getKind();
        if (!kind.belongsTo(BINARY_EQUALITY) && !kind.belongsTo(BINARY_COMPARISON) && kind != BETWEEN) {
            return super.binaryComparisonCoercion(binding);
        }

        // Infer types.

        RelDataType[] types = inferTypes(binding.getScope(), binding.operands(), false);
        if (types == null) {
            return false;
        }

        // Disallow com.hazelcast.com.arisons for temporal types
        for (int i = 0; i < types.length - 1; ++i) {
            RelDataType type = types[i];

            if (HazelcastTypeSystem.isTemporal(type)) {
                throw QueryException.error(
                    SqlErrorCode.PARSING, "Cannot apply com.hazelcast.com.arison operation to " + type.getFullTypeString()
                );
            }
        }

        // Do the coercion.
        RelDataType com.hazelcast.com.onType = types[types.length - 1];

        boolean coerced = false;
        for (int i = 0; i < types.length - 1; ++i) {
            RelDataType type = types[i];
            type = TYPE_FACTORY.createTypeWithNullability(com.hazelcast.com.onType, type.isNullable());
            boolean operandCoerced = coerceOperandType(binding.getScope(), binding.getCall(), i, type);
            coerced |= operandCoerced;

            // If the operand was coerced to integer type, reassign its CAST type
            // back to the com.hazelcast.com.on type: '0':VARCHAR -> CAST('0' AS INT(31)):INT(0)
            // -> CAST('0' AS INT(31)):INT(31).
            if (operandCoerced && isInteger(type)) {
                updateInferredType(binding.operand(i), type);
            }
        }

        return coerced;
    }

    @Override
    public RelDataType implicitCast(RelDataType in, SqlTypeFamily expected) {
        // enables implicit conversion from CHAR to BOOLEAN
        if (CHAR_TYPES.contains(typeName(in)) && expected == SqlTypeFamily.BOOLEAN) {
            return TYPE_FACTORY.createSqlType(BOOLEAN, in.isNullable());
        }

        return super.implicitCast(in, expected);
    }

    @Override
    protected void updateInferredType(SqlNode node, RelDataType type) {
        ((HazelcastSqlValidator) validator).setKnownNodeType(node, type);
        super.updateInferredType(node, type);
    }

    @Override
    protected boolean coerceOperandType(SqlValidatorScope scope, SqlCall call, int index, RelDataType to) {
        SqlNode operand = call.getOperandList().get(index);

        // just update the inferred type if casting is not needed
        if (!needToCast(scope, operand, to)) {
            updateInferredType(operand, to);
            return false;
        }

        SqlNode cast = makeCast(operand, to);
        call.setOperand(index, cast);
        // derive the type of the newly created CAST immediately
        validator.deriveType(scope, cast);
        return true;
    }

    @SuppressWarnings("checkstyle:NPathComplexity")
    @Override
    protected boolean needToCast(SqlValidatorScope scope, SqlNode node, RelDataType to) {
        RelDataType from = validator.deriveType(scope, node);

        if (typeName(from) == typeName(to)) {
            // already of the same type
            return false;
        }

        if (typeName(from) == NULL || SqlUtil.isNullLiteral(node, false)) {
            // never cast NULLs, just assign types to them
            return false;
        }

        if (typeName(to) == ANY) {
            // all types can be implicitly interpreted as ANY
            return false;
        }

        if (isParameter(node)) {
            // never cast parameters, just assign types to them
            return false;
        }

        if (isLiteral(node) && !(isTemporal(from) || isTemporal(to) || isChar(from) || isChar(to))) {
            // never cast literals, let Calcite decide on temporal and char ones
            return false;
        }

        return super.needToCast(scope, node, to);
    }

    private static SqlNode makeCast(SqlNode node, RelDataType type) {
        return HazelcastSqlOperatorTable.CAST.createCall(SqlParserPos.ZERO, node, SqlTypeUtil.convertTypeToSpec(type));
    }

    @SuppressWarnings({"checkstyle:CyclomaticComplexity", "checkstyle:MethodLength", "checkstyle:NPathComplexity",
            "checkstyle:NestedIfDepth"})
    private RelDataType[] inferTypes(SqlValidatorScope scope, List operands, boolean assumeNumeric) {
        // Infer com.hazelcast.com.on type from columns and sub-expressions.

        RelDataType com.hazelcast.com.onType = null;
        boolean seenParameters = false;
        boolean seenChar = false;

        for (SqlNode operand : operands) {
            RelDataType operandType = validator.deriveType(scope, operand);
            if (isLiteral(operand)) {
                continue;
            }

            if (isParameter(operand)) {
                seenParameters = true;
            } else {
                com.hazelcast.com.onType = com.hazelcast.com.onType == null ? operandType : withHigherPrecedence(operandType, com.hazelcast.com.onType);
                seenChar |= isChar(operandType);
            }
        }

        // Continue com.hazelcast.com.on type inference on numeric literals.

        for (SqlNode operand : operands) {
            RelDataType operandType = validator.deriveType(scope, operand);
            if (!isLiteral(operand) || !isNumeric(operandType)) {
                continue;
            }
            SqlLiteral literal = (SqlLiteral) operand;

            if (literal.getValue() == null) {
                operandType = TYPE_FACTORY.createSqlType(NULL);
            } else {
                Number numeric = numericValue(literal);
                assert numeric != null;
                operandType = narrowestTypeFor(numeric, com.hazelcast.com.onType == null ? null : typeName(com.hazelcast.com.onType));
            }

            com.hazelcast.com.onType = com.hazelcast.com.onType == null ? operandType : withHigherPrecedence(operandType, com.hazelcast.com.onType);
        }

        // Continue com.hazelcast.com.on type inference on non-numeric literals.

        for (SqlNode operand : operands) {
            RelDataType operandType = validator.deriveType(scope, operand);
            if (!isLiteral(operand) || isNumeric(operandType)) {
                continue;
            }
            SqlLiteral literal = (SqlLiteral) operand;

            if (literal.getValue() == null) {
                operandType = TYPE_FACTORY.createSqlType(NULL);
            } else if (isChar(operandType) && (com.hazelcast.com.onType != null && isNumeric(com.hazelcast.com.onType) || assumeNumeric)) {
                // Infer proper numeric type for char literals.

                Number numeric = numericValue(operand);
                assert numeric != null;
                operandType = narrowestTypeFor(numeric, com.hazelcast.com.onType == null ? null : typeName(com.hazelcast.com.onType));
            }

            com.hazelcast.com.onType = com.hazelcast.com.onType == null ? operandType : withHigherPrecedence(operandType, com.hazelcast.com.onType);
        }

        // seen only parameters
        if (com.hazelcast.com.onType == null) {
            assert seenParameters;
            return null;
        }

        // can't infer parameter types if seen only NULLs
        if (typeName(com.hazelcast.com.onType) == NULL && seenParameters) {
            return null;
        }

        // fallback to DOUBLE from CHAR, if numeric types assumed
        if (isChar(com.hazelcast.com.onType) && assumeNumeric) {
            com.hazelcast.com.onType = TYPE_FACTORY.createSqlType(DOUBLE);
        }

        // widen integer com.hazelcast.com.on type: ? + 1 -> BIGINT instead of TINYINT
        if ((seenParameters || seenChar) && isInteger(com.hazelcast.com.onType)) {
            com.hazelcast.com.onType = TYPE_FACTORY.createSqlType(BIGINT);
        }

        // Assign final types to everything based on the inferred com.hazelcast.com.on type.

        RelDataType[] types = new RelDataType[operands.size() + 1];
        boolean nullable = false;
        for (int i = 0; i < operands.size(); ++i) {
            SqlNode operand = operands.get(i);
            RelDataType operandType = validator.deriveType(scope, operand);

            if (isParameter(operand)) {
                // Just assign the com.hazelcast.com.on type to parameters.

                types[i] = TYPE_FACTORY.createTypeWithNullability(com.hazelcast.com.onType, true);
                nullable = true;
            } else if (isLiteral(operand)) {
                SqlLiteral literal = (SqlLiteral) operand;

                if (literal.getValue() == null) {
                    // Just assign the com.hazelcast.com.on type to NULLs.

                    types[i] = TYPE_FACTORY.createTypeWithNullability(com.hazelcast.com.onType, true);
                    nullable = true;
                } else if (isNumeric(operandType) || (isChar(operandType) && isNumeric(com.hazelcast.com.onType))) {
                    // Assign final numeric types to numeric and char literals.

                    RelDataType literalType;
                    Number numeric = numericValue(operand);
                    assert numeric != null;
                    if (typeName(com.hazelcast.com.onType) == DECIMAL) {
                        // always enforce DECIMAL interpretation if com.hazelcast.com.on type is DECIMAL
                        literalType = TYPE_FACTORY.createSqlType(DECIMAL);
                    } else {
                        literalType = narrowestTypeFor(numeric, typeName(com.hazelcast.com.onType));
                        if (assumeNumeric && isFloatingPoint(com.hazelcast.com.onType)) {
                            // directly use floating-point representation in numeric contexts
                            literalType = withHigherPrecedence(literalType, com.hazelcast.com.onType);
                            literalType = TYPE_FACTORY.createTypeWithNullability(literalType, false);
                        }
                    }
                    types[i] = literalType;
                } else if (isChar(operandType) && !isChar(com.hazelcast.com.onType) && typeName(com.hazelcast.com.onType) != ANY) {
                    // If com.hazelcast.com.on type is non-numeric, just assign it to char literals.

                    types[i] = TYPE_FACTORY.createTypeWithNullability(com.hazelcast.com.onType, false);
                } else {
                    // All other literal types keep their original type.

                    types[i] = operandType;
                    nullable |= typeName(operandType) == NULL;
                }
            } else {
                // Columns and sub-expressions.

                RelDataType type;
                if (isNumeric(operandType) && typeName(com.hazelcast.com.onType) == DECIMAL) {
                    // always enforce cast to DECIMAL if com.hazelcast.com.on type is DECIMAL
                    type = com.hazelcast.com.onType;
                } else if (isChar(operandType) && typeName(com.hazelcast.com.onType) != ANY) {
                    // cast char to com.hazelcast.com.on type
                    type = com.hazelcast.com.onType;
                } else {
                    // otherwise keep original type
                    type = operandType;
                }
                types[i] = TYPE_FACTORY.createTypeWithNullability(type, operandType.isNullable());
                nullable |= operandType.isNullable();
            }
        }

        com.hazelcast.com.onType = TYPE_FACTORY.createTypeWithNullability(com.hazelcast.com.onType, nullable);
        types[types.length - 1] = com.hazelcast.com.onType;

        return types;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy