Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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,12 @@ public RexNode makeCast(
String.format(Locale.ROOT, "Cannot cast from %s to %s", argExprType, udt.name()));
};
}
// Use a custom operator when casting an approximate numeric (e.g. double) to a character type.
// 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)) {
// NUMBER_TO_STRING first box the number, then invoke its toString method
return makeCall(type, PPLBuiltinOperators.NUMBER_TO_STRING, List.of(exp));
}
return super.makeCast(pos, type, exp, matchNullability, safe, format);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
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.ModFunction;
import org.opensearch.sql.expression.function.udf.math.NumberToStringFunction;

/** Defines functions and operators that are implemented only by PPL */
public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable {
Expand Down Expand Up @@ -355,6 +356,8 @@ 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 NUMBER_TO_STRING =
new NumberToStringFunction().toUDF("NUMBER_TO_STRING");

/**
* 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,51 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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

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.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 number to string cast.
*
* <p>This operator is necessary because Calcite's built-in CAST converts 0.0 to 0E0 when casting it
* to string.
*/
public class NumberToStringFunction extends ImplementorUDF {
public NumberToStringFunction() {
super(new NumberToStringImplementor(), NullPolicy.ANY);
}

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

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

public static class NumberToStringImplementor implements NotNullImplementor {

@Override
public Expression implement(
RexToLixTranslator translator, RexCall call, List<Expression> translatedOperands) {
Expression operand = translatedOperands.get(0);
return Expressions.call(Expressions.box(operand), "toString");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expressions.call(Primitive.ofBox(operand.getType()).getBoxClass(), "toString", operand)

would be a little better since it doesn't need to create a boxed object for non-nullable fields. But the performance difference should be very trivial

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Updated the implementation.

}
}
}
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