diff --git a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLOperandTypes.java b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLOperandTypes.java index dfdebb233c..abf37e6839 100644 --- a/core/src/main/java/org/opensearch/sql/calcite/utils/PPLOperandTypes.java +++ b/core/src/main/java/org/opensearch/sql/calcite/utils/PPLOperandTypes.java @@ -104,6 +104,13 @@ private PPLOperandTypes() {} UDFOperandMetadata.wrap( OperandTypes.family( SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, SqlTypeFamily.INTEGER)); + + public static final UDFOperandMetadata STRING_OR_STRING_INTEGER = + UDFOperandMetadata.wrap( + (CompositeOperandTypeChecker) + OperandTypes.family(SqlTypeFamily.CHARACTER) + .or(OperandTypes.family(SqlTypeFamily.CHARACTER, SqlTypeFamily.INTEGER))); + public static final UDFOperandMetadata STRING_STRING_INTEGER_INTEGER = UDFOperandMetadata.wrap( OperandTypes.family( diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java index edc9ebb0df..0fe042a515 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java @@ -156,6 +156,7 @@ public enum BuiltinFunctionName { /** Text Functions. */ TOSTRING(FunctionName.of("tostring")), + TONUMBER(FunctionName.of("tonumber")), /** IP Functions. */ CIDRMATCH(FunctionName.of("cidrmatch")), diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java index 36d617af01..26960192f0 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLBuiltinOperators.java @@ -67,6 +67,7 @@ import org.opensearch.sql.expression.function.udf.RexExtractMultiFunction; import org.opensearch.sql.expression.function.udf.RexOffsetFunction; import org.opensearch.sql.expression.function.udf.SpanFunction; +import org.opensearch.sql.expression.function.udf.ToNumberFunction; import org.opensearch.sql.expression.function.udf.ToStringFunction; import org.opensearch.sql.expression.function.udf.condition.EarliestFunction; import org.opensearch.sql.expression.function.udf.condition.EnhancedCoalesceFunction; @@ -412,6 +413,7 @@ public class PPLBuiltinOperators extends ReflectiveSqlOperatorTable { RELEVANCE_QUERY_FUNCTION_INSTANCE.toUDF("multi_match", false); public static final SqlOperator NUMBER_TO_STRING = new NumberToStringFunction().toUDF("NUMBER_TO_STRING"); + public static final SqlOperator TONUMBER = new ToNumberFunction().toUDF("TONUMBER"); public static final SqlOperator TOSTRING = new ToStringFunction().toUDF("TOSTRING"); public static final SqlOperator WIDTH_BUCKET = new org.opensearch.sql.expression.function.udf.binning.WidthBucketFunction() diff --git a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java index d72b6a7a25..76a6755ad5 100644 --- a/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java +++ b/core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java @@ -213,6 +213,7 @@ import static org.opensearch.sql.expression.function.BuiltinFunctionName.TIMESTAMPDIFF; import static org.opensearch.sql.expression.function.BuiltinFunctionName.TIME_FORMAT; import static org.opensearch.sql.expression.function.BuiltinFunctionName.TIME_TO_SEC; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.TONUMBER; import static org.opensearch.sql.expression.function.BuiltinFunctionName.TOSTRING; import static org.opensearch.sql.expression.function.BuiltinFunctionName.TO_DAYS; import static org.opensearch.sql.expression.function.BuiltinFunctionName.TO_SECONDS; @@ -956,6 +957,7 @@ void populate() { registerOperator(WEEKOFYEAR, PPLBuiltinOperators.WEEK); registerOperator(INTERNAL_PATTERN_PARSER, PPLBuiltinOperators.PATTERN_PARSER); + registerOperator(TONUMBER, PPLBuiltinOperators.TONUMBER); registerOperator(TOSTRING, PPLBuiltinOperators.TOSTRING); register( TOSTRING, @@ -1131,7 +1133,6 @@ void populate() { SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER)), false)); - register( LOG, (FunctionImp2) diff --git a/core/src/main/java/org/opensearch/sql/expression/function/udf/ToNumberFunction.java b/core/src/main/java/org/opensearch/sql/expression/function/udf/ToNumberFunction.java new file mode 100644 index 0000000000..2557121b0c --- /dev/null +++ b/core/src/main/java/org/opensearch/sql/expression/function/udf/ToNumberFunction.java @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function.udf; + +import java.math.BigInteger; +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.function.Strict; +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.ReturnTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; +import org.opensearch.sql.calcite.utils.PPLOperandTypes; +import org.opensearch.sql.expression.function.ImplementorUDF; +import org.opensearch.sql.expression.function.UDFOperandMetadata; + +/** + * A custom implementation of number/boolean to string . + * + *

This operator is necessary because tostring has following requirements "binary" Converts a + * number to a binary value. "hex" Converts the number to a hexadecimal value. "commas" Formats the + * number with commas. If the number includes a decimal, the function rounds the number to nearest + * two decimal places. "duration" Converts the value in seconds to the readable time format + * HH:MM:SS. if not format parameter provided, then consider value as boolean + */ +public class ToNumberFunction extends ImplementorUDF { + public ToNumberFunction() { + super( + new org.opensearch.sql.expression.function.udf.ToNumberFunction.ToNumberImplementor(), + NullPolicy.ANY); + } + + @Override + public SqlReturnTypeInference getReturnTypeInference() { + return ReturnTypes.DOUBLE_FORCE_NULLABLE; + } + + @Override + public UDFOperandMetadata getOperandMetadata() { + return PPLOperandTypes.STRING_OR_STRING_INTEGER; + } + + public static class ToNumberImplementor implements NotNullImplementor { + + @Override + public Expression implement( + RexToLixTranslator translator, RexCall call, List translatedOperands) { + Expression fieldValue = translatedOperands.get(0); + int base = 10; + if (translatedOperands.size() > 1) { + Expression baseExpr = translatedOperands.get(1); + return Expressions.call(ToNumberFunction.class, "toNumber", fieldValue, baseExpr); + } else { + return Expressions.call(ToNumberFunction.class, "toNumber", fieldValue); + } + } + } + + @Strict + public static Number toNumber(String numStr) { + return toNumber(numStr, 10); + } + + @Strict + public static Number toNumber(String numStr, int base) { + if (base < 2 || base > 36) { + throw new IllegalArgumentException("Base has to be between 2 and 36."); + } + Number result = null; + try { + if (base == 10) { + if (numStr.contains(".")) { + result = Double.parseDouble(numStr); + } else { + result = Long.parseLong(numStr); + } + } else { + BigInteger bigInteger = new BigInteger(numStr, base); + result = bigInteger.longValue(); + } + } catch (Exception e) { + + } + return result; + } +} diff --git a/core/src/test/java/org/opensearch/sql/expression/function/udf/ToNumberFunctionTest.java b/core/src/test/java/org/opensearch/sql/expression/function/udf/ToNumberFunctionTest.java new file mode 100644 index 0000000000..34ed102ec5 --- /dev/null +++ b/core/src/test/java/org/opensearch/sql/expression/function/udf/ToNumberFunctionTest.java @@ -0,0 +1,176 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.expression.function.udf; + +import static org.junit.jupiter.api.Assertions.*; + +import org.apache.calcite.sql.type.ReturnTypes; +import org.junit.jupiter.api.Test; +import org.opensearch.sql.calcite.utils.PPLOperandTypes; + +public class ToNumberFunctionTest { + + private final ToNumberFunction function = new ToNumberFunction(); + + @Test + void testGetReturnTypeInference() { + assertEquals(ReturnTypes.DOUBLE_FORCE_NULLABLE, function.getReturnTypeInference()); + } + + @Test + void testGetOperandMetadata() { + assertEquals(PPLOperandTypes.STRING_OR_STRING_INTEGER, function.getOperandMetadata()); + } + + @Test + void testToNumberWithDefaultBase() { + assertEquals(123L, ToNumberFunction.toNumber("123")); + assertEquals(0L, ToNumberFunction.toNumber("0")); + assertEquals(-456L, ToNumberFunction.toNumber("-456")); + assertEquals(123.45, ToNumberFunction.toNumber("123.45")); + assertEquals(-123.45, ToNumberFunction.toNumber("-123.45")); + assertEquals(0.5, ToNumberFunction.toNumber("0.5")); + assertEquals(-0.5, ToNumberFunction.toNumber("-0.5")); + } + + @Test + void testToNumberWithBase10() { + assertEquals(123L, ToNumberFunction.toNumber("123", 10)); + assertEquals(0L, ToNumberFunction.toNumber("0", 10)); + assertEquals(-456L, ToNumberFunction.toNumber("-456", 10)); + assertEquals(123.45, ToNumberFunction.toNumber("123.45", 10)); + assertEquals(-123.45, ToNumberFunction.toNumber("-123.45", 10)); + } + + @Test + void testToNumberWithBase2() { + assertEquals(5L, ToNumberFunction.toNumber("101", 2)); + assertEquals(0L, ToNumberFunction.toNumber("0", 2)); + assertEquals(1L, ToNumberFunction.toNumber("1", 2)); + assertEquals(7L, ToNumberFunction.toNumber("111", 2)); + assertEquals(10L, ToNumberFunction.toNumber("1010", 2)); + } + + @Test + void testToNumberWithBase8() { + assertEquals(64L, ToNumberFunction.toNumber("100", 8)); + assertEquals(8L, ToNumberFunction.toNumber("10", 8)); + assertEquals(83L, ToNumberFunction.toNumber("123", 8)); + assertEquals(511L, ToNumberFunction.toNumber("777", 8)); + } + + @Test + void testToNumberWithBase16() { + assertEquals(255L, ToNumberFunction.toNumber("FF", 16)); + assertEquals(16L, ToNumberFunction.toNumber("10", 16)); + assertEquals(171L, ToNumberFunction.toNumber("AB", 16)); + assertEquals(291L, ToNumberFunction.toNumber("123", 16)); + assertEquals(4095L, ToNumberFunction.toNumber("FFF", 16)); + } + + @Test + void testToNumberWithBase36() { + assertEquals(35L, ToNumberFunction.toNumber("Z", 36)); + assertEquals(1295L, ToNumberFunction.toNumber("ZZ", 36)); + assertEquals(46655L, ToNumberFunction.toNumber("ZZZ", 36)); + } + + @Test + void testToNumberWithDecimalBase2() { + assertEquals(2L, ToNumberFunction.toNumber("10", 2)); + assertEquals(1L, ToNumberFunction.toNumber("1", 2)); + assertEquals(3L, ToNumberFunction.toNumber("11", 2)); + } + + @Test + void testToNumberWithDecimalBase16() { + assertEquals(255L, ToNumberFunction.toNumber("FF", 16)); + assertEquals(16L, ToNumberFunction.toNumber("10", 16)); + assertEquals(171L, ToNumberFunction.toNumber("AB", 16)); + } + + @Test + void testToNumberWithNegativeDecimal() { + assertEquals(-2L, ToNumberFunction.toNumber("-10", 2)); + assertEquals(-255L, ToNumberFunction.toNumber("-FF", 16)); + assertEquals(-123.45, ToNumberFunction.toNumber("-123.45", 10)); + } + + @Test + void testToNumberWithEmptyFractionalPart() { + assertEquals(123.0, ToNumberFunction.toNumber("123.", 10)); + assertEquals(255L, ToNumberFunction.toNumber("FF", 16)); + assertEquals(5L, ToNumberFunction.toNumber("101", 2)); + } + + @Test + void testToNumberWithZeroIntegerPart() { + assertEquals(0.5, ToNumberFunction.toNumber("0.5", 10)); + assertEquals(0L, ToNumberFunction.toNumber("0", 2)); + } + + @Test + void testToNumberInvalidBase() { + assertThrows( + IllegalArgumentException.class, + () -> { + ToNumberFunction.toNumber("123", 1); + }); + + assertThrows( + IllegalArgumentException.class, + () -> { + ToNumberFunction.toNumber("123", 37); + }); + + assertThrows( + IllegalArgumentException.class, + () -> { + ToNumberFunction.toNumber("123", 0); + }); + + assertThrows( + IllegalArgumentException.class, + () -> { + ToNumberFunction.toNumber("123", -1); + }); + } + + @Test + void testToNumberInvalidDigits() { + assertEquals(null, ToNumberFunction.toNumber("12A", 10)); + assertEquals(null, ToNumberFunction.toNumber("102", 2)); + assertEquals(null, ToNumberFunction.toNumber("101.101", 2)); + assertEquals(null, ToNumberFunction.toNumber("189", 8)); + assertEquals(null, ToNumberFunction.toNumber("GHI", 16)); + assertEquals(null, ToNumberFunction.toNumber("FF.8", 16)); + } + + @Test + void testToNumberEdgeCases() { + assertEquals(0L, ToNumberFunction.toNumber("0", 2)); + assertEquals(0L, ToNumberFunction.toNumber("0", 36)); + assertEquals(0.0, ToNumberFunction.toNumber("0.0", 10)); + assertEquals(0.0, ToNumberFunction.toNumber("0.000", 10)); + } + + @Test + void testToNumberLargeNumbers() { + assertEquals( + (long) Integer.MAX_VALUE, ToNumberFunction.toNumber(String.valueOf(Integer.MAX_VALUE), 10)); + assertEquals( + (long) Integer.MIN_VALUE, ToNumberFunction.toNumber(String.valueOf(Integer.MIN_VALUE), 10)); + } + + @Test + void testToNumberCaseInsensitivity() { + assertEquals(255L, ToNumberFunction.toNumber("ff", 16)); + assertEquals(255L, ToNumberFunction.toNumber("FF", 16)); + assertEquals(255L, ToNumberFunction.toNumber("fF", 16)); + assertEquals(171L, ToNumberFunction.toNumber("ab", 16)); + assertEquals(171L, ToNumberFunction.toNumber("AB", 16)); + } +} diff --git a/docs/user/ppl/functions/conversion.rst b/docs/user/ppl/functions/conversion.rst index 82d760cc3c..c55e868f2c 100644 --- a/docs/user/ppl/functions/conversion.rst +++ b/docs/user/ppl/functions/conversion.rst @@ -118,6 +118,58 @@ Use string in comparison operator example :: +------+-------+------+-------+------+------+------+ + +TONUMBER +-------- + +Description +>>>>>>>>>>> + +The following usage options are available, depending on the parameter types and the number of parameters. + +Usage: tonumber(string, [base]) converts the value in first argument to provided base type string in second argument. If second argument is not provided, then it converts to base 10 number representation. + +Return type: Number + + +You can use this function with the eval commands and as part of eval expressions. +Base values can be between 2 and 36. The maximum value supported for base 10 is +(2-2^-52)·2^1023 and minimum is -(2-2^-52)·2^1023. +The maximum for other supported bases is 2^63-1 (or 7FFFFFFFFFFFFFFF) and minimum is -2^63 (or -7FFFFFFFFFFFFFFF). + +You can use this function to convert a string representation of a binary number to return the corresponding number in base 10. + +Following example converts a string in binary to the number representation:: + + os> source=people | eval int_value = tonumber('010101',2) | fields int_value | head 1 + fetched rows / total rows = 1/1 + +-----------+ + | int_value | + |-----------| + | 21.0 | + +-----------+ + + +Following example converts a string in hex to the number representation:: + + os> source=people | eval int_value = tonumber('FA34',16) | fields int_value | head 1 + fetched rows / total rows = 1/1 + +-----------+ + | int_value | + |-----------| + | 64052.0 | + +-----------+ + +Following example converts a string in decimal to the number representation:: + + os> source=people | eval int_value = tonumber('4598') | fields int_value | head 1 + fetched rows / total rows = 1/1 + +-----------+ + | int_value | + |-----------| + | 4598.0 | + +-----------+ + + TOSTRING ----------- @@ -200,4 +252,3 @@ Example:: |-------------| | TRUE | +-------------+ - diff --git a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 index b3587c319c..8d64bfabb1 100644 --- a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 @@ -408,6 +408,7 @@ STRFTIME: 'STRFTIME'; // TEXT FUNCTIONS SUBSTR: 'SUBSTR'; SUBSTRING: 'SUBSTRING'; +TONUMBER: 'TONUMBER'; TOSTRING: 'TOSTRING'; LTRIM: 'LTRIM'; RTRIM: 'RTRIM'; diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index d115f3dec3..cc5d3d4aa8 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -1304,6 +1304,7 @@ textFunctionName | LOCATE | REPLACE | REVERSE + | TONUMBER | REGEXP_REPLACE ; @@ -1509,6 +1510,7 @@ searchableKeyWord | USING | VALUE | CAST + | TONUMBER | TOSTRING | GET_FORMAT | EXTRACT diff --git a/ppl/src/test/java/org/opensearch/sql/ppl/calcite/CalcitePPLToNumberFunctionTest.java b/ppl/src/test/java/org/opensearch/sql/ppl/calcite/CalcitePPLToNumberFunctionTest.java new file mode 100644 index 0000000000..bd4f2c293b --- /dev/null +++ b/ppl/src/test/java/org/opensearch/sql/ppl/calcite/CalcitePPLToNumberFunctionTest.java @@ -0,0 +1,163 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.ppl.calcite; + +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.test.CalciteAssert; +import org.junit.Test; + +public class CalcitePPLToNumberFunctionTest extends CalcitePPLAbstractTest { + + public CalcitePPLToNumberFunctionTest() { + super(CalciteAssert.SchemaSpec.SCOTT_WITH_TEMPORAL); + } + + @Test + public void testNumberBinary() { + String ppl = "source=EMP | eval int_value = tonumber('010101',2) | fields int_value|head 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(int_value=[TONUMBER('010101':VARCHAR, 2)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "int_value=21.0\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('010101', 2) `int_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberHex() { + String ppl = "source=EMP | eval int_value = tonumber('FA34',16) | fields int_value|head 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(int_value=[TONUMBER('FA34':VARCHAR, 16)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "int_value=64052.0\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('FA34', 16) `int_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberHexMinLimit() { + String ppl = + "source=EMP | eval long_value = tonumber('-7FFFFFFFFFFFFFFF',16) | fields long_value|head" + + " 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(long_value=[TONUMBER('-7FFFFFFFFFFFFFFF':VARCHAR, 16)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "long_value=-9.223372036854776E18\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('-7FFFFFFFFFFFFFFF', 16) `long_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberHexMaxLimit() { + String ppl = + "source=EMP | eval long_value = tonumber('7FFFFFFFFFFFFFFF',16) | fields long_value|head" + + " 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(long_value=[TONUMBER('7FFFFFFFFFFFFFFF':VARCHAR, 16)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "long_value=9.223372036854776E18\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('7FFFFFFFFFFFFFFF', 16) `long_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberHexOverNegativeMaxLimit() { + String ppl = + "source=EMP | eval long_value = tonumber('-FFFFFFFFFFFFFFFF',16) | fields long_value|head" + + " 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(long_value=[TONUMBER('-FFFFFFFFFFFFFFFF':VARCHAR, 16)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "long_value=1.0\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('-FFFFFFFFFFFFFFFF', 16) `long_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberHexOverPositiveMaxLimit() { + String ppl = + "source=EMP | eval long_value = tonumber('FFFFFFFFFFFFFFFF',16) | fields long_value|head 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(long_value=[TONUMBER('FFFFFFFFFFFFFFFF':VARCHAR, 16)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "long_value=-1.0\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('FFFFFFFFFFFFFFFF', 16) `long_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumber() { + String ppl = "source=EMP | eval int_value = tonumber('4598') | fields int_value|head 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(int_value=[TONUMBER('4598':VARCHAR)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "int_value=4598.0\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = "SELECT `TONUMBER`('4598') `int_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + verifyPPLToSparkSQL(root, expectedSparkSql); + } + + @Test + public void testNumberDecimal() { + String ppl = "source=EMP | eval int_value = tonumber('4598.54922') | fields int_value|head 1"; + RelNode root = getRelNode(ppl); + String expectedLogical = + "LogicalSort(fetch=[1])\n" + + " LogicalProject(int_value=[TONUMBER('4598.54922':VARCHAR)])\n" + + " LogicalTableScan(table=[[scott, EMP]])\n"; + verifyLogical(root, expectedLogical); + String expectedResult = "int_value=4598.54922\n"; + verifyResult(root, expectedResult); + + String expectedSparkSql = + "SELECT `TONUMBER`('4598.54922') `int_value`\nFROM `scott`.`EMP`\nLIMIT 1"; + verifyPPLToSparkSQL(root, expectedSparkSql); + } +}