Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0ed23de
PPL tostring() implementation issue #4492
asifabashar Oct 16, 2025
5a5b778
removed sql changes
asifabashar Oct 16, 2025
9d71a95
doc changes
asifabashar Oct 16, 2025
be2c2e2
docs changes
asifabashar Oct 16, 2025
fc763a4
reverted string doc changes
asifabashar Oct 16, 2025
a04eb14
removed extra word
asifabashar Oct 16, 2025
590a8e6
added any type
asifabashar Oct 16, 2025
6938e2c
doc formatting fixes
asifabashar Oct 16, 2025
314fccd
description for boolean example
asifabashar Oct 16, 2025
0ee17b9
added format_time call from calcite , added duration_millis as splunk…
asifabashar Oct 17, 2025
6e24aa3
added doc update to specifically set 2nd argument as optional
asifabashar Oct 17, 2025
454cfc8
mentioned as value instead of number specifically
asifabashar Oct 17, 2025
b221e8c
fixed wrong bullet point
asifabashar Oct 17, 2025
a0a89c4
issue #4514 tonumber function as part of roadmap #4287
asifabashar Oct 20, 2025
ea9ba0f
added more unit tests
asifabashar Oct 20, 2025
370b9bc
Update docs/user/ppl/functions/conversion.rst
asifabashar Oct 22, 2025
f070684
fix per recommendation
asifabashar Oct 22, 2025
a2accf5
updated recommended changes
asifabashar Oct 22, 2025
3adda5d
updated recommended changes
asifabashar Oct 22, 2025
17b1d86
updated recommended changes
asifabashar Oct 22, 2025
eca7d13
updated recommended changes
asifabashar Oct 22, 2025
e780c15
updated recommended changes
asifabashar Oct 22, 2025
e3884b0
updated recommended changes
asifabashar Oct 22, 2025
042d3e8
spotless apply
asifabashar Oct 22, 2025
38ce420
merge conflict fix
asifabashar Oct 28, 2025
58d8e1b
merge conflict fix
asifabashar Oct 28, 2025
34f8635
added recommended changes
asifabashar Nov 2, 2025
8279bf3
fixed doctest for conversion.rst and applied spotless
asifabashar Nov 5, 2025
b5346a6
removed decimal point from hex
asifabashar Nov 5, 2025
7cf867b
added doctest fixes
asifabashar Nov 5, 2025
8d9db4e
fixed merged conflicts
asifabashar Nov 6, 2025
46f010b
removed unused variables
asifabashar Nov 6, 2025
b7afa17
spotless
asifabashar Nov 6, 2025
0f0125a
made recommended changes to simply parse to number, null when malformed
asifabashar Nov 6, 2025
804db79
hex max limit doc and unit tests
asifabashar Nov 9, 2025
7588397
fix to spotless
asifabashar Nov 9, 2025
04a5564
Merge branch 'main' into feature_tonumber
asifabashar Nov 12, 2025
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 @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public enum BuiltinFunctionName {

/** Text Functions. */
TOSTRING(FunctionName.of("tostring")),
TONUMBER(FunctionName.of("tonumber")),

/** IP Functions. */
CIDRMATCH(FunctionName.of("cidrmatch")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1131,7 +1133,6 @@ void populate() {
SqlTypeFamily.INTEGER,
SqlTypeFamily.INTEGER)),
false));

register(
LOG,
(FunctionImp2)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 .
*
* <p>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<Expression> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
53 changes: 52 additions & 1 deletion docs/user/ppl/functions/conversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------

Expand Down Expand Up @@ -200,4 +252,3 @@ Example::
|-------------|
| TRUE |
+-------------+

Loading
Loading