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.5.0
Show newest version
/*
 * Copyright (c) 2008-2021, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed 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 com.hazelcast.sql.impl.calcite.validate;

import com.hazelcast.sql.impl.ParameterConverter;
import com.hazelcast.sql.impl.calcite.schema.HazelcastTable;
import com.hazelcast.sql.impl.calcite.validate.literal.LiteralUtils;
import com.hazelcast.sql.impl.calcite.validate.param.StrictParameterConverter;
import com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeCoercion;
import com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeFactory;
import com.hazelcast.sql.impl.calcite.validate.types.HazelcastTypeUtils;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlDynamicParam;
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.SqlNumericLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlOperatorTable;
import com.hazelcast.org.apache.calcite.sql.SqlSelect;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
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.SqlValidatorImplBridge;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.hazelcast.sql.impl.calcite.parse.UnsupportedOperationVisitor.error;

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

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

    /** Visitor to rewrite Calcite operators to Hazelcast operators. */
    private final HazelcastSqlOperatorTable.RewriteVisitor rewriteVisitor;

    /** Parameter converter that will be passed to parameter metadata. */
    private final Map parameterConverterMap = new HashMap<>();

    /** Parameter positions. */
    private final Map parameterPositionMap = new HashMap<>();

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

        rewriteVisitor = new HazelcastSqlOperatorTable.RewriteVisitor(this);
    }

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

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

        operatorTables.add(HazelcastSqlOperatorTable.instance());

        return new ChainedSqlOperatorTable(operatorTables);
    }

    @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 deriveTypeImpl(SqlValidatorScope scope, SqlNode operand) {
        if (operand.getKind() == SqlKind.LITERAL) {
            RelDataType literalType = LiteralUtils.literalType(operand, (HazelcastTypeFactory) typeFactory);

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

        return super.deriveTypeImpl(scope, operand);
    }

    @Override
    public void validateLiteral(SqlLiteral literal) {
        // Disable validation of literals
    }

    @Override
    public void validateDynamicParam(SqlDynamicParam dynamicParam) {
        parameterPositionMap.put(dynamicParam.getIndex(), dynamicParam.getParserPosition());
    }

    @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
    public void validateQuery(SqlNode node, SqlValidatorScope scope, RelDataType targetRowType) {
        super.validateQuery(node, scope, targetRowType);

        if (node instanceof SqlSelect) {
            // Derive the types for offset-fetch expressions, Calcite doesn't do
            // that automatically.

            SqlSelect select = (SqlSelect) node;

            SqlNode offset = select.getOffset();
            if (offset != null) {
                deriveType(scope, offset);
                validateNonNegativeValue(offset);
            }

            SqlNode fetch = select.getFetch();
            if (fetch != null) {
                deriveType(scope, fetch);
                validateNonNegativeValue(fetch);
            }
        }
    }

    private void validateNonNegativeValue(SqlNode sqlNode) {
        if (!(sqlNode instanceof SqlNumericLiteral)) {
            throw error(sqlNode, "FETCH/OFFSET must be a numeric literal");
        }
        Object value = ((SqlNumericLiteral) sqlNode).getValue();
        long value0 = ((Number) value).longValue();

        if (value0 < 0L) {
            throw error(sqlNode, "FETCH/OFFSET value cannot be negative: " + value0);
        }
    }

    @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(rewriteVisitor);
        }

        return rewritten;
    }

    @Override
    public HazelcastTypeCoercion getTypeCoercion() {
        return (HazelcastTypeCoercion) super.getTypeCoercion();
    }

    public void setParameterConverter(int ordinal, ParameterConverter parameterConverter) {
        parameterConverterMap.put(ordinal, parameterConverter);
    }

    public ParameterConverter[] getParameterConverters(SqlNode node) {
        // Get original parameter row type.
        RelDataType rowType = getParameterRowType(node);

        // Create precedence-based converters with optional override by a more specialized converters.
        ParameterConverter[] res = new ParameterConverter[rowType.getFieldCount()];

        for (int i = 0; i < res.length; i++) {
            ParameterConverter converter = parameterConverterMap.get(i);

            if (converter == null) {
                converter = new StrictParameterConverter(
                    i,
                    parameterPositionMap.get(i),
                    HazelcastTypeUtils.toHazelcastType(rowType.getFieldList().get(i).getType().getSqlTypeName())
                );
            }

            res[i] = converter;
        }

        return res;
    }

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

    /**
     * Returns whether the validated node returns an infinite number of rows.
     *
     * @throws IllegalStateException if called before the node is validated.
     */
    public boolean isInfiniteRows() {
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy