Skip to content

Commit 28cb6a0

Browse files
committed
Fix casting double 0.0 to string
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent 7ccdcd1 commit 28cb6a0

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.apache.calcite.rex.RexLiteral;
1616
import org.apache.calcite.rex.RexNode;
1717
import org.apache.calcite.sql.SqlIntervalQualifier;
18+
import org.apache.calcite.sql.fun.SqlLibraryOperators;
1819
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
1920
import org.apache.calcite.sql.parser.SqlParserPos;
2021
import org.apache.calcite.sql.type.SqlTypeName;
@@ -28,9 +29,17 @@
2829
import org.opensearch.sql.expression.function.PPLBuiltinOperators;
2930

3031
public class ExtendedRexBuilder extends RexBuilder {
32+
/**
33+
* Formats double values in PPL using a non-scientific notation, displaying up to 16 digits after
34+
* the decimal point. This formatting is consistent with PPL V2.
35+
*/
36+
private static final String DOUBLE_FORMAT = "0.0###############";
37+
38+
private final RexLiteral doubleFormat;
3139

3240
public ExtendedRexBuilder(RexBuilder rexBuilder) {
3341
super(rexBuilder.getTypeFactory());
42+
doubleFormat = makeLiteral(DOUBLE_FORMAT);
3443
}
3544

3645
public RexNode coalesce(RexNode... nodes) {
@@ -150,6 +159,15 @@ public RexNode makeCast(
150159
String.format(Locale.ROOT, "Cannot cast from %s to %s", argExprType, udt.name()));
151160
};
152161
}
162+
// If casting an approximate numeric (e.g. double) to a character type and no format is
163+
// specified,
164+
// use the custom double format to ensure non\-scientific notation with up to 16 decimal digits.
165+
// This patch is necessary because Calcite's built-in CAST converts 0.0 to 0E0 as string.
166+
else if (SqlTypeUtil.isApproximateNumeric(exp.getType())
167+
&& SqlTypeUtil.isCharacter(type)
168+
&& format.getType().getSqlTypeName() == SqlTypeName.NULL) {
169+
return makeCall(type, SqlLibraryOperators.FORMAT_NUMBER, List.of(exp, doubleFormat));
170+
}
153171
return super.makeCast(pos, type, exp, matchNullability, safe, format);
154172
}
155173
}

integ-test/src/test/java/org/opensearch/sql/ppl/CastFunctionIT.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package org.opensearch.sql.ppl;
77

8+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT;
89
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATATYPE_NONNUMERIC;
910
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATATYPE_NUMERIC;
1011
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DATE_FORMATS;
@@ -18,6 +19,7 @@
1819
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
1920

2021
import java.io.IOException;
22+
import java.util.Locale;
2123
import org.json.JSONObject;
2224
import org.junit.Test;
2325
import org.opensearch.sql.common.antlr.SyntaxCheckException;
@@ -432,4 +434,17 @@ public void testCastToIP() throws IOException {
432434
"IP address string 'invalid_ip' is not valid. Error details: invalid_ip IP Address error:"
433435
+ " validation options do not allow you to specify a non-segmented single value");
434436
}
437+
438+
@Test
439+
public void testCastDoubleAsString() throws IOException {
440+
JSONObject actual =
441+
executeQuery(
442+
String.format(
443+
Locale.ROOT,
444+
"source=%s | head 1 | eval d= cast(0 as double) | eval s=cast(d as string) | fields"
445+
+ " s",
446+
TEST_INDEX_ACCOUNT));
447+
verifySchema(actual, schema("s", "string"));
448+
verifyDataRows(actual, rows("0.0"));
449+
}
435450
}

0 commit comments

Comments
 (0)