Skip to content

Commit bacb81d

Browse files
committed
Check that scale and offset are constant, if they're temporal after analysis and before evaluation
1 parent f69a54b commit bacb81d

File tree

1 file changed

+38
-33
lines changed
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score

1 file changed

+38
-33
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/score/Decay.java

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import org.elasticsearch.compute.operator.EvalOperator;
1717
import org.elasticsearch.geometry.Point;
1818
import org.elasticsearch.script.ScoreScriptUtils;
19+
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
20+
import org.elasticsearch.xpack.esql.common.Failures;
1921
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
2022
import org.elasticsearch.xpack.esql.core.expression.Expression;
2123
import org.elasticsearch.xpack.esql.core.expression.MapExpression;
@@ -31,18 +33,21 @@
3133
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
3234
import org.elasticsearch.xpack.esql.expression.function.Options;
3335
import org.elasticsearch.xpack.esql.expression.function.Param;
34-
import org.elasticsearch.xpack.esql.expression.function.fulltext.*;
3536
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
3637
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
38+
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
3739

3840
import java.io.IOException;
3941
import java.time.Duration;
4042
import java.util.Collection;
4143
import java.util.HashMap;
4244
import java.util.List;
4345
import java.util.Map;
46+
import java.util.Objects;
4447
import java.util.Set;
48+
import java.util.function.BiConsumer;
4549

