asolimando commented on code in PR #4557:
URL: https://github.com/apache/calcite/pull/4557#discussion_r2381053808


##########
core/src/test/java/org/apache/calcite/rex/RexLosslessCastTest.java:
##########
@@ -132,22 +137,305 @@ class RexLosslessCastTest extends RexProgramTestBase {
                 varcharType, rexBuilder.makeInputRef(intType, 0))), is(true));
   }
 
+  @Test void testLosslessCastIntegerToApproximate() {
+    final RelDataType tinyIntType = 
typeFactory.createSqlType(SqlTypeName.TINYINT);
+    final RelDataType smallIntType = 
typeFactory.createSqlType(SqlTypeName.SMALLINT);
+    final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    final RelDataType bigIntType = 
typeFactory.createSqlType(SqlTypeName.BIGINT);
+    final RelDataType doubleType = 
typeFactory.createSqlType(SqlTypeName.DOUBLE);
+    final RelDataType realType = typeFactory.createSqlType(SqlTypeName.REAL);
+
+    // Positive: tiny/small/int -> double
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(doubleType,
+        rexBuilder.makeInputRef(tinyIntType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(doubleType,
+        rexBuilder.makeInputRef(smallIntType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(doubleType,
+        rexBuilder.makeInputRef(intType, 0))), is(true));
+
+    // Negative: bigint -> double is not guaranteed to be exact
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(doubleType,
+        rexBuilder.makeInputRef(bigIntType, 0))), is(false));
+
+    // Positive: tiny/small -> real
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(realType,
+        rexBuilder.makeInputRef(tinyIntType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(realType,
+        rexBuilder.makeInputRef(smallIntType, 0))), is(true));
+
+    // Negative: int/bigint -> real not guaranteed exact
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(realType,
+        rexBuilder.makeInputRef(intType, 0))), is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(realType,
+        rexBuilder.makeInputRef(bigIntType, 0))), is(false));
+  }
+
+  @Test void testLosslessCastIntegerToDecimal() {
+    final RelDataType tinyIntType = 
typeFactory.createSqlType(SqlTypeName.TINYINT);
+    final RelDataType smallIntType = 
typeFactory.createSqlType(SqlTypeName.SMALLINT);
+    final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    final RelDataType bigIntType = 
typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+    final RelDataType decPrec3Scale0  = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 3, 0);
+    final RelDataType decPrec5Scale0  = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 0);
+    final RelDataType decPrec9Scale0  = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 9, 0);
+    final RelDataType decPrec10Scale0 = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 10, 0);
+    final RelDataType decPrec18Scale0 = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 18, 0);
+    final RelDataType decPrec19Scale0 = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 19, 0);
+    final RelDataType decPrec4Scale1  = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 4, 1);
+    final RelDataType decPrec5Scale1  = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 1);
+    final RelDataType decPrec12Scale2 = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 12, 2);
+
+    // Positive: integer digits "precision - scale" is large enough
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec3Scale0,
+        rexBuilder.makeInputRef(tinyIntType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec5Scale0,
+        rexBuilder.makeInputRef(smallIntType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec10Scale0,
+        rexBuilder.makeInputRef(intType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec19Scale0,
+        rexBuilder.makeInputRef(bigIntType, 0))), is(true));
+
+    // Negative: not enough integer digits
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec9Scale0,
+        rexBuilder.makeInputRef(intType, 0))), is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec18Scale0,
+        rexBuilder.makeInputRef(bigIntType, 0))), is(false));
+
+    // Non-zero scale is still lossless if "precision - scale" covers integer 
digits
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec12Scale2,
+        rexBuilder.makeInputRef(intType, 0))), is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec4Scale1,
+        rexBuilder.makeInputRef(smallIntType, 0))), is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(decPrec5Scale1,
+        rexBuilder.makeInputRef(tinyIntType, 0))), is(true));
+  }
+
+  @Test void testLosslessCastUnsignedAndSignedIntegers() {
+    final RelDataType uTinyIntType = 
typeFactory.createSqlType(SqlTypeName.UTINYINT);
+    final RelDataType uIntType = 
typeFactory.createSqlType(SqlTypeName.UINTEGER);
+    final RelDataType uBigIntType = 
typeFactory.createSqlType(SqlTypeName.UBIGINT);
+
+    final RelDataType tinyIntType = 
typeFactory.createSqlType(SqlTypeName.TINYINT);
+    final RelDataType smallIntType = 
typeFactory.createSqlType(SqlTypeName.SMALLINT);
+    final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    final RelDataType bigIntType = 
typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+    // unsigned -> signed of same width should be considered lossy
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(tinyIntType, 
rexBuilder.makeInputRef(uTinyIntType, 0))),
+        is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(intType, rexBuilder.makeInputRef(uIntType, 
0))),
+        is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(bigIntType, 
rexBuilder.makeInputRef(uBigIntType, 0))),
+        is(false));
+
+    // unsigned -> wider signed is safe
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(smallIntType, 
rexBuilder.makeInputRef(uTinyIntType, 0))),
+        is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(bigIntType, rexBuilder.makeInputRef(uIntType, 
0))),
+        is(true));
+
+    // signed -> unsigned stays conservative for now
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(uIntType, rexBuilder.makeInputRef(intType, 
0))),
+        is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(uBigIntType, 
rexBuilder.makeInputRef(bigIntType, 0))),
+        is(false));
+
+    final RelDataType dec9 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 9, 
0);
+    final RelDataType dec10 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
10, 0);
+    final RelDataType dec10Scale1 = 
typeFactory.createSqlType(SqlTypeName.DECIMAL, 10, 1);
+    final RelDataType dec19 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
19, 0);
+
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(dec10, rexBuilder.makeInputRef(uIntType, 0))),
+        is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(dec9, rexBuilder.makeInputRef(uIntType, 0))),
+        is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(uBigIntType, rexBuilder.makeInputRef(dec19, 
0))),
+        is(false));
+
+    // DECIMAL -> integer: requires scale 0 and fitting range
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(intType, rexBuilder.makeInputRef(dec9, 0))),
+        is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(intType, rexBuilder.makeInputRef(dec10, 0))),
+        is(false));
+    assertThat(
+        RexUtil.isLosslessCast(
+            rexBuilder.makeCast(intType, rexBuilder.makeInputRef(dec10Scale1, 
0))),
+        is(false));
+  }
+
+  @Test void testLosslessCastDecimalToApproximate() {
+    final RelDataType realType   = typeFactory.createSqlType(SqlTypeName.REAL);
+    final RelDataType doubleType = 
typeFactory.createSqlType(SqlTypeName.DOUBLE);
+
+    // DECIMAL(s = 0) -> APPROX: lossless iff precision <= target digits
+    final RelDataType dec7_0 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
7, 0);
+    final RelDataType dec8_0 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
8, 0);
+    final RelDataType dec15_0 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
15, 0);
+    final RelDataType dec16_0 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
16, 0);
+
+    // real: 7 digits in default type system
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(realType, rexBuilder.makeInputRef(dec7_0, 0))), 
is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(realType, rexBuilder.makeInputRef(dec8_0, 0))), 
is(false));
+
+    // double: 15 digits in default type system
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(doubleType, rexBuilder.makeInputRef(dec15_0, 0))), 
is(true));
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(doubleType, rexBuilder.makeInputRef(dec16_0, 0))), 
is(false));
+
+    // DECIMAL with scale > 0 -> APPROX: conservative false
+    final RelDataType dec10_2 = typeFactory.createSqlType(SqlTypeName.DECIMAL, 
10, 2);
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(doubleType, rexBuilder.makeInputRef(dec10_2, 0))), 
is(false));
+  }
+
+  @Test void testLosslessCastApproximateToApproximate() {
+    final RelDataType realType = typeFactory.createSqlType(SqlTypeName.REAL);
+    final RelDataType floatType = typeFactory.createSqlType(SqlTypeName.FLOAT);
+    final RelDataType doubleType = 
typeFactory.createSqlType(SqlTypeName.DOUBLE);
+
+    // real -> double: target has >= digits
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(doubleType, rexBuilder.makeInputRef(realType, 
0))), is(true));
+    // double -> real: target has fewer digits
+    assertThat(
+        RexUtil.isLosslessCast(
+        rexBuilder.makeCast(realType, rexBuilder.makeInputRef(doubleType, 
0))), is(false));
+    // real -> float: under default type system, float has >= digits than real
+    assertThat(

Review Comment:
   I totally agree, I did some intermediate tests and forgot to clean this up, 
thanks for the suggestion



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to