diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 1cac3913ea84..7f358521573b 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -2332,6 +2332,8 @@ private static class BinaryImplementor extends AbstractRexCallImplementor { final Primitive primitive = Primitive.ofBoxOr(type0); if (primitive == null || type1 == BigDecimal.class + || backupMethodName.equals("plus") + && isNumberOrString(type0) && isNumberOrString(type1) || COMPARISON_OPERATORS.contains(op) && !COMP_OP_TYPES.contains(primitive)) { return Expressions.call(SqlFunctions.class, backupMethodName, @@ -2352,6 +2354,15 @@ private static class BinaryImplementor extends AbstractRexCallImplementor { argValueList.get(0), argValueList.get(1)); } + // see https://olapio.atlassian.net/browse/KE-42243 + private boolean isNumberOrString(Type type) { + if (type == String.class || type == BigDecimal.class) { + return true; + } + Primitive primitive = Primitive.ofBoxOr(type); + return primitive != null && Number.class.isAssignableFrom(primitive.boxClass); + } + /** Returns whether any of a call's operands have ANY type. */ private static boolean anyAnyOperands(RexCall call) { for (RexNode operand : call.operands) { diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index c23aa06e04f4..cc0e54a73564 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -1006,30 +1006,35 @@ public static int plus(int b0, int b1) { } // see https://olapio.atlassian.net/browse/KE-42084 - public static Double plus(String s0, String s1) { + // see https://olapio.atlassian.net/browse/KE-42243 + public static BigDecimal plus(String s0, String s1) { try { - return plus(toBigDecimal(s0), toBigDecimal(s1)).doubleValue(); + return plus(toBigDecimal(s0), toBigDecimal(s1)); } catch (NumberFormatException ignored) { return null; } } - public static Double plus(String s0, BigDecimal b1) { + public static BigDecimal plus(String s0, BigDecimal b1) { try { - return plus(toBigDecimal(s0), b1).doubleValue(); + return plus(toBigDecimal(s0), b1); } catch (NumberFormatException ignored) { return null; } } - public static Double plus(String s0, Number b1) { + public static BigDecimal plus(String s0, Number b1) { return plus(s0, toBigDecimal(b1)); } - public static Double plus(Number b1, String s0) { + public static BigDecimal plus(Number b1, String s0) { return plus(s0, toBigDecimal(b1)); } + public static BigDecimal plus(Number b1, Number b2) { + return plus(toBigDecimal(b1), toBigDecimal(b2)); + } + /** SQL + operator applied to Object values (at least one operand * has ANY type; either may be null). */ public static @PolyNull Object plusAny(@PolyNull Object b0, @@ -2213,7 +2218,8 @@ public static BigDecimal toBigDecimal(String s) { public static BigDecimal toBigDecimal(Number number) { // There are some values of "long" that cannot be represented as "double". // Not so "int". If it isn't a long, go straight to double. - return number instanceof BigDecimal ? (BigDecimal) number + return number == null ? null + : number instanceof BigDecimal ? (BigDecimal) number : number instanceof BigInteger ? new BigDecimal((BigInteger) number) : number instanceof Long ? new BigDecimal(number.longValue()) : new BigDecimal(number.doubleValue()); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java index ce562a9a5167..2a108524e3bf 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java @@ -173,6 +173,12 @@ protected boolean binaryArithmeticWithStrings( SqlCallBinding binding, RelDataType left, RelDataType right) { + // see https://olapio.atlassian.net/browse/KE-42243 + // Calcite's implicit conversion may fail when the operator is "plus" and the left and right + // sides are numeric and string types respectively, which will not be supported in Kylin, + // and the logic is commented out for now. + return false; + // 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, @@ -183,20 +189,20 @@ protected boolean binaryArithmeticWithStrings( // 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; +// 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; } /**