Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -106,6 +106,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 @@ -160,6 +160,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 @@ -218,6 +218,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 @@ -983,6 +984,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 @@ -1200,7 +1202,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,102 @@
/*
* 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;

/**
* The following usage options are available, depending on the parameter types and the number of
* parameters.
*
* <p><b>Usage:</b> {@code tonumber(string, [base])} converts the value in the first argument. The
* second argument describes the base of the first argument. If the second argument is not provided,
* the value is converted using base 10.
*
* <p><b>Return type:</b> Number
*
* <p>You can use this function with the eval commands and as part of eval expressions.
*
* <p>Base values can range from 2 to 36. The maximum value supported for base 10 is {@code +(2 −
* 2^-52) · 2^1023} and the minimum is {@code −(2 − 2^-52) · 2^1023}.
*
* <p>The maximum for other supported bases is {@code 2^63 − 1} (or {@code 7FFFFFFFFFFFFFFF}) and
* the minimum is {@code -2^63} (or {@code -7FFFFFFFFFFFFFFF}).
*/
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));
}
}
Loading
Loading