diff --git a/velox/expression/tests/CastExprTest.cpp b/velox/expression/tests/CastExprTest.cpp index e37fd5536a61b..7c35dfe8a163d 100644 --- a/velox/expression/tests/CastExprTest.cpp +++ b/velox/expression/tests/CastExprTest.cpp @@ -1265,10 +1265,9 @@ TEST_F(CastExprTest, truncateVsRound) { "tinyint", {1111111, 2, 3, 1000, -100101}, {71, 2, 3, -24, -5}); setCastIntByTruncate(false); - EXPECT_THROW( - (testCast( - "tinyint", {1111111, 2, 3, 1000, -100101}, {71, 2, 3, -24, -5})), - VeloxUserError); + testCast("tinyint", {2, 3}, {2, 3}); + testCast( + "tinyint", {1111111, 1000, -100101}, {0, 0, 0}, true); } TEST_F(CastExprTest, nullInputs) { diff --git a/velox/type/tests/CMakeLists.txt b/velox/type/tests/CMakeLists.txt index 1106ed087ec70..4b71c38e32e61 100644 --- a/velox/type/tests/CMakeLists.txt +++ b/velox/type/tests/CMakeLists.txt @@ -22,7 +22,8 @@ add_executable( SubfieldTest.cpp TimestampConversionTest.cpp VariantTest.cpp - TimestampTest.cpp) + TimestampTest.cpp + ConversionsTest.cpp) add_test(velox_type_test velox_type_test) diff --git a/velox/type/tests/ConversionsTest.cpp b/velox/type/tests/ConversionsTest.cpp new file mode 100644 index 0000000000000..6708174fcfcd4 --- /dev/null +++ b/velox/type/tests/ConversionsTest.cpp @@ -0,0 +1,1007 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "velox/type/Conversions.h" +#include "gtest/gtest.h" +#include "velox/common/base/VeloxException.h" +#include "velox/common/base/tests/GTestUtils.h" + +using namespace facebook::velox; + +namespace facebook::velox::util { +namespace { + +constexpr float kInf = std::numeric_limits::infinity(); +constexpr float kNan = std::numeric_limits::quiet_NaN(); + +class ConversionsTest : public testing::Test { + protected: + template + static void testConversion( + std::vector input, + std::vector expectedResult, + bool truncate = false, + bool legacyCast = false, + bool expectError = false) { + if (!expectError) { + VELOX_CHECK_EQ(input.size(), expectedResult.size()); + } + const TypeKind toTypeKind = CppToType::typeKind; + + auto cast = [&](TFrom input) -> TTo { + if (truncate & legacyCast) { + return Converter::cast(input); + } else if (!truncate & legacyCast) { + return Converter::cast(input); + } else if (truncate & !legacyCast) { + return Converter::cast(input); + } else { + return Converter::cast(input); + } + }; + + for (auto i = 0; i < input.size(); i++) { + if (expectError) { + ASSERT_ANY_THROW(cast(input[i])) << "Converting input at " << i; + } else { + TTo actual = cast(input[i]); + TTo expected = expectedResult[i]; + if constexpr (std::is_floating_point_v) { + if (std::isnan(actual)) { + ASSERT_TRUE(std::isnan(expected)) + << ", when actual is NaN, and converting input at " << i; + } else if (std::isnan(expected)) { + ASSERT_TRUE(std::isnan(actual)) + << ", when expected is NaN, and converting input at " << i; + } else { + ASSERT_EQ(actual, expected) << "Converting input at " << i; + } + } else { + ASSERT_EQ(actual, expected) << "Converting input at " << i; + } + } + } + } +}; + +TEST_F(ConversionsTest, toBoolean) { + // From integral types. + { + // When TRUNCATE = false. + testConversion( + { + 1, + 0, + 12, + -1, + }, + { + true, + false, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 1, + 0, + 12, + -1, + }, + { + true, + false, + true, + true, + }, + /*truncate*/ true); + } + + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 1.0, + 1.1, + -1.0, + 0.0000000000001, + }, + { + true, + true, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 1.0, + 1.1, + -1.0, + 0.0000000000001, + }, + { + true, + true, + true, + true, + }, + /*truncate*/ true); + } + + // From float. + { + // When TRUNCATE = false. + testConversion( + { + 0.1, + 0.0, + -0.1, + kInf, + kNan, + }, + { + true, + false, + true, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 0.1, + 0.0, + -0.1, + kInf, + kNan, + }, + { + true, + false, + true, + true, + false, + }, + /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1", + "0", + "t", + "true", + "f", + "false", + }, + { + true, + false, + true, + true, + false, + false, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.7E308", + "nan", + "infinity", + "12", + "-1", + "tr", + "tru", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + "1", + "0", + "t", + "true", + "f", + "false", + }, + { + true, + false, + true, + true, + false, + false, + }, + /*truncate*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + { + "1.7E308", + "nan", + "infinity", + "12", + "-1", + "tr", + "tru", + }, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } + + // From timestamp. + { + // When TRUNCATE = false, invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } +} + +TEST_F(ConversionsTest, toIntegeralTypes) { + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 12345.12, + 12345.67, + }, + { + 12345, + 12346, + }, + /*truncate*/ false); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + }, + { + 2, + 3, + 4, + 100, + -100, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + 12345.67, + -12345.67, + 127.8, + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + 12345.12, + 12345.67, + }, + { + 12345, + 12345, + }, + /*truncate*/ true); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + }, + { + 1, + 2, + 3, + 100, + -100, + }, + /*truncate*/ true); + testConversion( + { + 12345.67, + -12345.67, + 127.8, + }, + { + 57, + -57, + 127, + }, + /*truncate*/ true); + testConversion( + { + 1, + 256, + 257, + 2147483646, + 2147483647, + 2147483648, + -2147483646, + -2147483647, + -2147483648, + -2147483649, + }, + { + 1, + 0, + 1, + -2, + -1, + -1, + 2, + 1, + 0, + 0, + }, + /*truncate*/ true); + } + + // From float. + { + // When TRUNCATE = false, invalid cases. + testConversion( + {kInf}, {}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + {kInf}, {9223372036854775807}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1", + "+1", + "-100", + }, + { + 1, + 1, + -100, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.2", + "1.23444", + ".2355", + "-1.8", + "1.", + "-1.", + "0.", + ".", + "-.", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + testConversion( + {"1234567"}, {}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + { + "1a", + "", + "1'234'567", + "1,234,567", + "infinity", + "nan", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + "1.2", + "1.23444", + ".2355", + "-1.8", + "1.", + "-1.", + "0.", + ".", + "-.", + }, + { + 1, + 1, + 0, + -1, + 1, + -1, + 0, + 0, + 0, + }, + /*truncate*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + {"1234567", "+1"}, {}, /*truncate*/ true, false, /*expectError*/ true); + testConversion( + { + "1a", + "", + "1'234'567", + "1,234,567", + "infinity", + "nan", + }, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } + + // From integral types. + { + // When TRUNCATE = false. + testConversion( + { + 2, + 3, + }, + { + 2, + 3, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + 1234567, + -1234567, + 1111111, + 1000, + -100101, + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + 1234567, + -1234567, + 1111111, + 2, + 3, + 1000, + -100101, + }, + { + -121, + 121, + 71, + 2, + 3, + -24, + -5, + }, + /*truncate*/ true); + } + + // From boolean + { + testConversion( + { + true, + false, + }, + { + 1, + 0, + }); + } +} + +TEST_F(ConversionsTest, toString) { + // From integral types. + { + testConversion( + { + 1, + 2, + 3, + 100, + -100, + }, + { + "1", + "2", + "3", + "100", + "-100", + }); + } + + // From double. + { + // When LEGACY_CAST = false. + testConversion( + { + 12345678901234567000.0, + 123456789.01234567, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456789.01234567, + -12345678901234567000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "1.2345678901234567E19", + "1.2345678901234567E8", + "1.0E7", + "12345.0", + "0.001", + "1.2E-4", + "0.0", + "-0.0", + "-1.2E-4", + "-0.001", + "-12345.0", + "-1.0E7", + "-1.2345678901234567E8", + "-1.2345678901234567E19", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + 12345678901234567000.0, + 123456789.01234567, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456789.01234567, + -12345678901234567000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "12345678901234567000.0", + "123456789.01234567", + "10000000.0", + "12345.0", + "0.001", + "0.00012", + "0.0", + "-0.0", + "-0.00012", + "-0.001", + "-12345.0", + "-10000000.0", + "-123456789.01234567", + "-12345678901234567000.0", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ true); + } + + // From float. + { + // When LEGACY_CAST = false. + testConversion( + { + 12345678000000000000.0, + 123456780.0, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456780.0, + -12345678000000000000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "1.2345678E19", + "1.2345678E8", + "1.0E7", + "12345.0", + "0.001", + "1.2E-4", + "0.0", + "-0.0", + "-1.2E-4", + "-0.001", + "-12345.0", + "-1.0E7", + "-1.2345678E8", + "-1.2345678E19", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + 12345678000000000000.0, + 123456780.0, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456780.0, + -12345678000000000000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "12345678295994466000.0", + "123456784.0", + "10000000.0", + "12345.0", + "0.0010000000474974513", + "0.00011999999696854502", + "0.0", + "-0.0", + "-0.00011999999696854502", + "-0.0010000000474974513", + "-12345.0", + "-10000000.0", + "-123456784.0", + "-12345678295994466000.0", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ true); + } + + // From Timestamp. + { + // When LEGACY_CAST = false. + testConversion( + { + Timestamp(-946684800, 0), + Timestamp(-7266, 0), + Timestamp(0, 0), + Timestamp(946684800, 0), + Timestamp(9466848000, 0), + Timestamp(94668480000, 0), + Timestamp(946729316, 0), + Timestamp(946729316, 123), + Timestamp(946729316, 129900000), + Timestamp(7266, 0), + Timestamp(-50049331200, 0), + Timestamp(253405036800, 0), + Timestamp(-62480037600, 0), + }, + { + "1940-01-02 00:00:00.000", + "1969-12-31 21:58:54.000", + "1970-01-01 00:00:00.000", + "2000-01-01 00:00:00.000", + "2269-12-29 00:00:00.000", + "4969-12-04 00:00:00.000", + "2000-01-01 12:21:56.000", + "2000-01-01 12:21:56.000", + "2000-01-01 12:21:56.129", + "1970-01-01 02:01:06.000", + "0384-01-01 08:00:00.000", + "10000-02-01 16:00:00.000", + "-0010-02-01 10:00:00.000", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + Timestamp(946729316, 123), + Timestamp(-50049331200, 0), + Timestamp(253405036800, 0), + Timestamp(-62480037600, 0), + }, + { + "2000-01-01T12:21:56.000", + "384-01-01T08:00:00.000", + "10000-02-01T16:00:00.000", + "-10-02-01T10:00:00.000", + }, + false, + /*legacyCast*/ true); + } +} + +TEST_F(ConversionsTest, toRealAndDouble) { + // From integral types. + { + testConversion({1}, {1.0}, /*truncate*/ false); + testConversion( + { + 1, + 2, + 3, + 100, + -100, + }, + { + 1.0, + 2.0, + 3.0, + 100.0, + -100.0, + }); + } + + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + /*truncate*/ false); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + {1.7E308}, {}, /*truncate*/ false, false, /*expectError*/ true); + + // When TRUNCATE = true. + testConversion({1.7E308}, {kInf}, /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1.7E308", + "1.", + "1", + "infinity", + "-infinity", + "InfiNiTy", + "-InfiNiTy", + "nan", + "nAn", + }, + { + kInf, + 1.0, + 1.0, + kInf, + -kInf, + kInf, + -kInf, + kNan, + kNan, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.2a", + "1.2.3", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + } + + // From Timestamp. + { + // Invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + } +} + +TEST_F(ConversionsTest, toTimestamp) { + // From string. + { + testConversion( + { + "1970-01-01", + "2000-01-01", + "1970-01-01 00:00:00", + "2000-01-01 12:21:56", + "1970-01-01 00:00:00-02:00", + }, + { + Timestamp(0, 0), + Timestamp(946684800, 0), + Timestamp(0, 0), + Timestamp(946729316, 0), + Timestamp(7200, 0), + }); + + // Invalid case. + testConversion( + {"2012-Oct-01"}, {}, false, false, /*expectError*/ true); + } + + // From integral types, invalid cases. + { + testConversion( + {123}, {}, false, false, /*expectError*/ true); + testConversion( + {12345}, {}, false, false, /*expectError*/ true); + testConversion( + {123456}, {}, false, false, /*expectError*/ true); + testConversion( + {123456}, {}, false, false, /*expectError*/ true); + } + + // From floating-point types, invalid cases. + { + testConversion( + {123456.78}, {}, false, false, /*expectError*/ true); + testConversion( + {123456.78}, {}, false, false, /*expectError*/ true); + } +} +} // namespace +} // namespace facebook::velox::util \ No newline at end of file