com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercionImpl Maven / Gradle / Ivy
/*
* 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:
*
* - Case1: `boolean expr1` = 1 or `boolean expr1` = 0, replace the numeric literal with
* `true` or `false` boolean literal.
* - Case2: `boolean expr1` = `numeric expr2`, replace expr1 to `1` or `0` numeric
* literal.
*
* 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);
}
}
}