diff --git a/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/decay.md b/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/decay.md index 8d72318865141..cc4f13d50a380 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/decay.md +++ b/docs/reference/query-languages/esql/_snippets/functions/functionNamedParams/decay.md @@ -3,7 +3,7 @@ **Supported function named parameters** `offset` -: (double, integer, long, time_duration, keyword, text) Distance from the origin where no decay occurs. +: (double, integer, long, unsigned_long, time_duration, keyword, text) Distance from the origin where no decay occurs. `type` : (keyword) Decay function to use: linear, exponential or gaussian. diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/decay.md b/docs/reference/query-languages/esql/_snippets/functions/types/decay.md index 2b64fee072ddc..80351bd083b9e 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/decay.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/decay.md @@ -12,4 +12,5 @@ | geo_point | geo_point | text | named parameters | double | | integer | integer | integer | named parameters | double | | long | long | long | named parameters | double | +| unsigned_long | unsigned_long | unsigned_long | named parameters | double | diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/decay.json b/docs/reference/query-languages/esql/kibana/definition/functions/decay.json index e2ca30b485983..674df698738fe 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/decay.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/decay.json @@ -251,6 +251,37 @@ ], "variadic" : false, "returnType" : "double" + }, + { + "params" : [ + { + "name" : "value", + "type" : "unsigned_long", + "optional" : false, + "description" : "The input value to apply decay scoring to." + }, + { + "name" : "origin", + "type" : "unsigned_long", + "optional" : false, + "description" : "Central point from which the distances are calculated." + }, + { + "name" : "scale", + "type" : "unsigned_long", + "optional" : false, + "description" : "Distance from the origin where the function returns the decay value." + }, + { + "name" : "options", + "type" : "function_named_parameters", + "mapParams" : "{name='offset', values=[], description='Distance from the origin where no decay occurs.'}, {name='type', values=[], description='Decay function to use: linear, exponential or gaussian.'}, {name='decay', values=[], description='Multiplier value returned at the scale distance from the origin.'}", + "optional" : true, + "description" : "" + } + ], + "variadic" : false, + "returnType" : "double" } ], "examples" : [ diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java index b3b36ac0fa160..c56a96d7b3987 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java @@ -633,6 +633,10 @@ public static boolean isSortable(DataType t) { return false == (t == SOURCE || isCounter(t) || isSpatialOrGrid(t) || t == AGGREGATE_METRIC_DOUBLE); } + public static boolean isUnsignedLong(DataType t) { + return t == UNSIGNED_LONG; + } + public String nameUpper() { return name; } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/decay.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/decay.csv-spec index 3a136455a3aa1..93e9ef993b908 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/decay.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/decay.csv-spec @@ -24,6 +24,28 @@ decay_result:double 0.75 ; +intLinearMixedNumericTypes1 +required_capability: decay_function + +ROW value = 5 +| EVAL decay_result = decay(value, 10::double, 10::long, {"offset": 0, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +0.75 +; + +intLinearMixedNumericTypes2 +required_capability: decay_function + +ROW value = 5 +| EVAL decay_result = decay(value, 10::unsigned_long, 10::long, {"offset": 0, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +0.75 +; + intExp required_capability: decay_function @@ -166,6 +188,28 @@ decay_result:double 0.75 ; +doubleLinearMixedNumericTypes1 +required_capability: decay_function + +ROW value = 5.0 +| EVAL decay_result = decay(value, 10.0::int, 10.0::long, {"offset": 0.0, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +0.75 +; + +doubleLinearMixedNumericTypes2 +required_capability: decay_function + +ROW value = 5.0 +| EVAL decay_result = decay(value, 10.0::int, 10.0::unsigned_long, {"offset": 0.0, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +0.75 +; + doubleExp required_capability: decay_function @@ -199,6 +243,61 @@ decay_result:double 1.0 ; +longLinearMixedNumericTypes1 +required_capability: decay_function + +ROW value = 15::long +| EVAL decay_result = decay(value, 10::int, 10::unsigned_long, {"offset": 10000000000, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +1.0 +; + +longLinearMixedNumericTypes2 +required_capability: decay_function + +ROW value = 15::long +| EVAL decay_result = decay(value, 10::double, 10::int, {"offset": 10000000000, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +1.0 +; + +unsignedLongLinear +required_capability: decay_function + +ROW value = 15::unsigned_long +| EVAL decay_result = decay(value, 10::unsigned_long, 10::unsigned_long, {"offset": 18446744073709551615, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +1.0 +; + +unsignedLongLinearMixedNumericTypes1 +required_capability: decay_function + +ROW value = 15::unsigned_long +| EVAL decay_result = decay(value, 10::long, 10::integer, {"offset": 18446744073709551615, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +1.0 +; + +unsignedLongLinearMixedNumericTypes2 +required_capability: decay_function + +ROW value = 15::unsigned_long +| EVAL decay_result = decay(value, 10::double, 10::integer, {"offset": 18446744073709551615, "decay": 0.5, "type": "linear"}) +| KEEP decay_result; + +decay_result:double +1.0 +; + cartesianPointLinear1 required_capability: decay_function diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayUnsignedLongEvaluator.java new file mode 100644 index 0000000000000..d20083ffce4b1 --- /dev/null +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayUnsignedLongEvaluator.java @@ -0,0 +1,166 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License +// 2.0; you may not use this file except in compliance with the Elastic License +// 2.0. +package org.elasticsearch.xpack.esql.expression.function.scalar.score; + +import java.lang.IllegalArgumentException; +import java.lang.Override; +import java.lang.String; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.DoubleVector; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.LongVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.operator.DriverContext; +import org.elasticsearch.compute.operator.EvalOperator; +import org.elasticsearch.compute.operator.Warnings; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.xpack.esql.core.tree.Source; + +/** + * {@link EvalOperator.ExpressionEvaluator} implementation for {@link Decay}. + * This class is generated. Edit {@code EvaluatorImplementer} instead. + */ +public final class DecayUnsignedLongEvaluator implements EvalOperator.ExpressionEvaluator { + private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(DecayUnsignedLongEvaluator.class); + + private final Source source; + + private final EvalOperator.ExpressionEvaluator value; + + private final long origin; + + private final long scale; + + private final long offset; + + private final double decay; + + private final Decay.DecayFunction decayFunction; + + private final DriverContext driverContext; + + private Warnings warnings; + + public DecayUnsignedLongEvaluator(Source source, EvalOperator.ExpressionEvaluator value, + long origin, long scale, long offset, double decay, Decay.DecayFunction decayFunction, + DriverContext driverContext) { + this.source = source; + this.value = value; + this.origin = origin; + this.scale = scale; + this.offset = offset; + this.decay = decay; + this.decayFunction = decayFunction; + this.driverContext = driverContext; + } + + @Override + public Block eval(Page page) { + try (LongBlock valueBlock = (LongBlock) value.eval(page)) { + LongVector valueVector = valueBlock.asVector(); + if (valueVector == null) { + return eval(page.getPositionCount(), valueBlock); + } + return eval(page.getPositionCount(), valueVector).asBlock(); + } + } + + @Override + public long baseRamBytesUsed() { + long baseRamBytesUsed = BASE_RAM_BYTES_USED; + baseRamBytesUsed += value.baseRamBytesUsed(); + return baseRamBytesUsed; + } + + public DoubleBlock eval(int positionCount, LongBlock valueBlock) { + try(DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + if (valueBlock.isNull(p)) { + result.appendNull(); + continue position; + } + if (valueBlock.getValueCount(p) != 1) { + if (valueBlock.getValueCount(p) > 1) { + warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value")); + } + result.appendNull(); + continue position; + } + result.appendDouble(Decay.processUnsignedLong(valueBlock.getLong(valueBlock.getFirstValueIndex(p)), this.origin, this.scale, this.offset, this.decay, this.decayFunction)); + } + return result.build(); + } + } + + public DoubleVector eval(int positionCount, LongVector valueVector) { + try(DoubleVector.FixedBuilder result = driverContext.blockFactory().newDoubleVectorFixedBuilder(positionCount)) { + position: for (int p = 0; p < positionCount; p++) { + result.appendDouble(p, Decay.processUnsignedLong(valueVector.getLong(p), this.origin, this.scale, this.offset, this.decay, this.decayFunction)); + } + return result.build(); + } + } + + @Override + public String toString() { + return "DecayUnsignedLongEvaluator[" + "value=" + value + ", origin=" + origin + ", scale=" + scale + ", offset=" + offset + ", decay=" + decay + ", decayFunction=" + decayFunction + "]"; + } + + @Override + public void close() { + Releasables.closeExpectNoException(value); + } + + private Warnings warnings() { + if (warnings == null) { + this.warnings = Warnings.createWarnings( + driverContext.warningsMode(), + source.source().getLineNumber(), + source.source().getColumnNumber(), + source.text() + ); + } + return warnings; + } + + static class Factory implements EvalOperator.ExpressionEvaluator.Factory { + private final Source source; + + private final EvalOperator.ExpressionEvaluator.Factory value; + + private final long origin; + + private final long scale; + + private final long offset; + + private final double decay; + + private final Decay.DecayFunction decayFunction; + + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory value, long origin, + long scale, long offset, double decay, Decay.DecayFunction decayFunction) { + this.source = source; + this.value = value; + this.origin = origin; + this.scale = scale; + this.offset = offset; + this.decay = decay; + this.decayFunction = decayFunction; + } + + @Override + public DecayUnsignedLongEvaluator get(DriverContext context) { + return new DecayUnsignedLongEvaluator(source, value.get(context), origin, scale, offset, decay, decayFunction, context); + } + + @Override + public String toString() { + return "DecayUnsignedLongEvaluator[" + "value=" + value + ", origin=" + origin + ", scale=" + scale + ", offset=" + offset + ", decay=" + decay + ", decayFunction=" + decayFunction + "]"; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/Decay.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/Decay.java index f691f6032d2dc..c12fc94664d62 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/Decay.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/Decay.java @@ -27,6 +27,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; @@ -38,8 +39,10 @@ import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; +import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter; import java.io.IOException; +import java.math.BigInteger; import java.time.Duration; import java.util.Arrays; import java.util.Collection; @@ -63,6 +66,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; import static org.elasticsearch.xpack.esql.core.type.DataType.TIME_DURATION; +import static org.elasticsearch.xpack.esql.core.type.DataType.UNSIGNED_LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.isDateNanos; import static org.elasticsearch.xpack.esql.core.type.DataType.isGeoPoint; import static org.elasticsearch.xpack.esql.core.type.DataType.isMillisOrNanos; @@ -91,7 +95,7 @@ public class Decay extends EsqlScalarFunction implements OptionalArgument, PostO private static final Map> ALLOWED_OPTIONS = Map.of( OFFSET, - Set.of(TIME_DURATION, INTEGER, LONG, DOUBLE, KEYWORD, TEXT), + Set.of(TIME_DURATION, INTEGER, LONG, UNSIGNED_LONG, DOUBLE, KEYWORD, TEXT), DECAY, Set.of(DOUBLE), TYPE, @@ -140,17 +144,17 @@ public Decay( Source source, @Param( name = "value", - type = { "double", "integer", "long", "date", "date_nanos", "geo_point", "cartesian_point" }, + type = { "double", "integer", "long", "unsigned_long", "date", "date_nanos", "geo_point", "cartesian_point" }, description = "The input value to apply decay scoring to." ) Expression value, @Param( name = ORIGIN, - type = { "double", "integer", "long", "date", "date_nanos", "geo_point", "cartesian_point" }, + type = { "double", "integer", "long", "unsigned_long", "date", "date_nanos", "geo_point", "cartesian_point" }, description = "Central point from which the distances are calculated." ) Expression origin, @Param( name = SCALE, - type = { "double", "integer", "long", "time_duration", "keyword", "text" }, + type = { "double", "integer", "long", "unsigned_long", "time_duration", "keyword", "text" }, description = "Distance from the origin where the function returns the decay value." ) Expression scale, @MapParam( @@ -158,7 +162,7 @@ public Decay( params = { @MapParam.MapParamEntry( name = OFFSET, - type = { "double", "integer", "long", "time_duration", "keyword", "text" }, + type = { "double", "integer", "long", "unsigned_long", "time_duration", "keyword", "text" }, description = "Distance from the origin where no decay occurs." ), @MapParam.MapParamEntry( @@ -285,8 +289,8 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua FoldContext foldCtx = toEvaluator.foldCtx(); // Constants - Object originFolded = origin.fold(foldCtx); - Object scaleFolded = getFoldedScale(foldCtx, valueDataType); + Object originFolded = convertToExpectedType(origin.fold(foldCtx), origin.dataType(), valueDataType); + Object scaleFolded = convertToExpectedType(getFoldedScale(foldCtx, valueDataType), scale.dataType(), valueDataType); Object offsetFolded = getOffset(foldCtx, valueDataType, offsetExpr); Double decayFolded = decayExpr != null ? (Double) decayExpr.fold(foldCtx) : DEFAULT_DECAY; DecayFunction decayFunction = DecayFunction.fromBytesRef(typeExpr != null ? (BytesRef) typeExpr.fold(foldCtx) : DEFAULT_FUNCTION); @@ -319,6 +323,15 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua decayFolded, decayFunction ); + case UNSIGNED_LONG -> new DecayUnsignedLongEvaluator.Factory( + source(), + valueFactory, + (Long) originFolded, + (Long) scaleFolded, + (Long) offsetFolded, + decayFolded, + decayFunction + ); case GEO_POINT -> new DecayGeoPointEvaluator.Factory( source(), valueFactory, @@ -403,7 +416,24 @@ static double process( @Fixed DecayFunction decayFunction ) { return decayFunction.numericDecay(value, origin, scale, offset, decay); + } + @Evaluator(extraName = "UnsignedLong") + static double processUnsignedLong( + long value, + @Fixed long origin, + @Fixed long scale, + @Fixed long offset, + @Fixed double decay, + @Fixed DecayFunction decayFunction + ) { + return decayFunction.numericDecay( + NumericUtils.unsignedLongToDouble(value), + NumericUtils.unsignedLongToDouble(origin), + NumericUtils.unsignedLongToDouble(scale), + NumericUtils.unsignedLongToDouble(offset), + decay + ); } @Evaluator(extraName = "GeoPoint") @@ -634,7 +664,7 @@ private Long getTemporalOffsetAsNanos(FoldContext foldCtx, Expression offset) { private Object getDefaultOffset(DataType valueDataType) { return switch (valueDataType) { case INTEGER -> DEFAULT_INTEGER_OFFSET; - case LONG -> DEFAULT_LONG_OFFSET; + case LONG, UNSIGNED_LONG -> DEFAULT_LONG_OFFSET; case DOUBLE -> DEFAULT_DOUBLE_OFFSET; case GEO_POINT -> DEFAULT_GEO_POINT_OFFSET; case CARTESIAN_POINT -> DEFAULT_CARTESIAN_POINT_OFFSET; @@ -643,4 +673,25 @@ private Object getDefaultOffset(DataType valueDataType) { }; } + private Object convertToExpectedType(Object value, DataType valueType, DataType targetType) { + // No conversion needed + if (targetType.isNumeric() == false) { + return value; + } + + // Conversion needed as unsigned longs are represented as signed longs + if (valueType == UNSIGNED_LONG) { + value = NumericUtils.unsignedLongToDouble(((Number) value).longValue()); + } + + Object convertedValue = EsqlDataTypeConverter.convert(value, targetType); + + // Unsigned long evaluator expects unsigned longs in signed long representation + if (convertedValue instanceof BigInteger valueAsBigInteger) { + return NumericUtils.asLongUnsigned(valueAsBigInteger); + } + + return convertedValue; + } + } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayTests.java index c68896b13884d..4c46cdc1a313e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayTests.java @@ -19,10 +19,12 @@ import org.elasticsearch.xpack.esql.core.expression.MapExpression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.NumericUtils; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.junit.BeforeClass; +import java.math.BigInteger; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -109,6 +111,9 @@ public static Iterable parameters() { // Long random testCaseSuppliers.addAll(longRandomTestCases()); + // Unsigned Long random + testCaseSuppliers.addAll(unsignedLongRandomTestCases()); + // Double Linear testCaseSuppliers.addAll(doubleTestCase(0.0, 10.0, 10000000.0, 200.0, 0.25, "linear", 1.0)); testCaseSuppliers.addAll(doubleTestCase(10.0, 10.0, 10000000.0, 200.0, 0.25, "linear", 1.0)); @@ -754,6 +759,77 @@ private static double longDecayWithScoreScript(long value, long origin, long sca }; } + private static List unsignedLongRandomTestCases() { + return List.of( + new TestCaseSupplier(List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.SOURCE), () -> { + BigInteger randomValueBig = randomUnsignedLongBetween(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX); + BigInteger randomOriginBig = randomUnsignedLongBetween(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX); + BigInteger randomScaleBig = randomUnsignedLongBetween(BigInteger.ONE, NumericUtils.UNSIGNED_LONG_MAX); + BigInteger randomOffsetBig = randomUnsignedLongBetween(BigInteger.ZERO, NumericUtils.UNSIGNED_LONG_MAX); + + // Convert to the signed long representation used internally + long randomValue = NumericUtils.asLongUnsigned(randomValueBig); + long randomOrigin = NumericUtils.asLongUnsigned(randomOriginBig); + long randomScale = NumericUtils.asLongUnsigned(randomScaleBig); + long randomOffset = NumericUtils.asLongUnsigned(randomOffsetBig); + + double randomDecay = randomDouble(); + String randomType = randomFrom("linear", "gauss", "exp"); + + double scoreScriptNumericResult = unsignedLongDecayWithScoreScript( + randomValue, + randomOrigin, + randomScale, + randomOffset, + randomDecay, + randomType + ); + + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(randomValue, DataType.UNSIGNED_LONG, "value"), + new TestCaseSupplier.TypedData(randomOrigin, DataType.UNSIGNED_LONG, "origin").forceLiteral(), + new TestCaseSupplier.TypedData(randomScale, DataType.UNSIGNED_LONG, "scale").forceLiteral(), + new TestCaseSupplier.TypedData(createOptionsMap(randomOffset, randomDecay, randomType), DataType.SOURCE, "options") + .forceLiteral() + ), + startsWith("DecayUnsignedLongEvaluator["), + DataType.DOUBLE, + equalTo(scoreScriptNumericResult) + ); + }) + ); + } + + private static double unsignedLongDecayWithScoreScript(long value, long origin, long scale, long offset, double decay, String type) { + var valueUnsignedLongAsDouble = NumericUtils.unsignedLongToDouble(value); + var originUnsignedLongAsDouble = NumericUtils.unsignedLongToDouble(origin); + var scaleUnsignedLongAsDouble = NumericUtils.unsignedLongToDouble(scale); + var offsetUnsignedLongAsDouble = NumericUtils.unsignedLongToDouble(offset); + + return switch (type) { + case "linear" -> new ScoreScriptUtils.DecayNumericLinear( + originUnsignedLongAsDouble, + scaleUnsignedLongAsDouble, + offsetUnsignedLongAsDouble, + decay + ).decayNumericLinear(valueUnsignedLongAsDouble); + case "gauss" -> new ScoreScriptUtils.DecayNumericGauss( + originUnsignedLongAsDouble, + scaleUnsignedLongAsDouble, + offsetUnsignedLongAsDouble, + decay + ).decayNumericGauss(valueUnsignedLongAsDouble); + case "exp" -> new ScoreScriptUtils.DecayNumericExp( + originUnsignedLongAsDouble, + scaleUnsignedLongAsDouble, + offsetUnsignedLongAsDouble, + decay + ).decayNumericExp(valueUnsignedLongAsDouble); + default -> throw new IllegalArgumentException("Unknown decay function type [" + type + "]"); + }; + } + private static List doubleTestCase( double value, double origin,