Skip to content

Commit c5f646e

Browse files
authored
KE-45007 incorrect precision when multiplying DECIMAL, INTEGER constants (#368)
1 parent 1cf5c97 commit c5f646e

File tree

3 files changed

+135
-15
lines changed

3 files changed

+135
-15
lines changed

core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@
2222
import org.apache.calcite.rel.type.RelDataTypeImpl;
2323
import org.apache.calcite.rel.type.RelDataTypeSystem;
2424
import org.apache.calcite.rel.type.RelProtoDataType;
25+
import org.apache.calcite.rex.RexCallBinding;
26+
import org.apache.calcite.rex.RexLiteral;
2527
import org.apache.calcite.sql.ExplicitOperatorBinding;
2628
import org.apache.calcite.sql.SqlCall;
2729
import org.apache.calcite.sql.SqlCallBinding;
2830
import org.apache.calcite.sql.SqlCollation;
2931
import org.apache.calcite.sql.SqlKind;
3032
import org.apache.calcite.sql.SqlNodeList;
33+
import org.apache.calcite.sql.SqlNumericLiteral;
3134
import org.apache.calcite.sql.SqlOperatorBinding;
3235
import org.apache.calcite.sql.SqlUtil;
3336
import org.apache.calcite.sql.validate.SqlValidatorNamespace;
3437
import org.apache.calcite.util.Glossary;
38+
import org.apache.calcite.util.Pair;
39+
import org.apache.calcite.util.SqlNodeUtils;
3540
import org.apache.calcite.util.Util;
3641

3742
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
@@ -54,19 +59,23 @@ public abstract class ReturnTypes {
5459
private ReturnTypes() {
5560
}
5661

57-
/** Creates a return-type inference that applies a rule then a sequence of
62+
/**
63+
* Creates a return-type inference that applies a rule then a sequence of
5864
* rules, returning the first non-null result.
5965
*
60-
* @see SqlReturnTypeInference#orElse(SqlReturnTypeInference) */
66+
* @see SqlReturnTypeInference#orElse(SqlReturnTypeInference)
67+
*/
6168
public static SqlReturnTypeInferenceChain chain(
6269
SqlReturnTypeInference... rules) {
6370
return new SqlReturnTypeInferenceChain(rules);
6471
}
6572

66-
/** Creates a return-type inference that applies a rule then a sequence of
73+
/**
74+
* Creates a return-type inference that applies a rule then a sequence of
6775
* transforms.
6876
*
69-
* @see SqlReturnTypeInference#andThen(SqlTypeTransform) */
77+
* @see SqlReturnTypeInference#andThen(SqlTypeTransform)
78+
*/
7079
public static SqlTypeTransformCascade cascade(SqlReturnTypeInference rule,
7180
SqlTypeTransform... transforms) {
7281
return new SqlTypeTransformCascade(rule, transforms);
@@ -101,21 +110,25 @@ public static ExplicitReturnTypeInference explicit(SqlTypeName typeName,
101110
return explicit(RelDataTypeImpl.proto(typeName, precision, false));
102111
}
103112

104-
/** Returns a return-type inference that first transforms a binding and
113+
/**
114+
* Returns a return-type inference that first transforms a binding and
105115
* then applies an inference.
106116
*
107-
* <p>{@link #stripOrderBy} is an example of {@code bindingTransform}. */
117+
* <p>{@link #stripOrderBy} is an example of {@code bindingTransform}.
118+
*/
108119
public static SqlReturnTypeInference andThen(
109120
UnaryOperator<SqlOperatorBinding> bindingTransform,
110121
SqlReturnTypeInference typeInference) {
111122
return opBinding ->
112123
typeInference.inferReturnType(bindingTransform.apply(opBinding));
113124
}
114125

115-
/** Converts a binding of {@code FOO(x, y ORDER BY z)}
126+
/**
127+
* Converts a binding of {@code FOO(x, y ORDER BY z)}
116128
* or {@code FOO(x, y ORDER BY z SEPARATOR s)}
117129
* to a binding of {@code FOO(x, y)}.
118-
* Used for {@code STRING_AGG} and {@code GROUP_CONCAT}. */
130+
* Used for {@code STRING_AGG} and {@code GROUP_CONCAT}.
131+
*/
119132
public static SqlOperatorBinding stripOrderBy(
120133
SqlOperatorBinding operatorBinding) {
121134
if (operatorBinding instanceof SqlCallBinding) {
@@ -367,7 +380,7 @@ public static SqlCall stripSeparator(SqlCall call) {
367380
* Type-inference strategy whereby the result type of a call is a Char.
368381
*/
369382
public static final SqlReturnTypeInference CHAR =
370-
explicit(SqlTypeName.CHAR);
383+
explicit(SqlTypeName.CHAR);
371384

372385
/**
373386
* Type-inference strategy whereby the result type of a call is a nullable
@@ -686,11 +699,69 @@ public static SqlCall stripSeparator(SqlCall call) {
686699
*/
687700
public static final SqlReturnTypeInference DECIMAL_PRODUCT = opBinding -> {
688701
RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
689-
RelDataType type1 = opBinding.getOperandType(0);
690-
RelDataType type2 = opBinding.getOperandType(1);
691-
return typeFactory.getTypeSystem().deriveDecimalMultiplyType(typeFactory, type1, type2);
702+
Pair<RelDataType, RelDataType> result = getDecimalMultiplyBindingType(opBinding, typeFactory);
703+
return typeFactory.getTypeSystem().deriveDecimalMultiplyType(typeFactory, result.left,
704+
result.right);
692705
};
693706

707+
private static Pair<RelDataType, RelDataType> getDecimalMultiplyBindingType(SqlOperatorBinding opBinding,
708+
RelDataTypeFactory typeFactory) {
709+
if (opBinding instanceof SqlCallBinding) {
710+
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 0);
711+
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (SqlCallBinding) opBinding, 1);
712+
return Pair.of(type1, type2);
713+
}
714+
if (opBinding instanceof RexCallBinding) {
715+
RelDataType type1 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 0);
716+
RelDataType type2 = createDecimalTypeOrDefault(typeFactory, (RexCallBinding) opBinding, 1);
717+
return Pair.of(type1, type2);
718+
}
719+
return Pair.of(opBinding.getOperandType(0), opBinding.getOperandType(1));
720+
}
721+
722+
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
723+
RexCallBinding opBinding, int ordinal) {
724+
RelDataType defaultType = opBinding.getOperandType(ordinal);
725+
try {
726+
if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
727+
return defaultType;
728+
}
729+
730+
RexLiteral literal = (RexLiteral) opBinding.operands().get(ordinal);
731+
732+
if (SqlNodeUtils.isDecimalConstant(literal)) {
733+
return defaultType;
734+
}
735+
736+
Long value = literal.getValueAs(Long.class);
737+
int length = (int) Math.floor(Math.log10(Math.abs(value))) + 1;
738+
return typeFactory.createSqlType(SqlTypeName.DECIMAL, length, 0);
739+
} catch (IllegalArgumentException | AssertionError e) {
740+
return defaultType;
741+
}
742+
}
743+
744+
private static RelDataType createDecimalTypeOrDefault(RelDataTypeFactory typeFactory,
745+
SqlCallBinding opBinding, int ordinal) {
746+
RelDataType defaultType = opBinding.getOperandType(ordinal);
747+
try {
748+
if (!SqlNodeUtils.isNumericLiteral(opBinding, ordinal)) {
749+
return defaultType;
750+
}
751+
752+
SqlNumericLiteral literal =
753+
(SqlNumericLiteral) opBinding.getCall().getOperandList().get(ordinal);
754+
// When parsing into a **SqlNode**, integers are converted to **DECIMAL** type.
755+
if (SqlNodeUtils.isDecimalConstant(literal)) {
756+
return typeFactory.createSqlType(SqlTypeName.DECIMAL, literal.getPrec(),
757+
literal.getScale());
758+
}
759+
return defaultType;
760+
} catch (IllegalArgumentException e) {
761+
return defaultType;
762+
}
763+
}
764+
694765
/**
695766
* Same as {@link #DECIMAL_PRODUCT} but returns with nullability if any of
696767
* the operands is nullable by using
@@ -746,7 +817,7 @@ public static SqlCall stripSeparator(SqlCall call) {
746817
* {@link org.apache.calcite.sql.type.SqlTypeTransforms#TO_NULLABLE}.
747818
*/
748819
public static final SqlReturnTypeInference DOUBLE_QUOTIENT_NULLABLE =
749-
DOUBLE_QUOTIENT.andThen(SqlTypeTransforms.TO_NULLABLE);
820+
DOUBLE_QUOTIENT.andThen(SqlTypeTransforms.TO_NULLABLE);
750821

751822
/**
752823
* Type-inference strategy whereby the result type of a call is
@@ -933,7 +1004,7 @@ public static SqlCall stripSeparator(SqlCall call) {
9331004
List<RelDataType> operandTypes = opBinding.collectOperandTypes();
9341005
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
9351006
final RelDataTypeSystem typeSystem = typeFactory.getTypeSystem();
936-
for (RelDataType operandType: operandTypes) {
1007+
for (RelDataType operandType : operandTypes) {
9371008
int operandPrecision = operandType.getPrecision();
9381009
amount = (long) operandPrecision + amount;
9391010
if (operandPrecision == RelDataType.PRECISION_NOT_SPECIFIED) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.util;
18+
19+
import org.apache.calcite.rex.RexLiteral;
20+
import org.apache.calcite.sql.SqlNumericLiteral;
21+
import org.apache.calcite.sql.SqlOperatorBinding;
22+
import org.apache.calcite.sql.type.SqlTypeName;
23+
import org.apache.calcite.sql.type.SqlTypeUtil;
24+
25+
public class SqlNodeUtils {
26+
27+
private SqlNodeUtils() {
28+
}
29+
30+
public static boolean isDecimalConstant(SqlNumericLiteral literal) {
31+
SqlTypeName typeName = literal.getTypeName();
32+
33+
return typeName == SqlTypeName.DECIMAL;
34+
}
35+
36+
public static boolean isNumericLiteral(SqlOperatorBinding binding, int ordinal) {
37+
return binding.isOperandLiteral(ordinal, false) &&
38+
SqlTypeUtil.isNumeric(binding.getOperandType(ordinal));
39+
}
40+
41+
public static boolean isDecimalConstant(RexLiteral literal) {
42+
SqlTypeName typeName = literal.getType().getSqlTypeName();
43+
44+
return typeName == SqlTypeName.DECIMAL
45+
|| typeName == SqlTypeName.DOUBLE
46+
|| typeName == SqlTypeName.REAL
47+
|| typeName == SqlTypeName.FLOAT;
48+
}
49+
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ systemProp.org.gradle.internal.publish.checksums.insecure=true
2727
# This is version for Calcite itself
2828
# Note: it should not include "-SNAPSHOT" as it is automatically added by build.gradle.kts
2929
# Release version can be generated by using -Prelease or -Prc=<int> arguments
30-
calcite.version=1.30.0-kylin-5.x-r5
30+
calcite.version=1.30.0-kylin-5.x-r6
3131
# This is a version to be used from Maven repository. It can be overridden by localAvatica below
3232
calcite.avatica.version=1.22.0
3333

0 commit comments

Comments
 (0)