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

com.hazelcast.sql.impl.calcite.validate.HazelcastSqlValidator 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;

import com.hazelcast.com.hazelcast.sql.impl.calcite.schema.HazelcastTable;
import com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastIntegerType;
import com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeCoercion;
import com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlIdentifier;
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.SqlOperatorTable;
import com.hazelcast.org.apache.calcite.sql.SqlUtil;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.sql.util.ChainedSqlOperatorTable;
import com.hazelcast.org.apache.calcite.sql.validate.SelectScope;
import com.hazelcast.org.apache.calcite.sql.validate.SqlConformance;
import com.hazelcast.org.apache.calcite.sql.validate.SqlQualified;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorImpl;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorScope;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorTable;
import com.hazelcast.org.apache.calcite.util.Util;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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.canRepresent;
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.narrowestTypeFor;
import static com.hazelcast.com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeSystem.typeName;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.CHAR;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.DECIMAL;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.NULL;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.NUMERIC_TYPES;
import static com.hazelcast.org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
import static com.hazelcast.org.apache.calcite.util.Static.RESOURCE;

/**
 * Hazelcast-specific SQL validator.
 */
public class HazelcastSqlValidator extends SqlValidatorImpl {

    private static final Config CONFIG = Config.DEFAULT.withIdentifierExpansion(true);

    /**
     * We manage an additional map of known node types on our own to workaround
     * a bug in {@link SqlValidatorImpl#getValidatedNodeTypeIfKnown}: it's
     * supposed to return {@code null} if a node type is unknown, but for
     * rewritten expressions ({@code originalExprs}) it still invokes {@link
     * SqlValidatorImpl#getValidatedNodeType} under the hood and that leads to
     * exceptions for nodes with unknown types instead of the expected {@code
     * null} result.
     */
    private final Map knownNodeTypes = new IdentityHashMap<>();

    public HazelcastSqlValidator(
        SqlValidatorCatalogReader catalogReader,
        HazelcastTypeFactory typeFactory,
        SqlConformance conformance
    ) {
        this(null, catalogReader, typeFactory, conformance);
    }

    public HazelcastSqlValidator(
        SqlOperatorTable extensionOperatorTable,
        SqlValidatorCatalogReader catalogReader,
        HazelcastTypeFactory typeFactory,
        SqlConformance conformance
    ) {
        super(operatorTable(extensionOperatorTable), catalogReader, typeFactory, CONFIG.withSqlConformance(conformance));
        setTypeCoercion(new HazelcastTypeCoercion(this));
    }

    private static SqlOperatorTable operatorTable(SqlOperatorTable extensionOperatorTable) {
        List operatorTables = new ArrayList<>();

        if (extensionOperatorTable != null) {
            operatorTables.add(extensionOperatorTable);
        }

        operatorTables.add(HazelcastSqlOperatorTable.instance());
        operatorTables.add(SqlStdOperatorTable.instance());

        return new ChainedSqlOperatorTable(operatorTables);
    }

    /**
     * Sets {@code type} as the known type for {@code node}.
     *
     * @param node the node to set the known type of.
     * @param type the type to set the know node type to.
     */
    public void setKnownNodeType(SqlNode node, RelDataType type) {
        assert !getUnknownType().equals(type);
        knownNodeTypes.put(node, type);
    }

    /**
     * Obtains a type known by this validator for the given node.
     *
     * @param node the node to obtain the type of.
     * @return the node type known by this validator or {@code null} if the type
     * of the given node is not known yet.
     */
    public RelDataType getKnownNodeType(SqlNode node) {
        return knownNodeTypes.get(node);
    }

    @Override
    protected void addToSelectList(List list, Set aliases, List> fieldList,
                                   SqlNode exp, SelectScope scope, boolean includeSystemVars) {
        if (isHiddenColumn(exp, scope)) {
            return;
        }

        super.addToSelectList(list, aliases, fieldList, exp, scope, includeSystemVars);
    }

    @Override
    public RelDataType deriveType(SqlValidatorScope scope, SqlNode expression) {
        RelDataType derived = super.deriveType(scope, expression);
        assert derived != null;

        if (derived.getSqlTypeName() == CHAR) {
            // normalize CHAR to VARCHAR
            derived = HazelcastTypeFactory.INSTANCE.createSqlType(VARCHAR, derived.isNullable());
            setValidatedNodeType(expression, derived);
        }

        switch (expression.getKind()) {
            case LITERAL:
                return deriveLiteralType(derived, expression);

            case CAST:
                return deriveCastType(derived, scope, expression);

            default:
                return derived;
        }
    }

    @Override
    public void validateLiteral(SqlLiteral literal) {
        validateLiteral(literal, getValidatedNodeType(literal));
    }

    @Override
    public void validateCall(SqlCall call, SqlValidatorScope scope) {
        // Enforce type derivation for all calls before validation. Calcite may
        // skip it if a call has a fixed type, for instance AND always has
        // BOOLEAN type, so operands may end up having no validated type.
        deriveType(scope, call);
        super.validateCall(call, scope);
    }

