From 69c3ac0805fd382a0902bfd533f3dae093a4e2d5 Mon Sep 17 00:00:00 2001 From: Teja Vegi Date: Wed, 13 Oct 2021 22:43:53 +0530 Subject: [PATCH] Add large number format function --- .../BuiltInFunctionNamespaceManager.java | 2 + .../operator/scalar/FormatNumberFunction.java | 99 ++++++++++++++++ .../ScalarFromAnnotationsParser.java | 2 +- .../scalar/TestFormatNumberFunction.java | 106 ++++++++++++++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 presto-main/src/main/java/io/prestosql/operator/scalar/FormatNumberFunction.java create mode 100644 presto-main/src/test/java/io/prestosql/operator/scalar/TestFormatNumberFunction.java diff --git a/presto-main/src/main/java/io/prestosql/metadata/BuiltInFunctionNamespaceManager.java b/presto-main/src/main/java/io/prestosql/metadata/BuiltInFunctionNamespaceManager.java index 08717ecb7..43f9529cd 100644 --- a/presto-main/src/main/java/io/prestosql/metadata/BuiltInFunctionNamespaceManager.java +++ b/presto-main/src/main/java/io/prestosql/metadata/BuiltInFunctionNamespaceManager.java @@ -104,6 +104,7 @@ import io.prestosql.operator.scalar.DataSizeFunctions; import io.prestosql.operator.scalar.DateTimeFunctions; import io.prestosql.operator.scalar.EmptyMapConstructor; import io.prestosql.operator.scalar.FailureFunction; +import io.prestosql.operator.scalar.FormatNumberFunction; import io.prestosql.operator.scalar.HmacFunctions; import io.prestosql.operator.scalar.HyperLogLogFunctions; import io.prestosql.operator.scalar.JoniRegexpCasts; @@ -496,6 +497,7 @@ public class BuiltInFunctionNamespaceManager .scalar(MathFunctions.Floor.class) .scalars(BitwiseFunctions.class) .scalars(DateTimeFunctions.class) + .scalars(FormatNumberFunction.class) .scalars(JsonFunctions.class) .scalars(ColorFunctions.class) .scalars(ColorOperators.class) diff --git a/presto-main/src/main/java/io/prestosql/operator/scalar/FormatNumberFunction.java b/presto-main/src/main/java/io/prestosql/operator/scalar/FormatNumberFunction.java new file mode 100644 index 000000000..56e41a35e --- /dev/null +++ b/presto-main/src/main/java/io/prestosql/operator/scalar/FormatNumberFunction.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018-2021. Huawei Technologies Co., Ltd. All rights reserved. + * 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. + */ + +package io.prestosql.operator.scalar; + +import io.airlift.slice.Slice; +import io.prestosql.spi.function.Description; +import io.prestosql.spi.function.ScalarFunction; +import io.prestosql.spi.function.SqlType; +import io.prestosql.spi.type.StandardTypes; + +import java.math.RoundingMode; +import java.text.DecimalFormat; + +import static io.airlift.slice.Slices.utf8Slice; + +public class FormatNumberFunction +{ + private static final DecimalFormat FORMAT_NUMBER_3 = new DecimalFormat("#.##"); + private static final DecimalFormat FORMAT_NUMBER_2 = new DecimalFormat("#.#"); + private static final DecimalFormat FORMAT_NUMBER_1 = new DecimalFormat("#"); + + public FormatNumberFunction() + { + FORMAT_NUMBER_3.setRoundingMode(RoundingMode.HALF_UP); + FORMAT_NUMBER_2.setRoundingMode(RoundingMode.HALF_UP); + FORMAT_NUMBER_1.setRoundingMode(RoundingMode.HALF_UP); + } + + @ScalarFunction + @Description("Formats large number using a unit symbol") + @SqlType(StandardTypes.VARCHAR) + public Slice formatNumber(@SqlType(StandardTypes.BIGINT) long value) + { + return utf8Slice(format(value)); + } + + @ScalarFunction + @Description("Formats large number using a unit symbol") + @SqlType(StandardTypes.VARCHAR) + public Slice formatNumber(@SqlType(StandardTypes.DOUBLE) double value) + { + return utf8Slice(format((long) value)); + } + + private String format(long count) + { + double fractional = count; + String unit = ""; + if (fractional >= 1000 || fractional <= -1000) { + fractional /= 1000; + unit = "K"; + } + if (fractional >= 1000 || fractional <= -1000) { + fractional /= 1000; + unit = "M"; + } + if (fractional >= 1000 || fractional <= -1000) { + fractional /= 1000; + unit = "B"; + } + if (fractional >= 1000 || fractional <= -1000) { + fractional /= 1000; + unit = "T"; + } + if (fractional >= 1000 || fractional <= -1000) { + fractional /= 1000; + unit = "Q"; + } + + return getFormat(fractional).format(fractional) + unit; + } + + private DecimalFormat getFormat(double value) + { + if (value < 10) { + // show up to two decimals to get 3 significant digits + return FORMAT_NUMBER_3; + } + if (value < 100) { + // show up to one decimal to get 3 significant digits + return FORMAT_NUMBER_2; + } + // show no decimals -- we have enough digits in the integer part + return FORMAT_NUMBER_1; + } +} diff --git a/presto-main/src/main/java/io/prestosql/operator/scalar/annotations/ScalarFromAnnotationsParser.java b/presto-main/src/main/java/io/prestosql/operator/scalar/annotations/ScalarFromAnnotationsParser.java index b66992dd2..ffcbc6246 100644 --- a/presto-main/src/main/java/io/prestosql/operator/scalar/annotations/ScalarFromAnnotationsParser.java +++ b/presto-main/src/main/java/io/prestosql/operator/scalar/annotations/ScalarFromAnnotationsParser.java @@ -57,7 +57,7 @@ public final class ScalarFromAnnotationsParser ImmutableList.Builder builder = ImmutableList.builder(); for (ScalarHeaderAndMethods methods : findScalarsInFunctionSetClass(clazz)) { // Non-static function only makes sense in classes annotated with @ScalarFunction or @ScalarOperator. - builder.add(parseParametricScalar(methods, Optional.empty())); + builder.add(parseParametricScalar(methods, FunctionsParserHelper.findConstructor(clazz))); } return builder.build(); } diff --git a/presto-main/src/test/java/io/prestosql/operator/scalar/TestFormatNumberFunction.java b/presto-main/src/test/java/io/prestosql/operator/scalar/TestFormatNumberFunction.java new file mode 100644 index 000000000..2aacdfb3d --- /dev/null +++ b/presto-main/src/test/java/io/prestosql/operator/scalar/TestFormatNumberFunction.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018-2021. Huawei Technologies Co., Ltd. All rights reserved. + * 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. + */ + +package io.prestosql.operator.scalar; + +import org.testng.annotations.Test; + +import static io.prestosql.spi.type.VarcharType.VARCHAR; + +public class TestFormatNumberFunction + extends AbstractTestFunctions +{ + @Test + public void testFormatNumber() + { + assertFunction("format_number(TINYINT '123')", VARCHAR, "123"); + assertFunction("format_number(SMALLINT '12345')", VARCHAR, "12.3K"); + assertFunction("format_number(SMALLINT '12399')", VARCHAR, "12.4K"); + assertFunction("format_number(INTEGER '12345678')", VARCHAR, "12.3M"); + assertFunction("format_number(INTEGER '12399999')", VARCHAR, "12.4M"); + assertFunction("format_number(BIGINT '12345678901')", VARCHAR, "12.3B"); + assertFunction("format_number(BIGINT '12399999999')", VARCHAR, "12.4B"); + + assertFunction("format_number(DOUBLE '1234.5')", VARCHAR, "1.23K"); + assertFunction("format_number(DOUBLE '1239.9')", VARCHAR, "1.24K"); + assertFunction("format_number(REAL '1234567.8')", VARCHAR, "1.23M"); + assertFunction("format_number(REAL '1239999.9')", VARCHAR, "1.24M"); + assertFunction("format_number(DECIMAL '1234567890.1')", VARCHAR, "1.23B"); + assertFunction("format_number(DECIMAL '1239999999.9')", VARCHAR, "1.24B"); + + assertFunction("format_number(-999)", VARCHAR, "-999"); + assertFunction("format_number(-1000)", VARCHAR, "-1K"); + assertFunction("format_number(-999999)", VARCHAR, "-1000K"); + assertFunction("format_number(-1000000)", VARCHAR, "-1M"); + assertFunction("format_number(-999999999)", VARCHAR, "-1000M"); + assertFunction("format_number(-1000000000)", VARCHAR, "-1B"); + assertFunction("format_number(-999999999999)", VARCHAR, "-1000B"); + assertFunction("format_number(-1000000000000)", VARCHAR, "-1T"); + assertFunction("format_number(-999999999999999)", VARCHAR, "-1000T"); + assertFunction("format_number(-1000000000000000)", VARCHAR, "-1Q"); + assertFunction("format_number(-9223372036854775808)", VARCHAR, "-9223.37Q"); + + assertFunction("format_number(0)", VARCHAR, "0"); + assertFunction("format_number(999)", VARCHAR, "999"); + assertFunction("format_number(1000)", VARCHAR, "1K"); + assertFunction("format_number(999999)", VARCHAR, "1000K"); + assertFunction("format_number(1000000)", VARCHAR, "1M"); + assertFunction("format_number(999999999)", VARCHAR, "1000M"); + assertFunction("format_number(1000000000)", VARCHAR, "1B"); + assertFunction("format_number(999999999999)", VARCHAR, "1000B"); + assertFunction("format_number(1000000000000)", VARCHAR, "1T"); + assertFunction("format_number(999999999999999)", VARCHAR, "1000T"); + assertFunction("format_number(1000000000000000)", VARCHAR, "1Q"); + assertFunction("format_number(9223372036854775807)", VARCHAR, "9223Q"); + + assertFunction("format_number(CAST(NULL AS TINYINT))", VARCHAR, null); + assertFunction("format_number(CAST(NULL AS SMALLINT))", VARCHAR, null); + assertFunction("format_number(CAST(NULL AS INTEGER))", VARCHAR, null); + assertFunction("format_number(CAST(NULL AS DOUBLE))", VARCHAR, null); + assertFunction("format_number(CAST(NULL AS REAL))", VARCHAR, null); + assertFunction("format_number(CAST(NULL AS DECIMAL))", VARCHAR, null); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testVarcharFormatFunction() + { + assertFunction("format_number('abc')", VARCHAR, ""); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testDateFormatFunction() + { + assertFunction("format_number(DATE '2015-09-01')", VARCHAR, ""); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testTimestampFormatFunction() + { + assertFunction("format_number(TIMESTAMP '2015-05-10 12:45:31')", VARCHAR, ""); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testBooleanFormatFunction() + { + assertFunction("format_number(true)", VARCHAR, ""); + assertFunction("format_number(false)", VARCHAR, ""); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testNumberAsVarcharFormatFunction() + { + assertFunction("format_number('123')", VARCHAR, ""); + } +}