Skip to content

Commit

Permalink
[CALCITE-5821] Add FORMAT_NUMBER function (enabled in Hive and Spark …
Browse files Browse the repository at this point in the history
…library)
  • Loading branch information
herunkang2018 committed Jul 13, 2023
1 parent 55f714c commit 50ad15c
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FLOOR_BIG_QUERY;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_DATE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_DATETIME;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_NUMBER;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_TIME;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_TIMESTAMP;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE32;
Expand Down Expand Up @@ -501,6 +502,7 @@ Builder populate() {
defineMethod(SHA256, BuiltInMethod.SHA256.method, NullPolicy.STRICT);
defineMethod(SHA512, BuiltInMethod.SHA512.method, NullPolicy.STRICT);
defineMethod(SUBSTRING, BuiltInMethod.SUBSTRING.method, NullPolicy.STRICT);
defineMethod(FORMAT_NUMBER, BuiltInMethod.FORMAT_NUMBER.method, NullPolicy.STRICT);
defineMethod(LEFT, BuiltInMethod.LEFT.method, NullPolicy.ANY);
defineMethod(RIGHT, BuiltInMethod.RIGHT.method, NullPolicy.ANY);
defineMethod(LPAD, BuiltInMethod.LPAD.method, NullPolicy.STRICT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,9 @@ ExInst<CalciteException> illegalArgumentForTableFunctionCall(String a0,
@BaseMessage("Substring error: negative substring length not allowed")
ExInst<CalciteException> illegalNegativeSubstringLength();

@BaseMessage("Illegal arguments for 'FORMAT_NUMBER' function: negative decimal value not allowed")
ExInst<CalciteException> illegalNegativeDecimalValue();

@BaseMessage("Illegal arguments: The length of the keys array {0,number,#} is not equal to the length of the values array {1,number,#} in MAP_FROM_ARRAYS function")
ExInst<CalciteException> illegalArgumentsInMapFromArraysFunc(int arg0, int arg1);

Expand Down
56 changes: 56 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,62 @@ public static ByteString substring(ByteString c, int s, int l) {
return c.substring(s0, e0);
}

/** SQL FORMAT_NUMBER(value, decimalOrFormat) function. */
public static String formatNumber(long value, int decimalVal) {
DecimalFormat numberFormat = getNumberFormat(decimalVal);
return numberFormat.format(value);
}

public static String formatNumber(double value, int decimalVal) {
DecimalFormat numberFormat = getNumberFormat(decimalVal);
return numberFormat.format(value);
}

public static String formatNumber(BigDecimal value, int decimalVal) {
DecimalFormat numberFormat = getNumberFormat(decimalVal);
return numberFormat.format(value);
}

public static String formatNumber(long value, String format) {
DecimalFormat numberFormat = getNumberFormat(format);
return numberFormat.format(value);
}

public static String formatNumber(double value, String format) {
DecimalFormat numberFormat = getNumberFormat(format);
return numberFormat.format(value);
}

public static String formatNumber(BigDecimal value, String format) {
DecimalFormat numberFormat = getNumberFormat(format);
return numberFormat.format(value);
}

public static String getFormatPattern(int decimalVal) {
StringBuilder pattern = new StringBuilder();
pattern.append("#,###,###,###,###,###,##0");

if (decimalVal > 0) {
pattern.append(".");
for (int i = 0; i < decimalVal; i++) {
pattern.append("0");
}
}
return pattern.toString();
}

private static DecimalFormat getNumberFormat(String pattern) {
return NumberUtil.decimalFormat(pattern);
}

private static DecimalFormat getNumberFormat(int decimalVal) {
if (decimalVal < 0) {
throw RESOURCE.illegalNegativeDecimalValue().ex();
}
String pattern = getFormatPattern(decimalVal);
return getNumberFormat(pattern);
}

/** SQL UPPER(string) function. */
public static String upper(String s) {
return s.toUpperCase(Locale.ROOT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,16 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding
OperandTypes.BINARY,
SqlFunctionCategory.STRING);

/** The "FORMAT_NUMBER(value, decimalOrFormat)" function. */
@LibraryOperator(libraries = {HIVE, SPARK})
public static final SqlFunction FORMAT_NUMBER =
SqlBasicFunction.create("FORMAT_NUMBER",
ReturnTypes.VARCHAR_NULLABLE,
OperandTypes.or(
OperandTypes.NUMERIC_NUMERIC,
OperandTypes.NUMERIC_CHARACTER),
SqlFunctionCategory.STRING);

/** The "TO_CHAR(timestamp, format)" function;
* converts {@code timestamp} to string according to the given {@code format}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ public static SqlOperandTypeChecker variadic(
// Second operand optional (operand index 0, 1)
number -> number == 1);

public static final SqlSingleOperandTypeChecker NUMERIC_CHARACTER =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.CHARACTER);

public static final SqlSingleOperandTypeChecker NUMERIC_INTEGER =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.INTEGER);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ public enum BuiltInMethod {
INITCAP(SqlFunctions.class, "initcap", String.class),
SUBSTRING(SqlFunctions.class, "substring", String.class, int.class,
int.class),
FORMAT_NUMBER(SqlFunctions.class, "formatNumber", long.class, int.class),
LPAD(SqlFunctions.class, "lpad", String.class, int.class, String.class),
RPAD(SqlFunctions.class, "rpad", String.class, int.class, String.class),
STARTS_WITH(SqlFunctions.class, "startsWith", String.class, String.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ DialectDoesNotSupportFeature=Dialect does not support feature: ''{0}''
IllegalNegativePadLength=Second argument for LPAD/RPAD must not be negative
IllegalEmptyPadPattern=Third argument (pad pattern) for LPAD/RPAD must not be empty
IllegalNegativeSubstringLength=Substring error: negative substring length not allowed
IllegalNegativeDecimalValue=Illegal arguments for 'FORMAT_NUMBER' function: negative decimal value not allowed
IllegalArgumentsInMapFromArraysFunc=Illegal arguments: The length of the keys array {0,number,#} is not equal to the length of the values array {1,number,#} in MAP_FROM_ARRAYS function
TrimError=Trim error: trim character must be exactly 1 character
InvalidTypesForArithmetic=Invalid types for arithmetic: {0} {1} {2}
Expand Down
2 changes: 2 additions & 0 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,8 @@ BigQuery's type system uses confusingly different names for types and functions:
| b | FLOOR(value) | Similar to standard `FLOOR(value)` except if *value* is an integer type, the return type is a double
| b | FORMAT_DATE(string, date) | Formats *date* according to the specified format *string*
| b | FORMAT_DATETIME(string, timestamp) | Formats *timestamp* according to the specified format *string*
| h s | FORMAT_NUMBER(value, decimalVal) | Formats the number *value* like '#,###,###.##', rounded to decimal places *decimalVal*. If *decimalVal* is 0, the result has no decimal point or fractional part
| h s | FORMAT_NUMBER(value, format) | Formats the number *value* to MySQL's FORMAT *format*, like '#,###,###.##0.00'
| b | FORMAT_TIME(string, time) | Formats *time* according to the specified format *string*
| b | FORMAT_TIMESTAMP(string timestamp) | Formats *timestamp* according to the specified format *string*
| b o | GREATEST(expr [, expr ]*) | Returns the greatest of the expressions
Expand Down
99 changes: 99 additions & 0 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7760,6 +7760,105 @@ void assertSubFunReturns(boolean binary, String s, int start,
}
}

@Test void testFormatNumber() {
final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.FORMAT_NUMBER);
f0.checkFails("^format_number(123, 2)^",
"No match found for function signature FORMAT_NUMBER\\(<NUMERIC>, <NUMERIC>\\)",
false);
final Consumer<SqlOperatorFixture> consumer = f -> {
// test with tinyint type
f.checkString("format_number(cast(1 as tinyint), 4)", "1.0000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1 as tinyint), '#,###,###,###,###,###,##0.0000')",
"1.0000",
"VARCHAR NOT NULL");

// test with smallint type
f.checkString("format_number(cast(1 as smallint), 4)", "1.0000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1234 as smallint), '#,###,###,###,###,###,##0.0000000')",
"1,234.0000000",
"VARCHAR NOT NULL");

// test with integer type
f.checkString("format_number(cast(1 as integer), 4)", "1.0000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1234 as integer), '#,###,###,###,###,###,##0.0000000')",
"1,234.0000000",
"VARCHAR NOT NULL");

// test with bigint type
f.checkString("format_number(cast(0 as bigint), 0)", "0",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1 as bigint), 4)", "1.0000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1234 as bigint), 7)", "1,234.0000000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1234 as bigint), '#,###,###,###,###,###,##0.0000000')",
"1,234.0000000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(-1 as bigint), 4)", "-1.0000",
"VARCHAR NOT NULL");

// test with float type
f.checkString("format_number(cast(12332.123456 as float), 4)", "12,332.1235",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(123456.123456789 as float), '########.###')",
"123456.123",
"VARCHAR NOT NULL");

// test with double type
f.checkString("format_number(cast(1234567.123456789 as double), 7)", "1,234,567.1234568",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(1234567.123456789 as double), '##,###,###.##')",
"1,234,567.12",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(-0.123456789 as double), 15)", "-0.123456789000000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(-0.123456789 as double),"
+ " '#,###,###,###,###,###,##0.000000000000000')",
"-0.123456789000000",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(0.000000 as double), 1)", "0.0",
"VARCHAR NOT NULL");
f.checkString("format_number(cast(0.000000 as double), '#,###,###,###,###,###,##0.0')",
"0.0",
"VARCHAR NOT NULL");

// test with decimal type
f.checkString("format_number(1234567.123456789, 7)", "1,234,567.1234568",
"VARCHAR NOT NULL");
f.checkString("format_number(1234567.123456789, '##,###,###.##')",
"1,234,567.12",
"VARCHAR NOT NULL");
f.checkString("format_number(-0.123456789, 15)", "-0.123456789000000",
"VARCHAR NOT NULL");
f.checkString("format_number(-0.123456789,"
+ " '#,###,###,###,###,###,##0.000000000000000')",
"-0.123456789000000",
"VARCHAR NOT NULL");
f.checkString("format_number(0.000000, 1)", "0.0",
"VARCHAR NOT NULL");
f.checkString("format_number(0.0, '#,###,###,###,###,###,##0.0000')",
"0.0000",
"VARCHAR NOT NULL");

// test with illegal argument
f.checkFails("format_number(12332.123456, -1)",
"Illegal arguments for 'FORMAT_NUMBER' function:"
+ " negative decimal value not allowed",
true);

// test with null values
f.checkNull("format_number(cast(null as integer), 1)");
f.checkNull("format_number(0, cast(null as integer))");
f.checkNull("format_number(0, cast(null as varchar))");
f.checkNull("format_number(cast(null as integer), cast(null as integer))");
f.checkNull("format_number(cast(null as integer), cast(null as varchar))");
};
f0.forEachLibrary(list(SqlLibrary.HIVE, SqlLibrary.SPARK), consumer);
}

@Test void testTrimFunc() {
final SqlOperatorFixture f = fixture();
f.setFor(SqlStdOperatorTable.TRIM, VmName.EXPAND);
Expand Down

0 comments on commit 50ad15c

Please sign in to comment.