[CALCITE-1760] Implement utility method to identify lossless casts
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/a2bd49c3 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/a2bd49c3 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/a2bd49c3 Branch: refs/heads/master Commit: a2bd49c32b6b08f92cf92fd603f8448684044431 Parents: 27ca310 Author: Jesus Camacho Rodriguez <[email protected]> Authored: Tue Apr 25 17:11:04 2017 +0100 Committer: Jesus Camacho Rodriguez <[email protected]> Committed: Wed Apr 26 20:03:11 2017 +0100 ---------------------------------------------------------------------- .../java/org/apache/calcite/rex/RexUtil.java | 43 +++++++++ .../org/apache/calcite/test/RexProgramTest.java | 99 ++++++++++++++++++++ 2 files changed, 142 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/a2bd49c3/core/src/main/java/org/apache/calcite/rex/RexUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java index 95ab2c2..26c1268 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java +++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java @@ -35,6 +35,7 @@ import org.apache.calcite.sql.SqlAggFunction; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.type.SqlTypeFamily; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.calcite.sql.validate.SqlValidatorUtil; @@ -1365,6 +1366,48 @@ public class RexUtil { } } + /** + * Returns whether the input is a 'lossless' casts, i.e., a cast from which the original + * value of the field can be certainly recovered. + * + * <p>For instance, int -> bigint is true (as you can cast back to int without loss of + * information), however bigint -> int is false. + * + * <p>The implementation of this method does not return false positives. However, it is + * not complete. + */ + public static boolean isLosslessCast(RexNode node) { + if (!node.isA(SqlKind.CAST)) { + return false; + } + final RelDataType source = ((RexCall) node).getOperands().get(0).getType(); + final SqlTypeName sourceSqlTypeName = source.getSqlTypeName(); + final RelDataType target = node.getType(); + final SqlTypeName targetSqlTypeName = target.getSqlTypeName(); + // 1) Both INT numeric types + if (SqlTypeFamily.INTEGER.getTypeNames().contains(sourceSqlTypeName) + && SqlTypeFamily.INTEGER.getTypeNames().contains(targetSqlTypeName)) { + return targetSqlTypeName.compareTo(sourceSqlTypeName) >= 0; + } + // 2) Both CHARACTER types: it depends on the precision (length) + if (SqlTypeFamily.CHARACTER.getTypeNames().contains(sourceSqlTypeName) + && SqlTypeFamily.CHARACTER.getTypeNames().contains(targetSqlTypeName)) { + return targetSqlTypeName.compareTo(sourceSqlTypeName) >= 0 + && source.getPrecision() <= target.getPrecision(); + } + // 3) From NUMERIC family to CHARACTER family: it depends on the precision/scale + if (sourceSqlTypeName.getFamily() == SqlTypeFamily.NUMERIC + && targetSqlTypeName.getFamily() == SqlTypeFamily.CHARACTER) { + int sourceLength = source.getPrecision() + 1; // include sign + if (source.getScale() != -1 && source.getScale() != 0) { + sourceLength += source.getScale() + 1; // include decimal mark + } + return target.getPrecision() >= sourceLength; + } + // Return FALSE by default + return false; + } + /** Converts an expression to conjunctive normal form (CNF). * * <p>The following expression is in CNF: http://git-wip-us.apache.org/repos/asf/calcite/blob/a2bd49c3/core/src/test/java/org/apache/calcite/test/RexProgramTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java index ebc17f6..f2f6c68 100644 --- a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java +++ b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java @@ -634,6 +634,105 @@ public class RexProgramTest { } + /** Unit test for {@link org.apache.calcite.rex.RexUtil#isLosslessCast(RexNode)}. */ + @Test public void testLosslessCast() { + 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 floatType = typeFactory.createSqlType(SqlTypeName.FLOAT); + final RelDataType booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN); + final RelDataType charType5 = typeFactory.createSqlType(SqlTypeName.CHAR, 5); + final RelDataType charType6 = typeFactory.createSqlType(SqlTypeName.CHAR, 6); + final RelDataType varCharType10 = typeFactory.createSqlType(SqlTypeName.VARCHAR, 10); + final RelDataType varCharType11 = typeFactory.createSqlType(SqlTypeName.VARCHAR, 11); + + // Negative + assertThat(RexUtil.isLosslessCast(rexBuilder.makeInputRef(intType, 0)), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + tinyIntType, rexBuilder.makeInputRef(smallIntType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + smallIntType, rexBuilder.makeInputRef(intType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + intType, rexBuilder.makeInputRef(bigIntType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + bigIntType, rexBuilder.makeInputRef(floatType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + booleanType, rexBuilder.makeInputRef(bigIntType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + intType, rexBuilder.makeInputRef(charType5, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + intType, rexBuilder.makeInputRef(varCharType10, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType10, rexBuilder.makeInputRef(varCharType11, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + charType5, rexBuilder.makeInputRef(bigIntType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + charType5, rexBuilder.makeInputRef(smallIntType, 0))), is(false)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType10, rexBuilder.makeInputRef(intType, 0))), is(false)); + + // Positive + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + smallIntType, rexBuilder.makeInputRef(tinyIntType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + intType, rexBuilder.makeInputRef(smallIntType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + bigIntType, rexBuilder.makeInputRef(intType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + intType, rexBuilder.makeInputRef(intType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + charType6, rexBuilder.makeInputRef(smallIntType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType10, rexBuilder.makeInputRef(smallIntType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType11, rexBuilder.makeInputRef(intType, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType11, rexBuilder.makeInputRef(charType6, 0))), is(true)); + assertThat( + RexUtil.isLosslessCast( + rexBuilder.makeCast( + varCharType11, rexBuilder.makeInputRef(varCharType10, 0))), is(true)); + } + /** Unit test for {@link org.apache.calcite.rex.RexUtil#toCnf}. */ @Test public void testCnf() { final RelDataType booleanType =
