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

com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercionImpl Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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.org.apache.calcite.sql.validate.implicit;

import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlCallBinding;
import com.hazelcast.org.apache.calcite.sql.SqlFunction;
import com.hazelcast.org.apache.calcite.sql.SqlIdentifier;
import com.hazelcast.org.apache.calcite.sql.SqlInsert;
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.SqlNodeList;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.SqlSelect;
import com.hazelcast.org.apache.calcite.sql.SqlUpdate;
import com.hazelcast.org.apache.calcite.sql.SqlWith;
import com.hazelcast.org.apache.calcite.sql.fun.SqlCase;
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.SqlValidator;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorScope;
import com.hazelcast.org.apache.calcite.util.Util;

import java.math.BigDecimal;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Default implementation of Calcite implicit type cast.
 */
public class TypeCoercionImpl extends AbstractTypeCoercion {

  public TypeCoercionImpl(RelDataTypeFactory typeFactory, SqlValidator validator) {
    super(typeFactory, validator);
  }

  /**
   * Widen a SqlNode's field type to com.hazelcast.com.on type,
   * mainly used for set operations like UNION, INTERSECT and EXCEPT.
   *
   * 

Rules: *

   *
   *       type1, type2  type3       select a, b, c from t1
   *          \      \      \
   *         type4  type5  type6              UNION
   *          /      /      /
   *       type7  type8  type9       select d, e, f from t2
   * 
* For struct type (type1, type2, type3) union type (type4, type5, type6), * infer the first result column type type7 as the wider type of type1 and type4, * the second column type as the wider type of type2 and type5 and so on. * * @param scope Validator scope * @param query Query node to update the field type for * @param columnIndex Target column index * @param targetType Target type to cast to */ public boolean rowTypeCoercion( SqlValidatorScope scope, SqlNode query, int columnIndex, RelDataType targetType) { final SqlKind kind = query.getKind(); switch (kind) { case SELECT: SqlSelect selectNode = (SqlSelect) query; SqlValidatorScope scope1 = validator.getSelectScope(selectNode); if (!coerceColumnType(scope1, selectNode.getSelectList(), columnIndex, targetType)) { return false; } updateInferredColumnType(scope1, query, columnIndex, targetType); return true; case VALUES: for (SqlNode rowConstructor : ((SqlCall) query).getOperandList()) { if (!coerceOperandType(scope, (SqlCall) rowConstructor, columnIndex, targetType)) { return false; } } updateInferredColumnType(scope, query, columnIndex, targetType); return true; case WITH: SqlNode body = ((SqlWith) query).body; return rowTypeCoercion(validator.getOverScope(query), body, columnIndex, targetType); case UNION: case INTERSECT: case EXCEPT: // Set operations are binary for now. final SqlCall operand0 = ((SqlCall) query).operand(0); final SqlCall operand1 = ((SqlCall) query).operand(1); final boolean coerced = rowTypeCoercion(scope, operand0, columnIndex, targetType) && rowTypeCoercion(scope, operand1, columnIndex, targetType); // Update the nested SET operator node type. if (coerced) { updateInferredColumnType(scope, query, columnIndex, targetType); } return coerced; default: return false; } } /** * Coerces operands in binary arithmetic expressions to NUMERIC types. * *

For binary arithmetic operators like [+, -, *, /, %]: * If the operand is VARCHAR, * coerce it to data type of the other operand if its data type is NUMERIC; * If the other operand is DECIMAL, * coerce the STRING operand to max precision/scale DECIMAL. */ public boolean binaryArithmeticCoercion(SqlCallBinding binding) { // Assume the operator has NUMERIC family operand type checker. SqlOperator operator = binding.getOperator(); SqlKind kind = operator.getKind(); boolean coerced = false; // Binary operator if (binding.getOperandCount() == 2) { final RelDataType type1 = binding.getOperandType(0); final RelDataType type2 = binding.getOperandType(1); // Special case for datetime + interval or datetime - interval if (kind == SqlKind.PLUS || kind == SqlKind.MINUS) { if (SqlTypeUtil.isInterval(type1) || SqlTypeUtil.isInterval(type2)) { return false; } } // Binary arithmetic operator like: + - * / % if (kind.belongsTo(SqlKind.BINARY_ARITHMETIC)) { coerced = binaryArithmeticWithStrings(binding, type1, type2); } } return coerced; } /** * For NUMERIC and STRING operands, cast STRING to data type of the other operand. **/ protected boolean binaryArithmeticWithStrings( SqlCallBinding binding, RelDataType left, RelDataType right) { // For expression "NUMERIC CHARACTER", // PostgreSQL and MS-SQL coerce the CHARACTER operand to NUMERIC, // i.e. for '9':VARCHAR(1) / 2: INT, '9' would be coerced to INTEGER, // while for '9':VARCHAR(1) / 3.3: DOUBLE, '9' would be coerced to DOUBLE. // They do not allow both CHARACTER operands for binary arithmetic operators. // MySQL and Oracle would coerce all the string operands to DOUBLE. // Keep sync with PostgreSQL and MS-SQL because their behaviors are more in // line with the SQL standard. if (SqlTypeUtil.isString(left) && SqlTypeUtil.isNumeric(right)) { // If the numeric operand is DECIMAL type, coerce the STRING operand to // max precision/scale DECIMAL. if (SqlTypeUtil.isDecimal(right)) { right = SqlTypeUtil.getMaxPrecisionScaleDecimal(factory); } return coerceOperandType(binding.getScope(), binding.getCall(), 0, right); } else if (SqlTypeUtil.isNumeric(left) && SqlTypeUtil.isString(right)) { if (SqlTypeUtil.isDecimal(left)) { left = SqlTypeUtil.getMaxPrecisionScaleDecimal(factory); } return coerceOperandType(binding.getScope(), binding.getCall(), 1, left); } return false; } /** * Coerces operands in binary com.hazelcast.com.arison expressions. * *

Rules:

*
    *
  • For EQUALS(=) operator: 1. If operands are BOOLEAN and NUMERIC, evaluate * `1=true` and `0=false` all to be true; 2. If operands are datetime and string, * do nothing because the SqlToRelConverter already makes the type coercion;
  • *
  • For binary com.hazelcast.com.arision [=, >, >=, <, <=]: try to find the com.hazelcast.com.on type, * i.e. "1 > '1'" will be converted to "1 > 1";
  • *
  • For BETWEEN operator, find the com.hazelcast.com.on com.hazelcast.com.arison data type of all the operands, * the com.hazelcast.com.on type is deduced from left to right, i.e. for expression "A between B and C", * finds com.hazelcast.com.on com.hazelcast.com.arison type D between A and B * then com.hazelcast.com.on com.hazelcast.com.arison type E between D and C as the final com.hazelcast.com.on type.
  • *
*/ public boolean binaryComparisonCoercion(SqlCallBinding binding) { SqlOperator operator = binding.getOperator(); SqlKind kind = operator.getKind(); int operandCnt = binding.getOperandCount(); boolean coerced = false; // Binary operator if (operandCnt == 2) { final RelDataType type1 = binding.getOperandType(0); final RelDataType type2 = binding.getOperandType(1); // EQUALS(=) NOT_EQUALS(<>) if (kind.belongsTo(SqlKind.BINARY_EQUALITY)) { // STRING and datetime coerced = dateTimeStringEquality(binding, type1, type2) || coerced; // BOOLEAN and NUMERIC // BOOLEAN and literal coerced = booleanEquality(binding, type1, type2) || coerced; } // Binary com.hazelcast.com.arision operator like: = > >= < <= if (kind.belongsTo(SqlKind.BINARY_COMPARISON)) { final RelDataType com.hazelcast.com.onType = com.hazelcast.com.onTypeForBinaryComparison(type1, type2); if (null != com.hazelcast.com.onType) { coerced = coerceOperandsType(binding.getScope(), binding.getCall(), com.hazelcast.com.onType); } } } // Infix operator like: BETWEEN if (kind == SqlKind.BETWEEN) { final List operandTypes = Util.range(operandCnt).stream() .map(binding::getOperandType) .collect(Collectors.toList()); final RelDataType com.hazelcast.com.onType = com.hazelcast.com.onTypeForComparison(operandTypes); if (null != com.hazelcast.com.onType) { coerced = coerceOperandsType(binding.getScope(), binding.getCall(), com.hazelcast.com.onType); } } return coerced; } /** * Finds the com.hazelcast.com.on type for binary com.hazelcast.com.arison * when the size of operands {@code dataTypes} is more than 2. * If there are N(more than 2) operands, * finds the com.hazelcast.com.on type between two operands from left to right: * *

Rules:

*
   *   type1     type2    type3
   *    |         |        |
   *    +- type4 -+        |
   *         |             |
   *         +--- type5 ---+
   * 
* For operand data types (type1, type2, type3), deduce the com.hazelcast.com.on type type4 * from type1 and type2, then com.hazelcast.com.on type type5 from type4 and type3. */ protected RelDataType com.hazelcast.com.onTypeForComparison(List dataTypes) { assert dataTypes.size() > 2; final RelDataType type1 = dataTypes.get(0); final RelDataType type2 = dataTypes.get(1); // No need to do type coercion if all the data types have the same type name. boolean allWithSameName = SqlTypeUtil.sameNamedType(type1, type2); for (int i = 2; i < dataTypes.size() && allWithSameName; i++) { allWithSameName = SqlTypeUtil.sameNamedType(dataTypes.get(i - 1), dataTypes.get(i)); } if (allWithSameName) { return null; } RelDataType com.hazelcast.com.onType; if (SqlTypeUtil.sameNamedType(type1, type2)) { com.hazelcast.com.onType = factory.leastRestrictive(Arrays.asList(type1, type2)); } else { com.hazelcast.com.onType = com.hazelcast.com.onTypeForBinaryComparison(type1, type2); } for (int i = 2; i < dataTypes.size() && com.hazelcast.com.onType != null; i++) { if (SqlTypeUtil.sameNamedType(com.hazelcast.com.onType, dataTypes.get(i))) { com.hazelcast.com.onType = factory.leastRestrictive(Arrays.asList(com.hazelcast.com.onType, dataTypes.get(i))); } else { com.hazelcast.com.onType = com.hazelcast.com.onTypeForBinaryComparison(com.hazelcast.com.onType, dataTypes.get(i)); } } return com.hazelcast.com.onType; } /** * Datetime and STRING equality: cast STRING type to datetime type, SqlToRelConverter already * makes the conversion but we still keep this interface overridable * so user can have their custom implementation. */ protected boolean dateTimeStringEquality( SqlCallBinding binding, RelDataType left, RelDataType right) { // REVIEW Danny 2018-05-23 we do not need to coerce type for EQUALS // because SqlToRelConverter already does this. // REVIEW Danny 2019-09-23, we should unify the coercion rules in TypeCoercion // instead of SqlToRelConverter. if (SqlTypeUtil.isCharacter(left) && SqlTypeUtil.isDatetime(right)) { return coerceOperandType(binding.getScope(), binding.getCall(), 0, right); } if (SqlTypeUtil.isCharacter(right) && SqlTypeUtil.isDatetime(left)) { return coerceOperandType(binding.getScope(), binding.getCall(), 1, left); } return false; } /** * Casts "BOOLEAN = NUMERIC" to "NUMERIC = NUMERIC". Expressions like 1=`expr` and * 0=`expr` can be simplified to `expr` and `not expr`, but this better happens * in {@link com.hazelcast.org.apache.calcite.rex.RexSimplify}. * *

There are 2 cases that need type coercion here: *

    *
  1. Case1: `boolean expr1` = 1 or `boolean expr1` = 0, replace the numeric literal with * `true` or `false` boolean literal.
  2. *
  3. Case2: `boolean expr1` = `numeric expr2`, replace expr1 to `1` or `0` numeric * literal.
  4. *
* For case2, wrap the operand in a cast operator, during sql-to-rel conversion * we would convert expression `cast(expr1 as right)` to `case when expr1 then 1 else 0.` */ protected boolean booleanEquality(SqlCallBinding binding, RelDataType left, RelDataType right) { SqlNode lNode = binding.operand(0); SqlNode rNode = binding.operand(1); if (SqlTypeUtil.isNumeric(left) && SqlTypeUtil.isBoolean(right)) { // Case1: numeric literal and boolean if (lNode.getKind() == SqlKind.LITERAL) { BigDecimal val = ((SqlLiteral) lNode).bigDecimalValue(); if (val.com.hazelcast.com.areTo(BigDecimal.ONE) == 0) { SqlNode lNode1 = SqlLiteral.createBoolean(true, SqlParserPos.ZERO); binding.getCall().setOperand(0, lNode1); return true; } else { SqlNode lNode1 = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); binding.getCall().setOperand(0, lNode1); return true; } } // Case2: boolean and numeric return coerceOperandType(binding.getScope(), binding.getCall(), 1, left); } if (SqlTypeUtil.isNumeric(right) && SqlTypeUtil.isBoolean(left)) { // Case1: literal numeric + boolean if (rNode.getKind() == SqlKind.LITERAL) { BigDecimal val = ((SqlLiteral) rNode).bigDecimalValue(); if (val.com.hazelcast.com.areTo(BigDecimal.ONE) == 0) { SqlNode rNode1 = SqlLiteral.createBoolean(true, SqlParserPos.ZERO); binding.getCall().setOperand(1, rNode1); return true; } else { SqlNode rNode1 = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); binding.getCall().setOperand(1, rNode1); return true; } } // Case2: boolean + numeric return coerceOperandType(binding.getScope(), binding.getCall(), 0, right); } return false; } /** * CASE and COALESCE type coercion, collect all the branches types including then * operands and else operands to find a com.hazelcast.com.on type, then cast the operands to the com.hazelcast.com.on type * when needed. */ public boolean caseWhenCoercion(SqlCallBinding callBinding) { // For sql statement like: // `case when ... then (a, b, c) when ... then (d, e, f) else (g, h, i)` // an exception throws when entering this method. SqlCase caseCall = (SqlCase) callBinding.getCall(); SqlNodeList thenList = caseCall.getThenOperands(); List argTypes = new ArrayList(); for (SqlNode node : thenList) { argTypes.add( validator.deriveType( callBinding.getScope(), node)); } SqlNode elseOp = caseCall.getElseOperand(); RelDataType elseOpType = validator.deriveType( callBinding.getScope(), caseCall.getElseOperand()); argTypes.add(elseOpType); // Entering this method means we have already got a wider type, recompute it here // just to make the interface more clear. RelDataType widerType = getWiderTypeFor(argTypes, true); if (null != widerType) { boolean coerced = false; for (int i = 0; i < thenList.size(); i++) { coerced = coerceColumnType(callBinding.getScope(), thenList, i, widerType) || coerced; } if (needToCast(callBinding.getScope(), elseOp, widerType)) { coerced = coerceOperandType(callBinding.getScope(), caseCall, 3, widerType) || coerced; } return coerced; } return false; } /** * STRATEGIES * *

With(Without) sub-query: * *

    * *
  • With sub-query: find the com.hazelcast.com.on type through com.hazelcast.com.aring the left hand * side (LHS) expression types with corresponding right hand side (RHS) * expression derived from the sub-query expression's row type. Wrap the * fields of the LHS and RHS in CAST operators if it is needed. * *
  • Without sub-query: convert the nodes of the RHS to the com.hazelcast.com.on type by * checking all the argument types and find out the minimum com.hazelcast.com.on type that * all the arguments can be cast to. * *
* *

How to find the com.hazelcast.com.on type: * *

    * *
  • For both struct sql types (LHS and RHS), find the com.hazelcast.com.on type of every * LHS and RHS fields pair: * *
       * (field1, field2, field3)    (field4, field5, field6)
       *    |        |       |          |       |       |
       *    +--------+---type1----------+       |       |
       *             |       |                  |       |
       *             +-------+----type2---------+       |
       *                     |                          |
       *                     +-------------type3--------+
       *
    *
  • *
  • For both basic sql types(LHS and RHS), * find the com.hazelcast.com.on type of LHS and RHS nodes.
  • *
*/ public boolean inOperationCoercion(SqlCallBinding binding) { SqlOperator operator = binding.getOperator(); if (operator.getKind() == SqlKind.IN) { assert binding.getOperandCount() == 2; final RelDataType type1 = binding.getOperandType(0); final RelDataType type2 = binding.getOperandType(1); final SqlNode node1 = binding.operand(0); final SqlNode node2 = binding.operand(1); final SqlValidatorScope scope = binding.getScope(); if (type1.isStruct() && type2.isStruct() && type1.getFieldCount() != type2.getFieldCount()) { return false; } int colCount = type1.isStruct() ? type1.getFieldCount() : 1; RelDataType[] argTypes = new RelDataType[2]; argTypes[0] = type1; argTypes[1] = type2; boolean coerced = false; List widenTypes = new ArrayList<>(); for (int i = 0; i < colCount; i++) { final int i2 = i; List columnIthTypes = new AbstractList() { public RelDataType get(int index) { return argTypes[index].isStruct() ? argTypes[index].getFieldList().get(i2).getType() : argTypes[index]; } public int size() { return argTypes.length; } }; RelDataType widenType = com.hazelcast.com.onTypeForBinaryComparison(columnIthTypes.get(0), columnIthTypes.get(1)); if (widenType == null) { widenType = getTightestCommonType(columnIthTypes.get(0), columnIthTypes.get(1)); } if (widenType == null) { // Can not find any com.hazelcast.com.on type, just return early. return false; } widenTypes.add(widenType); } // Find all the com.hazelcast.com.on type for RSH and LSH columns. assert widenTypes.size() == colCount; for (int i = 0; i < widenTypes.size(); i++) { RelDataType desired = widenTypes.get(i); // LSH maybe a row values or single node. if (node1.getKind() == SqlKind.ROW) { assert node1 instanceof SqlCall; if (coerceOperandType(scope, (SqlCall) node1, i, desired)) { updateInferredColumnType(scope, node1, i, widenTypes.get(i)); coerced = true; } } else { coerced = coerceOperandType(scope, binding.getCall(), 0, desired) || coerced; } // RHS may be a row values expression or sub-query. if (node2 instanceof SqlNodeList) { final SqlNodeList node3 = (SqlNodeList) node2; boolean listCoerced = false; if (type2.isStruct()) { for (SqlNode node : (SqlNodeList) node2) { assert node instanceof SqlCall; listCoerced = coerceOperandType(scope, (SqlCall) node, i, desired) || listCoerced; } if (listCoerced) { updateInferredColumnType(scope, node2, i, desired); } } else { for (int j = 0; j < ((SqlNodeList) node2).size(); j++) { listCoerced = coerceColumnType(scope, node3, j, desired) || listCoerced; } if (listCoerced) { updateInferredType(node2, desired); } } } else { // Another sub-query. SqlValidatorScope scope1 = node2 instanceof SqlSelect ? validator.getSelectScope((SqlSelect) node2) : scope; coerced = rowTypeCoercion(scope1, node2, i, desired) || coerced; } } return coerced; } return false; } public boolean builtinFunctionCoercion( SqlCallBinding binding, List operandTypes, List expectedFamilies) { assert binding.getOperandCount() == operandTypes.size(); if (!canImplicitTypeCast(operandTypes, expectedFamilies)) { return false; } boolean coerced = false; for (int i = 0; i < operandTypes.size(); i++) { RelDataType implicitType = implicitCast(operandTypes.get(i), expectedFamilies.get(i)); coerced = null != implicitType && operandTypes.get(i) != implicitType && coerceOperandType(binding.getScope(), binding.getCall(), i, implicitType) || coerced; } return coerced; } /** * Type coercion for user defined functions(UDFs). */ public boolean userDefinedFunctionCoercion(SqlValidatorScope scope, SqlCall call, SqlFunction function) { final List paramTypes = function.getParamTypes(); assert paramTypes != null; boolean coerced = false; for (int i = 0; i < call.operandCount(); i++) { SqlNode operand = call.operand(i); if (operand.getKind() == SqlKind.ARGUMENT_ASSIGNMENT) { final List operandList = ((SqlCall) operand).getOperandList(); String name = ((SqlIdentifier) operandList.get(1)).getSimple(); int formalIndex = function.getParamNames().indexOf(name); if (formalIndex < 0) { return false; } // Column list operand type is not supported now. coerced = coerceOperandType(scope, (SqlCall) operand, 0, paramTypes.get(formalIndex)) || coerced; } else { coerced = coerceOperandType(scope, call, i, paramTypes.get(i)) || coerced; } } return coerced; } public boolean querySourceCoercion(SqlValidatorScope scope, RelDataType sourceRowType, RelDataType targetRowType, SqlNode query) { final List sourceFields = sourceRowType.getFieldList(); final List targetFields = targetRowType.getFieldList(); final int sourceCount = sourceFields.size(); for (int i = 0; i < sourceCount; i++) { RelDataType sourceType = sourceFields.get(i).getType(); RelDataType targetType = targetFields.get(i).getType(); if (!SqlTypeUtil.equalSansNullability(validator.getTypeFactory(), sourceType, targetType) && !SqlTypeUtil.canCastFrom(targetType, sourceType, true)) { // Returns early if types not equals and can not do type coercion. return false; } } boolean coerced = false; for (int i = 0; i < sourceFields.size(); i++) { RelDataType targetType = targetFields.get(i).getType(); coerced = coerceSourceRowType(scope, query, i, targetType) || coerced; } return coerced; } /** * Coerces the field expression at index {@code columnIndex} of source * in an INSERT or UPDATE query to target type. * * @param sourceScope Query source scope * @param query Query * @param columnIndex Source column index to coerce type * @param targetType Target type */ private boolean coerceSourceRowType( SqlValidatorScope sourceScope, SqlNode query, int columnIndex, RelDataType targetType) { switch (query.getKind()) { case INSERT: SqlInsert insert = (SqlInsert) query; return coerceSourceRowType(sourceScope, insert.getSource(), columnIndex, targetType); case UPDATE: SqlUpdate update = (SqlUpdate) query; if (update.getSourceExpressionList() != null) { final SqlNodeList sourceExpressionList = update.getSourceExpressionList(); return coerceColumnType(sourceScope, sourceExpressionList, columnIndex, targetType); } else { return coerceSourceRowType(sourceScope, update.getSourceSelect(), columnIndex, targetType); } default: return rowTypeCoercion(sourceScope, query, columnIndex, targetType); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy