Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": 18446744073709551615, "decay": 0.5, "type": "linear"})
| KEEP decay_result;

decay_result:double
1.0
;

cartesianPointLinear1
required_capability: decay_function

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,6 +64,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;
Expand Down Expand Up @@ -91,7 +93,7 @@ public class Decay extends EsqlScalarFunction implements OptionalArgument, PostO

private static final Map<String, Collection<DataType>> 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,
Expand Down Expand Up @@ -140,25 +142,25 @@ 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(
name = "options",
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(
Expand Down Expand Up @@ -285,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);
Expand Down Expand Up @@ -319,6 +321,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,
Expand Down Expand Up @@ -403,7 +414,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")
Expand Down Expand Up @@ -634,7 +662,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;
Expand All @@ -643,4 +671,36 @@ private Object getDefaultOffset(DataType valueDataType) {
};
}

private Object convertToExpectedType(Object value, DataType valueType, DataType targetType) {
if (targetType == INTEGER && value instanceof Integer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use EsqlDataTypeConverter.convert() here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -102,6 +104,9 @@ public static Iterable<Object[]> 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));
Expand Down Expand Up @@ -747,6 +752,77 @@ private static double longDecayWithScoreScript(long value, long origin, long sca
};
}

private static List<TestCaseSupplier> 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<TestCaseSupplier> doubleTestCase(
double value,
double origin,
Expand Down
Loading