From 0ae645b1bb8af1babc3e525552eea6d04496e221 Mon Sep 17 00:00:00 2001 From: v-jizhang Date: Wed, 26 Jan 2022 13:34:09 -0800 Subject: [PATCH] Test casts of approximate numeric types to bounded varchar Cherry-pick of https://github.com/trinodb/trino/pull/10211/commits/631a95891208308f6fcdea2026aed9e1f0656d84 This change applies to double and real Co-authored-by: kasiafi <30203062+kasiafi@users.noreply.github.com> --- .../presto/sql/TestExpressionInterpreter.java | 106 ++++++++++++++++-- .../rule/TestSimplifyExpressions.java | 65 +++++++++-- .../presto/type/TestDoubleOperators.java | 12 ++ .../presto/type/TestRealOperators.java | 12 ++ 4 files changed, 175 insertions(+), 20 deletions(-) diff --git a/presto-main/src/test/java/com/facebook/presto/sql/TestExpressionInterpreter.java b/presto-main/src/test/java/com/facebook/presto/sql/TestExpressionInterpreter.java index 54e5be70d79d1..1657b9667e4f2 100644 --- a/presto-main/src/test/java/com/facebook/presto/sql/TestExpressionInterpreter.java +++ b/presto-main/src/test/java/com/facebook/presto/sql/TestExpressionInterpreter.java @@ -587,21 +587,18 @@ public void testCastToString() } @Test - public void testCastBigintToBoundedVarchar() - { + public void testCastBigintToBoundedVarchar() { assertEvaluatedEquals("CAST(12300000000 AS varchar(11))", "'12300000000'"); assertEvaluatedEquals("CAST(12300000000 AS varchar(50))", "'12300000000'"); try { evaluate("CAST(12300000000 AS varchar(3))", true); fail("Expected to throw an INVALID_CAST_ARGUMENT exception"); - } - catch (PrestoException e) { + } catch (PrestoException e) { try { assertEquals(e.getErrorCode(), INVALID_CAST_ARGUMENT.toErrorCode()); assertEquals(e.getMessage(), "Value 12300000000 cannot be represented as varchar(3)"); - } - catch (Throwable failure) { + } catch (Throwable failure) { failure.addSuppressed(e); throw failure; } @@ -609,19 +606,108 @@ public void testCastBigintToBoundedVarchar() try { evaluate("CAST(-12300000000 AS varchar(3))", true); - } - catch (PrestoException e) { + } catch (PrestoException e) { try { assertEquals(e.getErrorCode(), INVALID_CAST_ARGUMENT.toErrorCode()); assertEquals(e.getMessage(), "Value -12300000000 cannot be represented as varchar(3)"); - } - catch (Throwable failure) { + } catch (Throwable failure) { failure.addSuppressed(e); throw failure; } } } + @Test + public void testCastDoubleToBoundedVarchar() + { + // NaN + assertEvaluatedEquals("CAST(0e0 / 0e0 AS varchar(3))", "'NaN'"); + assertEvaluatedEquals("CAST(0e0 / 0e0 AS varchar(50))", "'NaN'"); + + // Infinity + assertEvaluatedEquals("CAST(DOUBLE 'Infinity' AS varchar(8))", "'Infinity'"); + assertEvaluatedEquals("CAST(DOUBLE 'Infinity' AS varchar(50))", "'Infinity'"); + + // incorrect behavior: the string representation is not compliant with the SQL standard + assertEvaluatedEquals("CAST(0e0 AS varchar(3))", "'0.0'"); + assertEvaluatedEquals("CAST(DOUBLE '0' AS varchar(3))", "'0.0'"); + assertEvaluatedEquals("CAST(DOUBLE '-0' AS varchar(4))", "'-0.0'"); + assertEvaluatedEquals("CAST(DOUBLE '0' AS varchar(50))", "'0.0'"); + + assertEvaluatedEquals("CAST(12e0 AS varchar(4))", "'12.0'"); + assertEvaluatedEquals("CAST(12e2 AS varchar(6))", "'1200.0'"); + assertEvaluatedEquals("CAST(12e-2 AS varchar(4))", "'0.12'"); + + assertEvaluatedEquals("CAST(12e0 AS varchar(50))", "'12.0'"); + assertEvaluatedEquals("CAST(12e2 AS varchar(50))", "'1200.0'"); + assertEvaluatedEquals("CAST(12e-2 AS varchar(50))", "'0.12'"); + + assertEvaluatedEquals("CAST(-12e0 AS varchar(5))", "'-12.0'"); + assertEvaluatedEquals("CAST(-12e2 AS varchar(7))", "'-1200.0'"); + assertEvaluatedEquals("CAST(-12e-2 AS varchar(5))", "'-0.12'"); + + assertEvaluatedEquals("CAST(-12e0 AS varchar(50))", "'-12.0'"); + assertEvaluatedEquals("CAST(-12e2 AS varchar(50))", "'-1200.0'"); + assertEvaluatedEquals("CAST(-12e-2 AS varchar(50))", "'-0.12'"); + + // the string representation is compliant with the SQL standard + assertEvaluatedEquals("CAST(12345678.9e0 AS varchar(12))", "'1.23456789E7'"); + assertEvaluatedEquals("CAST(0.00001e0 AS varchar(6))", "'1.0E-5'"); + + // incorrect behavior: the result value does not fit in the type (also, it is not compliant with the SQL standard) + assertEvaluatedEquals("CAST(12e0 AS varchar(1))", "'12.0'"); + assertEvaluatedEquals("CAST(-12e2 AS varchar(1))", "'-1200.0'"); + assertEvaluatedEquals("CAST(0e0 AS varchar(1))", "'0.0'"); + assertEvaluatedEquals("CAST(0e0 / 0e0 AS varchar(1))", "'NaN'"); + assertEvaluatedEquals("CAST(DOUBLE 'Infinity' AS varchar(1))", "'Infinity'"); + assertEvaluatedEquals("CAST(1200000e0 AS varchar(5))", "'1200000.0'"); + } + + @Test + public void testCastRealToBoundedVarchar() + { + // NaN + assertEvaluatedEquals("CAST(REAL '0e0' / REAL '0e0' AS varchar(3))", "'NaN'"); + assertEvaluatedEquals("CAST(REAL '0e0' / REAL '0e0' AS varchar(50))", "'NaN'"); + + // Infinity + assertEvaluatedEquals("CAST(REAL 'Infinity' AS varchar(8))", "'Infinity'"); + assertEvaluatedEquals("CAST(REAL 'Infinity' AS varchar(50))", "'Infinity'"); + + // incorrect behavior: the string representation is not compliant with the SQL standard + assertEvaluatedEquals("CAST(REAL '0' AS varchar(3))", "'0.0'"); + assertEvaluatedEquals("CAST(REAL '-0' AS varchar(4))", "'-0.0'"); + assertEvaluatedEquals("CAST(REAL '0' AS varchar(50))", "'0.0'"); + + assertEvaluatedEquals("CAST(REAL '12' AS varchar(4))", "'12.0'"); + assertEvaluatedEquals("CAST(REAL '12e2' AS varchar(6))", "'1200.0'"); + assertEvaluatedEquals("CAST(REAL '12e-2' AS varchar(4))", "'0.12'"); + + assertEvaluatedEquals("CAST(REAL '12' AS varchar(50))", "'12.0'"); + assertEvaluatedEquals("CAST(REAL '12e2' AS varchar(50))", "'1200.0'"); + assertEvaluatedEquals("CAST(REAL '12e-2' AS varchar(50))", "'0.12'"); + + assertEvaluatedEquals("CAST(REAL '-12' AS varchar(5))", "'-12.0'"); + assertEvaluatedEquals("CAST(REAL '-12e2' AS varchar(7))", "'-1200.0'"); + assertEvaluatedEquals("CAST(REAL '-12e-2' AS varchar(5))", "'-0.12'"); + + assertEvaluatedEquals("CAST(REAL '-12' AS varchar(50))", "'-12.0'"); + assertEvaluatedEquals("CAST(REAL '-12e2' AS varchar(50))", "'-1200.0'"); + assertEvaluatedEquals("CAST(REAL '-12e-2' AS varchar(50))", "'-0.12'"); + + // the string representation is compliant with the SQL standard + assertEvaluatedEquals("CAST(REAL '12345678.9e0' AS varchar(12))", "'1.2345679E7'"); + assertEvaluatedEquals("CAST(REAL '0.00001e0' AS varchar(6))", "'1.0E-5'"); + + // incorrect behavior: the result value does not fit in the type (also, it is not compliant with the SQL standard) + assertEvaluatedEquals("CAST(REAL '12' AS varchar(1))", "'12.0'"); + assertEvaluatedEquals("CAST(REAL '-12e2' AS varchar(1))", "'-1200.0'"); + assertEvaluatedEquals("CAST(REAL '0' AS varchar(1))", "'0.0'"); + assertEvaluatedEquals("CAST(REAL '0e0' / REAL '0e0' AS varchar(1))", "'NaN'"); + assertEvaluatedEquals("CAST(REAL 'Infinity' AS varchar(1))", "'Infinity'"); + assertEvaluatedEquals("CAST(REAL '1200000' AS varchar(5))", "'1200000.0'"); + } + @Test public void testCastToBoolean() { diff --git a/presto-main/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestSimplifyExpressions.java b/presto-main/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestSimplifyExpressions.java index 9d1453c577ba5..21fa67f42c96d 100644 --- a/presto-main/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestSimplifyExpressions.java +++ b/presto-main/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestSimplifyExpressions.java @@ -137,8 +137,7 @@ public void testExtractCommonPredicates() } @Test - public void testCastBigintToBoundedVarchar() - { + public void testCastBigintToBoundedVarchar() { // the varchar type length is enough to contain the number's representation assertSimplifies("CAST(12300000000 AS varchar(11))", "'12300000000'"); // The last argument "'-12300000000'" is varchar(12). Need varchar(50) to the following test pass. @@ -148,13 +147,11 @@ public void testCastBigintToBoundedVarchar() try { assertSimplifies("CAST(12300000000 AS varchar(3))", "CAST(12300000000 AS varchar(3))"); fail("Expected to throw an PrestoException exception"); - } - catch (PrestoException e) { + } catch (PrestoException e) { try { assertEquals(e.getErrorCode(), INVALID_CAST_ARGUMENT.toErrorCode()); assertEquals(e.getMessage(), "Value 12300000000 cannot be represented as varchar(3)"); - } - catch (Throwable failure) { + } catch (Throwable failure) { failure.addSuppressed(e); throw failure; } @@ -162,19 +159,67 @@ public void testCastBigintToBoundedVarchar() try { assertSimplifies("CAST(-12300000000 AS varchar(3))", "CAST(-12300000000 AS varchar(3))"); - } - catch (PrestoException e) { + } catch (PrestoException e) { try { assertEquals(e.getErrorCode(), INVALID_CAST_ARGUMENT.toErrorCode()); assertEquals(e.getMessage(), "Value -12300000000 cannot be represented as varchar(3)"); - } - catch (Throwable failure) { + } catch (Throwable failure) { failure.addSuppressed(e); throw failure; } } } + @Test + public void testCastDoubleToBoundedVarchar() + { + // the varchar type length is enough to contain the number's representation + assertSimplifies("CAST(0e0 AS varchar(3))", "'0.0'"); + assertSimplifies("CAST(-0e0 AS varchar(4))", "'-0.0'"); + assertSimplifies("CAST(0e0 / 0e0 AS varchar(3))", "'NaN'"); + assertSimplifies("CAST(DOUBLE 'Infinity' AS varchar(8))", "'Infinity'"); + assertSimplifies("CAST(12e2 AS varchar(6))", "'1200.0'"); + //assertSimplifies("CAST(-12e2 AS varchar(50))", "CAST('-1200.0' AS varchar(50))"); + + // the varchar type length is not enough to contain the number's representation: + // the cast operator returns a value that is too long for the expected type ('1200.0' for varchar(3)) + // the value is then wrapped in another cast by the LiteralEncoder (CAST('1200.0' AS varchar(3))), + // so eventually we get a truncated string '120' + assertSimplifies("CAST(12e2 AS varchar(3))", "CAST('1200.0' AS varchar(3))"); + assertSimplifies("CAST(-12e2 AS varchar(3))", "CAST('-1200.0' AS varchar(3))"); + assertSimplifies("CAST(DOUBLE 'NaN' AS varchar(2))", "CAST('NaN' AS varchar(2))"); + assertSimplifies("CAST(DOUBLE 'Infinity' AS varchar(7))", "CAST('Infinity' AS varchar(7))"); + + // the cast operator returns a value that is too long for the expected type ('1200.0' for varchar(3)) + // the value is nested in a comparison expression, so it is not truncated by the LiteralEncoder + assertSimplifies("CAST(12e2 AS varchar(3)) = '1200.0'", "true"); + } + + @Test + public void testCastRealToBoundedVarchar() + { + // the varchar type length is enough to contain the number's representation + assertSimplifies("CAST(REAL '0e0' AS varchar(3))", "'0.0'"); + assertSimplifies("CAST(REAL '-0e0' AS varchar(4))", "'-0.0'"); + assertSimplifies("CAST(REAL '0e0' / REAL '0e0' AS varchar(3))", "'NaN'"); + assertSimplifies("CAST(REAL 'Infinity' AS varchar(8))", "'Infinity'"); + assertSimplifies("CAST(REAL '12e2' AS varchar(6))", "'1200.0'"); + assertSimplifies("CAST(REAL '-12e2' AS varchar(50))", "CAST('-1200.0' AS varchar(50))"); + + // the varchar type length is not enough to contain the number's representation: + // the cast operator returns a value that is too long for the expected type ('1200.0' for varchar(3)) + // the value is then wrapped in another cast by the LiteralEncoder (CAST('1200.0' AS varchar(3))), + // so eventually we get a truncated string '120' + assertSimplifies("CAST(REAL '12e2' AS varchar(3))", "CAST('1200.0' AS varchar(3))"); + assertSimplifies("CAST(REAL '-12e2' AS varchar(3))", "CAST('-1200.0' AS varchar(3))"); + assertSimplifies("CAST(REAL 'NaN' AS varchar(2))", "CAST('NaN' AS varchar(2))"); + assertSimplifies("CAST(REAL 'Infinity' AS varchar(7))", "CAST('Infinity' AS varchar(7))"); + + // the cast operator returns a value that is too long for the expected type ('1200.0' for varchar(3)) + // the value is nested in a comparison expression, so it is not truncated by the LiteralEncoder + assertSimplifies("CAST(REAL '12e2' AS varchar(3)) = '1200.0'", "true"); + } + private static void assertSimplifies(String expression, String expected) { assertSimplifies(expression, expected, null); diff --git a/presto-main/src/test/java/com/facebook/presto/type/TestDoubleOperators.java b/presto-main/src/test/java/com/facebook/presto/type/TestDoubleOperators.java index 32158e2ebefff..716f4c139dcb2 100644 --- a/presto-main/src/test/java/com/facebook/presto/type/TestDoubleOperators.java +++ b/presto-main/src/test/java/com/facebook/presto/type/TestDoubleOperators.java @@ -22,6 +22,7 @@ import static com.facebook.presto.common.type.DoubleType.DOUBLE; import static com.facebook.presto.common.type.RealType.REAL; import static com.facebook.presto.common.type.VarcharType.VARCHAR; +import static com.facebook.presto.common.type.VarcharType.createVarcharType; import static com.facebook.presto.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static java.lang.Double.doubleToLongBits; import static java.lang.Double.doubleToRawLongBits; @@ -178,6 +179,17 @@ public void testCastToVarchar() { assertFunction("cast(37.7E0 as varchar)", VARCHAR, "37.7"); assertFunction("cast(17.1E0 as varchar)", VARCHAR, "17.1"); + assertFunction("cast(12e2 as varchar(6))", createVarcharType(6), "1200.0"); + assertFunction("cast(12e2 as varchar(50))", createVarcharType(50), "1200.0"); + assertFunction("cast(12345678.9e0 as varchar(50))", createVarcharType(50), "1.23456789E7"); + assertFunction("cast(DOUBLE 'NaN' as varchar(3))", createVarcharType(3), "NaN"); + assertFunction("cast(DOUBLE 'Infinity' as varchar(50))", createVarcharType(50), "Infinity"); + assertFunctionThrowsIncorrectly("cast(12e2 as varchar(5))", IllegalArgumentException.class, "Character count exceeds length limit 5.*"); + assertFunctionThrowsIncorrectly("cast(12e2 as varchar(4))", IllegalArgumentException.class, "Character count exceeds length limit 4.*"); + assertFunctionThrowsIncorrectly("cast(0e0 as varchar(2))", IllegalArgumentException.class, "Character count exceeds length limit 2.*"); + assertFunctionThrowsIncorrectly("cast(-0e0 as varchar(3))", IllegalArgumentException.class, "Character count exceeds length limit 3.*"); + assertFunctionThrowsIncorrectly("cast(0e0 / 0e0 as varchar(2))", IllegalArgumentException.class, "Character count exceeds length limit 2.*"); + assertFunctionThrowsIncorrectly("cast(DOUBLE 'Infinity' as varchar(7))", IllegalArgumentException.class, "Character count exceeds length limit 7.*"); } @Test diff --git a/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java b/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java index 353245350fa00..bf9a2d4d2c865 100644 --- a/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java +++ b/presto-main/src/test/java/com/facebook/presto/type/TestRealOperators.java @@ -25,6 +25,7 @@ import static com.facebook.presto.common.type.SmallintType.SMALLINT; import static com.facebook.presto.common.type.TinyintType.TINYINT; import static com.facebook.presto.common.type.VarcharType.VARCHAR; +import static com.facebook.presto.common.type.VarcharType.createVarcharType; import static java.lang.Float.floatToIntBits; import static java.lang.Float.intBitsToFloat; import static java.lang.Float.isNaN; @@ -174,6 +175,17 @@ public void testCastToVarchar() assertFunction("CAST(REAL'-754.2008' as VARCHAR)", VARCHAR, "-754.2008"); assertFunction("CAST(REAL'Infinity' as VARCHAR)", VARCHAR, "Infinity"); assertFunction("CAST(REAL'0.0' / REAL'0.0' as VARCHAR)", VARCHAR, "NaN"); + assertFunction("cast(REAL '12e2' as varchar(6))", createVarcharType(6), "1200.0"); + assertFunction("cast(REAL '12e2' as varchar(50))", createVarcharType(50), "1200.0"); + assertFunction("cast(REAL '12345678.9e0' as varchar(50))", createVarcharType(50), "1.2345679E7"); + assertFunction("cast(REAL 'NaN' as varchar(3))", createVarcharType(3), "NaN"); + assertFunction("cast(REAL 'Infinity' as varchar(50))", createVarcharType(50), "Infinity"); + assertFunctionThrowsIncorrectly("cast(REAL '12e2' as varchar(5))", IllegalArgumentException.class, "Character count exceeds length limit 5.*"); + assertFunctionThrowsIncorrectly("cast(REAL '12e2' as varchar(4))", IllegalArgumentException.class, "Character count exceeds length limit 4.*"); + assertFunctionThrowsIncorrectly("cast(REAL '0e0' as varchar(2))", IllegalArgumentException.class, "Character count exceeds length limit 2.*"); + assertFunctionThrowsIncorrectly("cast(REAL '-0e0' as varchar(3))", IllegalArgumentException.class, "Character count exceeds length limit 3.*"); + assertFunctionThrowsIncorrectly("cast(REAL '0e0' / REAL '0e0' as varchar(2))", IllegalArgumentException.class, "Character count exceeds length limit 2.*"); + assertFunctionThrowsIncorrectly("cast(REAL 'Infinity' as varchar(7))", IllegalArgumentException.class, "Character count exceeds length limit 7.*"); } @Test