Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ public RexNode makeCast(
String.format(Locale.ROOT, "Cannot cast from %s to %s", argExprType, udt.name()));
};
}
// If casting an approximate numeric (e.g. double) to a character type and no format is
// specified,
// use the custom double format to ensure non-scientific notation with up to 16 decimal digits.
// This patch is necessary because Calcite's built-in CAST converts 0.0 to 0E0 as string.
else if (SqlTypeUtil.isApproximateNumeric(exp.getType())
&& SqlTypeUtil.isCharacter(type)
&& format.getType().getSqlTypeName() == SqlTypeName.NULL) {
// Use a custom FORMAT_NUMBER which first convert the number to a BigDecimal, then
// calls SqlFunctions.formatNumber
return makeCall(type, PPLBuiltinOperators.FORMAT_NUMBER, List.of(exp));
}
return super.makeCast(pos, type, exp, matchNullability, safe, format);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.opensearch.sql.expression.function.udf.math.ConvFunction;
import org.opensearch.sql.expression.function.udf.math.DivideFunction;
import org.opensearch.sql.expression.function.udf.math.EulerFunction;
import org.opensearch.sql.expression.function.udf.math.FormatNumberFunction;
import org.opensearch.sql.expression.function.udf.math.ModFunction;

/** Defines functions and operators that are implemented only by PPL */
Expand Down Expand Up @@ -355,6 +356,7 @@ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable {
RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("query_string", false);
public static final SqlOperator MULTI_MATCH =
RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multi_match", false);
public static final SqlOperator FORMAT_NUMBER = new FormatNumberFunction().toUDF("FORMAT_NUMBER");

/**
* Returns the PPL specific operator table, creating it if necessary.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.function.udf.math;

import java.math.BigDecimal;
import java.util.List;
import org.apache.calcite.adapter.enumerable.NotNullImplementor;
import org.apache.calcite.adapter.enumerable.NullPolicy;
import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.opensearch.sql.calcite.utils.PPLOperandTypes;
import org.opensearch.sql.calcite.utils.PPLReturnTypes;
import org.opensearch.sql.expression.function.ImplementorUDF;
import org.opensearch.sql.expression.function.UDFOperandMetadata;

/**
* A custom implementation of FormatNumber in replace of SqlLibraryOperators.FORMAT_NUMBER
*
* <p>The operators SqlLibraryOperators.FORMAT_NUMBER will convert a float to double with code
* equivalent to {@code ((Number) float).doubleValue()}, which will lead to a loss in precision.
* E.g. 6.2 becomes 6.199999809265137. This operator fix the problem by converting the number to a
* BigDecimal before formatting it.
*/
public class FormatNumberFunction extends ImplementorUDF {

/**
* Formats double values in PPL using a non-scientific notation, displaying up to 16 digits after
* the decimal point. This formatting is consistent with PPL V2.
*/
private static final String DOUBLE_FORMAT = "0.0###############";

public FormatNumberFunction() {
super(new FormatNumberImplementor(), NullPolicy.ANY);
}

@Override
public SqlReturnTypeInference getReturnTypeInference() {
return PPLReturnTypes.STRING_FORCE_NULLABLE;
}

@Override
public UDFOperandMetadata getOperandMetadata() {
return PPLOperandTypes.NUMERIC;
}

public static class FormatNumberImplementor implements NotNullImplementor {

@Override
public Expression implement(
RexToLixTranslator translator, RexCall call, List<Expression> translatedOperands) {
Expression operand = translatedOperands.get(0);
Expression decimal =
Expressions.call(
FormatNumberImplementor.class, "convertNumber", Expressions.box(operand));
return Expressions.call(
SqlFunctions.class, "formatNumber", decimal, Expressions.constant(DOUBLE_FORMAT));
}

public static BigDecimal convertNumber(Number number) {
return new BigDecimal(number.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.opensearch.sql.util.MatcherUtils.verifySchema;

import java.io.IOException;
import java.util.Locale;
import org.json.JSONObject;
import org.junit.Test;
import org.opensearch.sql.common.antlr.SyntaxCheckException;
Expand Down Expand Up @@ -432,4 +433,17 @@ public void testCastToIP() throws IOException {
"IP address string 'invalid_ip' is not valid. Error details: invalid_ip IP Address error:"
+ " validation options do not allow you to specify a non-segmented single value");
}

@Test
public void testCastDoubleAsString() throws IOException {
JSONObject actual =
executeQuery(
String.format(
Locale.ROOT,
"source=%s | head 1 | eval d = cast(0 as double) | eval s = cast(d as string) |"
+ " fields s",
TEST_INDEX_STATE_COUNTRY));
verifySchema(actual, schema("s", "string"));
verifyDataRows(actual, rows("0.0"));
}
}
Loading