50+
import static org.elasticsearch.xpack.esql.common.Failure.fail;
4651
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
4752
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FOURTH;
4853
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
@@ -73,7 +78,7 @@
7378
* - Spatial types (geo_point, cartesian_point)
7479
* - Temporal types (datetime, date_nanos)
7580
*/
76-
public class Decay extends EsqlScalarFunction implements OptionalArgument {
81+
public class Decay extends EsqlScalarFunction implements OptionalArgument, PostAnalysisPlanVerificationAware {
7782

7883
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Decay", Decay::new);
7984

@@ -104,6 +109,8 @@ public class Decay extends EsqlScalarFunction implements OptionalArgument {
104109
private final Expression scale;
105110
private final Expression options;
106111

112+
private final Map<String, Object> resolvedOptions;
113+
107114
@FunctionInfo(
108115
returnType = "double",
109116
preview = true,
@@ -155,6 +162,7 @@ public Decay(
155162
this.origin = origin;
156163
this.scale = scale;
157164
this.options = options;
165+
this.resolvedOptions = new HashMap<>();
158166
}
159167

160168
private Decay(StreamInput in) throws IOException {
@@ -282,18 +290,17 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua
282290

283291
DataType valueDataType = value.dataType();
284292

285-
Map<String, Object> decayOptions = new HashMap<>();
286293
Options.populateMapWithExpressionsMultipleDataTypesAllowed(
287294
(MapExpression) options,
288-
decayOptions,
295+
resolvedOptions,
289296
source(),
290297
FOURTH,
291298
ALLOWED_OPTIONS
292299
);
293300

294-
Expression offset = (Expression) decayOptions.get(OFFSET);
295-
Expression decay = (Expression) decayOptions.get(DECAY);
296-
Expression type = (Expression) decayOptions.get(TYPE);
301+
Expression offset = (Expression) resolvedOptions.get(OFFSET);
302+
Expression decay = (Expression) resolvedOptions.get(DECAY);
303+
Expression type = (Expression) resolvedOptions.get(TYPE);
297304

298305
EvalOperator.ExpressionEvaluator.Factory scaleFactory = getScaleFactory(toEvaluator, valueDataType);
299306
EvalOperator.ExpressionEvaluator.Factory offsetFactory = getOffsetFactory(toEvaluator, valueDataType, offset);
@@ -374,6 +381,26 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua
374381
};
375382
}
376383

384+
@Override
385+
public BiConsumer<LogicalPlan, Failures> postAnalysisPlanVerification() {
386+
return (LogicalPlan plan, Failures failures) -> {
387+
Expression offset = (Expression) resolvedOptions.get(OFFSET);
388+
389+
Map<String, Expression> potentiallyTemporalExpressions = new HashMap<>();
390+
potentiallyTemporalExpressions.put("scale", scale);
391+
potentiallyTemporalExpressions.put("offset", offset);
392+
393+
// Verify that scale and offset are constant, if they're of type "time_duration"
394+
potentiallyTemporalExpressions.forEach((exprName, expr) -> {
395+
if (Objects.nonNull(expr) && isTimeDuration(expr.dataType()) && expr.foldable() == false) {
396+
failures.add(
397+
fail(offset, "Function [{}] has non-constant temporal [{}] [{}].", sourceText(), exprName, offset.sourceText())
398+
);
399+
}
400+
});
401+
};
402+
}
403+
377404
@Evaluator(extraName = "Int")
378405
static double process(int value, int origin, int scale, int offset, double decay, BytesRef functionType) {
379406
return switch (functionType.utf8ToString()) {
@@ -520,49 +547,27 @@ private EvalOperator.ExpressionEvaluator.Factory getScaleFactory(ToEvaluator toE
520547
}
521548

522549
private EvalOperator.ExpressionEvaluator.Factory getTemporalScaleAsMillis(ToEvaluator toEvaluator) {
523-
EvalOperator.ExpressionEvaluator.Factory scaleFactory;
524-
if (scale.foldable() == false) {
525-
throw new IllegalArgumentException(
526-
"Function [" + sourceText() + "] has non-constant temporal scale [" + scale.sourceText() + "]."
527-
);
528-
}
529550
Object foldedScale = scale.fold(toEvaluator.foldCtx());
530551
long scaleMillis = ((Duration) foldedScale).toMillis();
531-
scaleFactory = EvalOperator.LongFactory(scaleMillis);
532-
return scaleFactory;
552+
553+
return EvalOperator.LongFactory(scaleMillis);
533554
}
534555

535556
private EvalOperator.ExpressionEvaluator.Factory getTemporalScaleAsNanos(ToEvaluator toEvaluator) {
536-
if (scale.foldable() == false) {
537-
throw new IllegalArgumentException(
538-
"Function [" + sourceText() + "] has non-constant temporal scale [" + scale.sourceText() + "]."
539-
);
540-
}
541557
Object foldedScale = scale.fold(toEvaluator.foldCtx());
558+
542559
Duration scaleDuration = (Duration) foldedScale;
543560
long scaleNanos = scaleDuration.toNanos();
544561
return EvalOperator.LongFactory(scaleNanos);
545562
}
546563

547564
private EvalOperator.ExpressionEvaluator.Factory getTemporalOffsetAsMillis(ToEvaluator toEvaluator, Expression offset) {
548-
EvalOperator.ExpressionEvaluator.Factory offsetFactory;
549-
if (offset.foldable() == false) {
550-
throw new IllegalArgumentException(
551-
"Function [" + sourceText() + "] has non-constant temporal offset [" + offset.sourceText() + "]."
552-
);
553-
}
554565
Object foldedOffset = offset.fold(toEvaluator.foldCtx());
555566
long offsetMillis = ((Duration) foldedOffset).toMillis();
556-
offsetFactory = EvalOperator.LongFactory(offsetMillis);
557-
return offsetFactory;
567+
return EvalOperator.LongFactory(offsetMillis);
558568
}
559569

560570
private EvalOperator.ExpressionEvaluator.Factory getTemporalOffsetAsNanos(ToEvaluator toEvaluator, Expression offset) {
561-
if (offset.foldable() == false) {
562-
throw new IllegalArgumentException(
563-
"Function [" + sourceText() + "] has non-constant temporal offset [" + offset.sourceText() + "]."
564-
);
565-
}
566571
Object foldedOffset = offset.fold(toEvaluator.foldCtx());
567572
Duration offsetDuration = (Duration) foldedOffset;
568573

0 commit comments

Comments
 (0)