    @Override
    protected SqlNode performUnconditionalRewrites(SqlNode node, boolean underFrom) {
        SqlNode rewritten = super.performUnconditionalRewrites(node, underFrom);

        if (rewritten != null && rewritten.isA(SqlKind.TOP_LEVEL)) {
            // Rewrite operators to Hazelcast ones starting at every top node.
            // For instance, SELECT a + b is rewritten to SELECT a + b, where
            // the first '+' refers to the standard Calcite SqlStdOperatorTable.PLUS
            // operator and the second '+' refers to HazelcastSqlOperatorTable.PLUS
            // operator.
            rewritten.accept(HazelcastOperatorTableVisitor.INSTANCE);
        }

        return rewritten;
    }

    private RelDataType deriveLiteralType(RelDataType derived, SqlNode expression) {
        RelDataType known = knownNodeTypes.get(expression);
        if (derived == known) {
            return derived;
        }

        SqlLiteral literal = (SqlLiteral) expression;

        if (HazelcastIntegerType.supports(typeName(derived)) && literal.getValue() != null) {
            // Assign narrowest type to non-null integer literals.

            derived = HazelcastIntegerType.deriveLiteralType(literal);
            setKnownAndValidatedNodeType(expression, derived);
        }

        return derived;
    }

    @SuppressWarnings({"checkstyle:CyclomaticComplexity", "checkstyle:NPathComplexity"})
    private RelDataType deriveCastType(RelDataType derived, SqlValidatorScope scope, SqlNode expression) {
        RelDataType known = knownNodeTypes.get(expression);
        if (derived == known) {
            return derived;
        }

        SqlCall call = (SqlCall) expression;
        SqlNode operand = call.operand(0);
        RelDataType from = deriveType(scope, operand);

        RelDataType to = deriveType(scope, call.operand(1));
        assert !to.isNullable();

        // Handle NULL.

        if (SqlUtil.isNullLiteral(operand, false)) {
            setKnownAndValidatedNodeType(operand, HazelcastTypeFactory.INSTANCE.createSqlType(NULL));
            derived = HazelcastTypeFactory.INSTANCE.createTypeWithNullability(to, true);
            setKnownAndValidatedNodeType(expression, derived);
            return derived;
        }

        derived = to;

        // Assign type for parameters.

        if (isParameter(operand)) {
            from = HazelcastTypeFactory.INSTANCE.createTypeWithNullability(to, true);
        }

        // Assign type to numeric literals and validate them.

        Number numeric = isNumeric(from) || isNumeric(to) ? numericValue(operand) : null;

        if (numeric != null) {
            from = narrowestTypeFor(numeric, typeName(to));
        }

        if (isLiteral(operand)) {
            validateLiteral((SqlLiteral) operand, to);
        }

        // Infer return type.

        if (isInteger(to) && isInteger(from)) {
            derived = HazelcastIntegerType.deriveCastType(from, to);
        } else if (isInteger(to) && numeric != null) {
            long longValue = numeric.longValue();
            derived = HazelcastIntegerType.deriveCastType(longValue, to);
        }

        derived = HazelcastTypeFactory.INSTANCE.createTypeWithNullability(derived, from.isNullable());

        setKnownAndValidatedNodeType(operand, from);
        setKnownAndValidatedNodeType(expression, derived);

        return derived;
    }

    private void validateLiteral(SqlLiteral literal, RelDataType type) {
        SqlTypeName literalTypeName = literal.getTypeName();

        if (!canRepresent(literal, type)) {
            if (NUMERIC_TYPES.contains(literalTypeName) && isNumeric(type)) {
                throw newValidationError(literal, RESOURCE.numberLiteralOutOfRange(literal.toString()));
            } else {
                throw SqlUtil.newContextException(literal.getParserPosition(),
                        RESOURCE.invalidLiteral(literal.toString(), type.toString()));
            }
        }

        if (literalTypeName != DECIMAL) {
            super.validateLiteral(literal);
        }
    }

    private void setKnownAndValidatedNodeType(SqlNode node, RelDataType type) {
        setKnownNodeType(node, type);
        setValidatedNodeType(node, type);
    }

    private boolean isHiddenColumn(SqlNode node, SelectScope scope) {
        if (!(node instanceof SqlIdentifier)) {
            return false;
        }

        SqlIdentifier identifier = (SqlIdentifier) node;

        String fieldName = extractFieldName(identifier, scope);

        if (fieldName == null) {
            return false;
        }

        SqlValidatorTable table = scope.fullyQualify(identifier).namespace.getTable();

        if (table == null) {
            return false;
        }

        HazelcastTable unwrappedTable = table.unwrap(HazelcastTable.class);

        if (unwrappedTable == null) {
            return false;
        }

        return unwrappedTable.isHidden(fieldName);
    }

    private String extractFieldName(SqlIdentifier identifier, SelectScope scope) {
        SqlCall call = makeNullaryCall(identifier);

        if (call != null) {
            return null;
        }

        SqlQualified qualified = scope.fullyQualify(identifier);

        List names = qualified.identifier.names;

        if (names.size() < 2) {
            return null;
        }

        return Util.last(names);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy