From d57a8e9d88bbb306f5e459cb12a83807ce375255 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Wed, 10 Sep 2025 12:14:20 +0200 Subject: [PATCH 1/8] Add unsigned long support to Decay function --- .../functions/functionNamedParams/decay.md | 2 +- .../esql/_snippets/functions/types/decay.md | 1 + .../kibana/definition/functions/decay.json | 31 +++++++ .../src/main/resources/decay.csv-spec | 11 +++ .../function/scalar/score/Decay.java | 15 ++-- .../function/scalar/score/DecayTests.java | 90 +++++++++++++++++++ 6 files changed, 142 insertions(+), 8 deletions(-) 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 56ca96d77d071..035f35d80574d 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/qa/testFixtures/src/main/resources/decay.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/decay.csv-spec index 313404d5a33af..02232ba9d479a 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 @@ -195,6 +195,17 @@ 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": 10000000000, "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/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..f0e7803fac0a4 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 @@ -63,6 +63,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 +92,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 +141,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 +159,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( @@ -310,7 +311,7 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua decayFolded, decayFunction ); - case LONG -> new DecayLongEvaluator.Factory( + case LONG, UNSIGNED_LONG -> new DecayLongEvaluator.Factory( source(), valueFactory, (Long) originFolded, @@ -634,7 +635,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; 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 fda42d74a0e30..6112ea971cae9 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 @@ -102,6 +102,33 @@ public static Iterable parameters() { // Long random testCaseSuppliers.addAll(longRandomTestCases()); + // Unsigned Long Linear + testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "linear", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "linear", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "linear", 0.99666407)); + testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "linear", 0.97991407)); + testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "linear", 0.0)); + + // Unsigned Long Exponential + testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "exp", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "exp", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "exp", 0.9944951761701727)); + testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "exp", 0.9673096701204178)); + testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "exp", 0.0)); + + // Unsigned Long Gaussian + testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "gauss", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "gauss", 1.0)); + testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "gauss", 0.999972516142306)); + testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "gauss", 0.9990040963055015)); + testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "gauss", 0.0)); + + // Unsigned Long defaults + testCaseSuppliers.addAll(unsignedLongTestCase(10L, 0L, 10L, null, null, null, 0.5)); + + // 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)); @@ -738,6 +765,69 @@ private static List longRandomTestCases() { })); } + private static List unsignedLongTestCase( + long value, + long origin, + long scale, + Long offset, + Double decay, + String functionType, + double expected + ) { + return List.of( + new TestCaseSupplier( + List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.SOURCE), + () -> new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(value, DataType.UNSIGNED_LONG, "value"), + new TestCaseSupplier.TypedData(origin, DataType.UNSIGNED_LONG, "origin").forceLiteral(), + new TestCaseSupplier.TypedData(scale, DataType.UNSIGNED_LONG, "scale").forceLiteral(), + new TestCaseSupplier.TypedData(createOptionsMap(offset, decay, functionType), DataType.SOURCE, "options") + .forceLiteral() + ), + startsWith("DecayLongEvaluator["), + DataType.DOUBLE, + closeTo(expected, Math.ulp(expected)) + ) + ) + ); + } + + private static List unsignedLongRandomTestCases() { + return List.of( + new TestCaseSupplier(List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.SOURCE), () -> { + long randomValue = randomLong(); + long randomOrigin = randomLong(); + long randomScale = randomLong(); + long randomOffset = randomLong(); + double randomDecay = randomDouble(); + String randomType = randomFrom("linear", "gauss", "exp"); + + double scoreScriptNumericResult = longDecayWithScoreScript( + 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("DecayLongEvaluator["), + DataType.DOUBLE, + equalTo(scoreScriptNumericResult) + ); + }) + ); + } + private static double longDecayWithScoreScript(long value, long origin, long scale, long offset, double decay, String type) { return switch (type) { case "linear" -> new ScoreScriptUtils.DecayNumericLinear(origin, scale, offset, decay).decayNumericLinear(value); From 58a230c466a0ce982f44331c46724c8eeb350e63 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Tue, 16 Sep 2025 16:57:18 +0200 Subject: [PATCH 2/8] Separate unsigned long and long evaluators --- .../xpack/esql/core/type/DataType.java | 4 + .../function/scalar/score/Decay.java | 32 ++++++- .../function/scalar/score/DecayTests.java | 91 +++++++------------ 3 files changed, 66 insertions(+), 61 deletions(-) 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 528f9ac2f57ea..d372bb280575b 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 @@ -623,6 +623,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/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 f0e7803fac0a4..e8e918123a36c 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; @@ -69,6 +70,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.isMillisOrNanos; import static org.elasticsearch.xpack.esql.core.type.DataType.isSpatialPoint; import static org.elasticsearch.xpack.esql.core.type.DataType.isTimeDuration; +import static org.elasticsearch.xpack.esql.core.type.DataType.isUnsignedLong; /** * Decay a numeric, spatial or date type value based on the distance of it to an origin. @@ -235,6 +237,8 @@ private TypeResolution validateOriginAndScale(DataType valueType) { ); } else if (isMillisOrNanos(valueType)) { return validateOriginAndScale(DataType::isMillisOrNanos, "datetime or date_nanos", DataType::isTimeDuration, "time_duration"); + } else if(isUnsignedLong(valueType)){ + return validateOriginAndScale(DataType::isUnsignedLong, "unsigned long", DataType::isUnsignedLong, "unsigned_long"); } else { return validateOriginAndScale(DataType::isNumeric, "numeric", DataType::isNumeric, "numeric"); } @@ -311,7 +315,16 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua decayFolded, decayFunction ); - case LONG, UNSIGNED_LONG -> new DecayLongEvaluator.Factory( + case LONG -> new DecayLongEvaluator.Factory( + source(), + valueFactory, + (Long) originFolded, + (Long) scaleFolded, + (Long) offsetFolded, + decayFolded, + decayFunction + ); + case UNSIGNED_LONG -> new DecayUnsignedLongEvaluator.Factory( source(), valueFactory, (Long) originFolded, @@ -404,7 +417,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") 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 6112ea971cae9..48af8fad3b19e 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 @@ -18,9 +18,11 @@ 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 java.math.BigInteger; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; @@ -102,30 +104,6 @@ public static Iterable parameters() { // Long random testCaseSuppliers.addAll(longRandomTestCases()); - // Unsigned Long Linear - testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "linear", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "linear", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "linear", 0.99666407)); - testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "linear", 0.97991407)); - testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "linear", 0.0)); - - // Unsigned Long Exponential - testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "exp", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "exp", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "exp", 0.9944951761701727)); - testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "exp", 0.9673096701204178)); - testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "exp", 0.0)); - - // Unsigned Long Gaussian - testCaseSuppliers.addAll(unsignedLongTestCase(0L, 10L, 10000000L, 200L, 0.33, "gauss", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(10L, 10L, 10000000L, 200L, 0.33, "gauss", 1.0)); - testCaseSuppliers.addAll(unsignedLongTestCase(50000L, 10L, 10000000L, 200L, 0.33, "gauss", 0.999972516142306)); - testCaseSuppliers.addAll(unsignedLongTestCase(300000L, 10L, 10000000L, 200L, 0.33, "gauss", 0.9990040963055015)); - testCaseSuppliers.addAll(unsignedLongTestCase(123456789112123L, 10L, 10000000L, 200L, 0.33, "gauss", 0.0)); - - // Unsigned Long defaults - testCaseSuppliers.addAll(unsignedLongTestCase(10L, 0L, 10L, null, null, null, 0.5)); - // Unsigned Long random testCaseSuppliers.addAll(unsignedLongRandomTestCases()); @@ -765,45 +743,33 @@ private static List longRandomTestCases() { })); } - private static List unsignedLongTestCase( - long value, - long origin, - long scale, - Long offset, - Double decay, - String functionType, - double expected - ) { - return List.of( - new TestCaseSupplier( - List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.SOURCE), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(value, DataType.UNSIGNED_LONG, "value"), - new TestCaseSupplier.TypedData(origin, DataType.UNSIGNED_LONG, "origin").forceLiteral(), - new TestCaseSupplier.TypedData(scale, DataType.UNSIGNED_LONG, "scale").forceLiteral(), - new TestCaseSupplier.TypedData(createOptionsMap(offset, decay, functionType), DataType.SOURCE, "options") - .forceLiteral() - ), - startsWith("DecayLongEvaluator["), - DataType.DOUBLE, - closeTo(expected, Math.ulp(expected)) - ) - ) - ); + private static double longDecayWithScoreScript(long value, long origin, long scale, long offset, double decay, String type) { + return switch (type) { + case "linear" -> new ScoreScriptUtils.DecayNumericLinear(origin, scale, offset, decay).decayNumericLinear(value); + case "gauss" -> new ScoreScriptUtils.DecayNumericGauss(origin, scale, offset, decay).decayNumericGauss(value); + case "exp" -> new ScoreScriptUtils.DecayNumericExp(origin, scale, offset, decay).decayNumericExp(value); + default -> throw new IllegalArgumentException("Unknown decay function type [" + type + "]"); + }; } private static List unsignedLongRandomTestCases() { return List.of( new TestCaseSupplier(List.of(DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.UNSIGNED_LONG, DataType.SOURCE), () -> { - long randomValue = randomLong(); - long randomOrigin = randomLong(); - long randomScale = randomLong(); - long randomOffset = randomLong(); + 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 = longDecayWithScoreScript( + double scoreScriptNumericResult = unsignedLongDecayWithScoreScript( randomValue, randomOrigin, randomScale, @@ -820,7 +786,7 @@ private static List unsignedLongRandomTestCases() { new TestCaseSupplier.TypedData(createOptionsMap(randomOffset, randomDecay, randomType), DataType.SOURCE, "options") .forceLiteral() ), - startsWith("DecayLongEvaluator["), + startsWith("DecayUnsignedLongEvaluator["), DataType.DOUBLE, equalTo(scoreScriptNumericResult) ); @@ -828,11 +794,16 @@ private static List unsignedLongRandomTestCases() { ); } - private static double longDecayWithScoreScript(long value, long origin, long scale, long offset, double decay, String type) { + 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(origin, scale, offset, decay).decayNumericLinear(value); - case "gauss" -> new ScoreScriptUtils.DecayNumericGauss(origin, scale, offset, decay).decayNumericGauss(value); - case "exp" -> new ScoreScriptUtils.DecayNumericExp(origin, scale, offset, decay).decayNumericExp(value); + 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 + "]"); }; } From 408d4c993384973152b8f8061aad8742688a9e10 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Tue, 16 Sep 2025 16:59:09 +0200 Subject: [PATCH 3/8] Use actual unsigned long in unsigned long integration test case --- .../esql/qa/testFixtures/src/main/resources/decay.csv-spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 02232ba9d479a..d50ea793eedcc 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 @@ -199,7 +199,7 @@ unsignedLongLinear required_capability: decay_function ROW value = 15::unsigned_long -| EVAL decay_result = decay(value, 10::unsigned_long, 10::unsigned_long, {"offset": 10000000000, "decay": 0.5, "type": "linear"}) +| EVAL decay_result = decay(value, 10::unsigned_long, 10::unsigned_long, {"offset": 18446744073709551615, "decay": 0.5, "type": "linear"}) | KEEP decay_result; decay_result:double From c86e56c131799a92e39eee5a2d92e22651cf5a2d Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Tue, 16 Sep 2025 17:03:38 +0200 Subject: [PATCH 4/8] spotlessApply --- .../xpack/esql/core/type/DataType.java | 2 +- .../function/scalar/score/Decay.java | 2 +- .../function/scalar/score/DecayTests.java | 21 ++++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) 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 d372bb280575b..5cc144a099332 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 @@ -623,7 +623,7 @@ public static boolean isSortable(DataType t) { return false == (t == SOURCE || isCounter(t) || isSpatialOrGrid(t) || t == AGGREGATE_METRIC_DOUBLE); } - public static boolean isUnsignedLong(DataType t){ + public static boolean isUnsignedLong(DataType t) { return t == UNSIGNED_LONG; } 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 e8e918123a36c..9da6c94835563 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 @@ -237,7 +237,7 @@ private TypeResolution validateOriginAndScale(DataType valueType) { ); } else if (isMillisOrNanos(valueType)) { return validateOriginAndScale(DataType::isMillisOrNanos, "datetime or date_nanos", DataType::isTimeDuration, "time_duration"); - } else if(isUnsignedLong(valueType)){ + } else if (isUnsignedLong(valueType)) { return validateOriginAndScale(DataType::isUnsignedLong, "unsigned long", DataType::isUnsignedLong, "unsigned_long"); } else { return validateOriginAndScale(DataType::isNumeric, "numeric", DataType::isNumeric, "numeric"); 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 48af8fad3b19e..e7f1950edac74 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 @@ -801,9 +801,24 @@ private static double unsignedLongDecayWithScoreScript(long value, long origin, 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); + 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 + "]"); }; } From f43b505dfe56184723bc694da7aec3ccd60513e5 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Wed, 17 Sep 2025 10:25:43 +0200 Subject: [PATCH 5/8] Support mixed numeric types for numeric decay --- .../function/scalar/score/Decay.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) 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 9da6c94835563..e755c47f45308 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 @@ -70,7 +70,6 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.isMillisOrNanos; import static org.elasticsearch.xpack.esql.core.type.DataType.isSpatialPoint; import static org.elasticsearch.xpack.esql.core.type.DataType.isTimeDuration; -import static org.elasticsearch.xpack.esql.core.type.DataType.isUnsignedLong; /** * Decay a numeric, spatial or date type value based on the distance of it to an origin. @@ -237,8 +236,6 @@ private TypeResolution validateOriginAndScale(DataType valueType) { ); } else if (isMillisOrNanos(valueType)) { return validateOriginAndScale(DataType::isMillisOrNanos, "datetime or date_nanos", DataType::isTimeDuration, "time_duration"); - } else if (isUnsignedLong(valueType)) { - return validateOriginAndScale(DataType::isUnsignedLong, "unsigned long", DataType::isUnsignedLong, "unsigned_long"); } else { return validateOriginAndScale(DataType::isNumeric, "numeric", DataType::isNumeric, "numeric"); } @@ -290,8 +287,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); @@ -674,4 +671,36 @@ private Object getDefaultOffset(DataType valueDataType) { }; } + private Object convertToExpectedType(Object value, DataType valueType, DataType targetType) { + if (targetType == INTEGER && value instanceof Integer) { + return value; + } + if (targetType == LONG && value instanceof Long) { + return value; + } + if (targetType == UNSIGNED_LONG && value instanceof Long) { + return value; + } + if (targetType == DOUBLE && value instanceof Double) { + return value; + } + + // Unsigned longs are represented using (Long.MIN_VALUE, Long.MAX_VALUE), therefore we need to convert + // if the targetType is not "unsigned_long" + if (valueType == UNSIGNED_LONG && targetType != UNSIGNED_LONG) { + value = NumericUtils.unsignedLongToDouble(((Number) value).longValue()); + } + + if (value instanceof Number num) { + return switch (targetType) { + case INTEGER -> num.intValue(); + case LONG, UNSIGNED_LONG -> num.longValue(); + case DOUBLE -> num.doubleValue(); + default -> value; + }; + } + + return value; + } + } From 13199036825f88747bb53a6d4c842aea7a5f68b0 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Wed, 17 Sep 2025 10:25:54 +0200 Subject: [PATCH 6/8] Add mixed numeric types test cases --- .../src/main/resources/decay.csv-spec | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) 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 d50ea793eedcc..a5647e7f3e20a 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 @@ -162,6 +184,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 @@ -195,6 +239,28 @@ 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 @@ -206,6 +272,28 @@ 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 From eee150fa4c003126826bf827a054ae7ab6b540e1 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Wed, 17 Sep 2025 11:34:18 +0200 Subject: [PATCH 7/8] Use EsqlDataTypeConverter for most cases --- .../function/scalar/score/Decay.java | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) 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 e755c47f45308..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 @@ -39,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; @@ -672,35 +674,24 @@ private Object getDefaultOffset(DataType valueDataType) { } private Object convertToExpectedType(Object value, DataType valueType, DataType targetType) { - if (targetType == INTEGER && value instanceof Integer) { - return value; - } - if (targetType == LONG && value instanceof Long) { - return value; - } - if (targetType == UNSIGNED_LONG && value instanceof Long) { - return value; - } - if (targetType == DOUBLE && value instanceof Double) { + // No conversion needed + if (targetType.isNumeric() == false) { return value; } - // Unsigned longs are represented using (Long.MIN_VALUE, Long.MAX_VALUE), therefore we need to convert - // if the targetType is not "unsigned_long" - if (valueType == UNSIGNED_LONG && targetType != UNSIGNED_LONG) { + // Conversion needed as unsigned longs are represented as signed longs + if (valueType == UNSIGNED_LONG) { value = NumericUtils.unsignedLongToDouble(((Number) value).longValue()); } - if (value instanceof Number num) { - return switch (targetType) { - case INTEGER -> num.intValue(); - case LONG, UNSIGNED_LONG -> num.longValue(); - case DOUBLE -> num.doubleValue(); - default -> value; - }; + 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 value; + return convertedValue; } } From 13f103a67e15e315c2d2e409ac29a33716d7d1a5 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Mon, 22 Sep 2025 10:04:31 +0200 Subject: [PATCH 8/8] Add Evaluator --- .../score/DecayUnsignedLongEvaluator.java | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/score/DecayUnsignedLongEvaluator.java 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 + "]"; + } + } +}