diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java index 54efce3b8521..cd99de7b4a9a 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java @@ -22,16 +22,21 @@ import org.apache.calcite.rel.type.RelDataTypeImpl; import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.RelProtoDataType; +import org.apache.calcite.rex.RexCallBinding; +import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.sql.ExplicitOperatorBinding; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlCallBinding; import org.apache.calcite.sql.SqlCollation; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlNumericLiteral; import org.apache.calcite.sql.SqlOperatorBinding; import org.apache.calcite.sql.SqlUtil; import org.apache.calcite.sql.validate.SqlValidatorNamespace; import org.apache.calcite.util.Glossary; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.SqlNodeUtils; import org.apache.calcite.util.Util; import org.apache.kylin.guava30.shaded.common.base.Preconditions; @@ -54,19 +59,23 @@ public abstract class ReturnTypes { private ReturnTypes() { } - /** Creates a return-type inference that applies a rule then a sequence of + /** + * Creates a return-type inference that applies a rule then a sequence of * rules, returning the first non-null result. * - * @see SqlReturnTypeInference#orElse(SqlReturnTypeInference) */ + * @see SqlReturnTypeInference#orElse(SqlReturnTypeInference) + */ public static SqlReturnTypeInferenceChain chain( SqlReturnTypeInference... rules) { return new SqlReturnTypeInferenceChain(rules); } - /** Creates a return-type inference that applies a rule then a sequence of + /** + * Creates a return-type inference that applies a rule then a sequence of * transforms. * - * @see SqlReturnTypeInference#andThen(SqlTypeTransform) */ + * @see SqlReturnTypeInference#andThen(SqlTypeTransform) + */ public static SqlTypeTransformCascade cascade(SqlReturnTypeInference rule, SqlTypeTransform... transforms) { return new SqlTypeTransformCascade(rule, transforms); @@ -101,10 +110,12 @@ public static ExplicitReturnTypeInference explicit(SqlTypeName typeName, return explicit(RelDataTypeImpl.proto(typeName, precision, false)); } - /** Returns a return-type inference that first transforms a binding and + /** + * Returns a return-type inference that first transforms a binding and * then applies an inference. * - *

{@link #stripOrderBy} is an example of {@code bindingTransform}. */ + *

{@link #stripOrderBy} is an example of {@code bindingTransform}. + */ public static SqlReturnTypeInference andThen( UnaryOperator bindingTransform, SqlReturnTypeInference typeInference) { @@ -112,10 +123,12 @@ public static SqlReturnTypeInference andThen( typeInference.inferReturnType(bindingTransform.apply(opBinding)); } - /** Converts a binding of {@code FOO(x, y ORDER BY z)} + /** + * Converts a binding of {@code FOO(x, y ORDER BY z)} * or {@code FOO(x, y ORDER BY z SEPARATOR s)} * to a binding of {@code FOO(x, y)}. - * Used for {@code STRING_AGG} and {@code GROUP_CONCAT}. */ + * Used for {@code STRING_AGG} and {@code GROUP_CONCAT}. + */ public static SqlOperatorBinding stripOrderBy( SqlOperatorBinding operatorBinding) { if (operatorBinding instanceof SqlCallBinding) { @@ -367,7 +380,7 @@ public static SqlCall stripSeparator(SqlCall call) { * Type-inference strategy whereby the result type of a call is a Char. */ public static final SqlReturnTypeInference CHAR = - explicit(SqlTypeName.CHAR); + explicit(SqlTypeName.CHAR); /** * Type-inference strategy whereby the result type of a call is a nullable @@ -686,11 +699,69 @@ public static SqlCall stripSeparator(SqlCall call) { */ public static final SqlReturnTypeInference DECIMAL_PRODUCT = opBinding -> { RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); - RelDataType type1 = opBinding.getOperandType(0); - RelDataType type2 = opBinding.getOperandType(1); - return typeFactory.getTypeSystem().deriveDecimalMultiplyType(typeFactory, type1, type2); + Pair result = getDecimalMultiplyBindingType(opBinding, typeFactory); + return typeFactory.getTypeSystem().deriveDecimalMultiplyType(typeFactory, result.left, + result.right); }; + private static Pair getDecimalMultiplyBindingType(SqlOperatorBinding opBinding, + RelDataTypeFactory typeFactory) { + if (opBinding instanceof SqlCallBinding) { + RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 0); + RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 1); + return Pair.of(type1, type2); + } + if (opBinding instanceof RexCallBinding) { + RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 0); + RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 1); + return Pair.of(type1, type2); + } + return Pair.of(opBinding.getOperandType(0), opBinding.getOperandType(1)); + } + + private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory, + RexCallBinding opBinding, int ordinal) { + RelDataType defaultType = opBinding.getOperandType(ordinal); + try { + if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) { + return defaultType; + } + + RexLiteral literal = (RexLiteral) opBinding.operands().get(ordinal); + + if (SqlNodeUtils.isDecimalConstant(literal)) { + return defaultType; + } + + Long value = literal.getValueAs(Long.class); + int length = (int) Math.floor(Math.log10(Math.abs(value))) + 1; + return typeFactory.createSqlType(SqlTypeName.DECIMAL, length, 0); + } catch (IllegalArgumentException | AssertionError e) { + return defaultType; + } + } + + private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory, + SqlCallBinding opBinding, int ordinal) { + RelDataType defaultType = opBinding.getOperandType(ordinal); + try { + if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) { + return defaultType; + } + + SqlNumericLiteral literal = + (SqlNumericLiteral) opBinding.getCall().getOperandList().get(ordinal); + // When parsing into a **SqlNode**, integers are converted to **DECIMAL** type. + if (SqlNodeUtils.isDecimalConstant(literal)) { + return typeFactory.createSqlType(SqlTypeName.DECIMAL, literal.getPrec(), + literal.getScale()); + } + return defaultType; + } catch (IllegalArgumentException e) { + return defaultType; + } + } + /** * Same as {@link #DECIMAL_PRODUCT} but returns with nullability if any of * the operands is nullable by using @@ -746,7 +817,7 @@ public static SqlCall stripSeparator(SqlCall call) { * {@link org.apache.calcite.sql.type.SqlTypeTransforms#TO_NULLABLE}. */ public static final SqlReturnTypeInference DOUBLE_QUOTIENT_NULLABLE = - DOUBLE_QUOTIENT.andThen(SqlTypeTransforms.TO_NULLABLE); + DOUBLE_QUOTIENT.andThen(SqlTypeTransforms.TO_NULLABLE); /** * Type-inference strategy whereby the result type of a call is @@ -933,7 +1004,7 @@ public static SqlCall stripSeparator(SqlCall call) { List operandTypes = opBinding.collectOperandTypes(); final RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); final RelDataTypeSystem typeSystem = typeFactory.getTypeSystem(); - for (RelDataType operandType: operandTypes) { + for (RelDataType operandType : operandTypes) { int operandPrecision = operandType.getPrecision(); amount = (long) operandPrecision + amount; if (operandPrecision == RelDataType.PRECISION_NOT_SPECIFIED) { diff --git a/core/src/main/java/org/apache/calcite/util/SqlNodeUtils.java b/core/src/main/java/org/apache/calcite/util/SqlNodeUtils.java new file mode 100644 index 000000000000..935c3b7246b4 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/SqlNodeUtils.java @@ -0,0 +1,49 @@ +/* + * 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 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 org.apache.calcite.util; + +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.sql.SqlNumericLiteral; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.type.SqlTypeUtil; + +public class SqlNodeUtils { + + private SqlNodeUtils() { + } + + public static boolean isDecimalConstant(SqlNumericLiteral literal) { + SqlTypeName typeName = literal.getTypeName(); + + return typeName == SqlTypeName.DECIMAL; + } + + public static boolean isNumericLiteral(SqlOperatorBinding binding, int ordinal) { + return binding.isOperandLiteral(ordinal, false) && + SqlTypeUtil.isNumeric(binding.getOperandType(ordinal)); + } + + public static boolean isDecimalConstant(RexLiteral literal) { + SqlTypeName typeName = literal.getType().getSqlTypeName(); + + return typeName == SqlTypeName.DECIMAL + || typeName == SqlTypeName.DOUBLE + || typeName == SqlTypeName.REAL + || typeName == SqlTypeName.FLOAT; + } +} diff --git a/gradle.properties b/gradle.properties index 413426bef7d1..354bf93f31ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,7 +27,7 @@ systemProp.org.gradle.internal.publish.checksums.insecure=true # This is version for Calcite itself # Note: it should not include "-SNAPSHOT" as it is automatically added by build.gradle.kts # Release version can be generated by using -Prelease or -Prc= arguments -calcite.version=1.30.0-kylin-5.x-r5 +calcite.version=1.30.0-kylin-5.x-r6 # This is a version to be used from Maven repository. It can be overridden by localAvatica below calcite.avatica.version=1.22.0