|
16 | 16 | import org.elasticsearch.compute.operator.EvalOperator; |
17 | 17 | import org.elasticsearch.geometry.Point; |
18 | 18 | import org.elasticsearch.script.ScoreScriptUtils; |
| 19 | +import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; |
| 20 | +import org.elasticsearch.xpack.esql.common.Failures; |
19 | 21 | import org.elasticsearch.xpack.esql.core.InvalidArgumentException; |
20 | 22 | import org.elasticsearch.xpack.esql.core.expression.Expression; |
21 | 23 | import org.elasticsearch.xpack.esql.core.expression.MapExpression; |
|
31 | 33 | import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; |
32 | 34 | import org.elasticsearch.xpack.esql.expression.function.Options; |
33 | 35 | import org.elasticsearch.xpack.esql.expression.function.Param; |
34 | | -import org.elasticsearch.xpack.esql.expression.function.fulltext.*; |
35 | 36 | import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; |
36 | 37 | import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; |
| 38 | +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; |
37 | 39 |
|
38 | 40 | import java.io.IOException; |
39 | 41 | import java.time.Duration; |
40 | 42 | import java.util.Collection; |
41 | 43 | import java.util.HashMap; |
42 | 44 | import java.util.List; |
43 | 45 | import java.util.Map; |
| 46 | +import java.util.Objects; |
44 | 47 | import java.util.Set; |
| 48 | +import java.util.function.BiConsumer; |
45 | 49 |
|
| 50 | +import static org.elasticsearch.xpack.esql.common.Failure.fail; |
46 | 51 | import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; |
47 | 52 | import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FOURTH; |
48 | 53 | import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; |
|
73 | 78 | * - Spatial types (geo_point, cartesian_point) |
74 | 79 | * - Temporal types (datetime, date_nanos) |
75 | 80 | */ |
76 | | -public class Decay extends EsqlScalarFunction implements OptionalArgument { |
| 81 | +public class Decay extends EsqlScalarFunction implements OptionalArgument, PostAnalysisPlanVerificationAware { |
77 | 82 |
|
78 | 83 | public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Decay", Decay::new); |
79 | 84 |
|
@@ -104,6 +109,8 @@ public class Decay extends EsqlScalarFunction implements OptionalArgument { |
104 | 109 | private final Expression scale; |
105 | 110 | private final Expression options; |
106 | 111 |
|
| 112 | + private final Map<String, Object> resolvedOptions; |
| 113 | + |
107 | 114 | @FunctionInfo( |
108 | 115 | returnType = "double", |
109 | 116 | preview = true, |
@@ -155,6 +162,7 @@ public Decay( |
155 | 162 | this.origin = origin; |
156 | 163 | this.scale = scale; |
157 | 164 | this.options = options; |
| 165 | + this.resolvedOptions = new HashMap<>(); |
158 | 166 | } |
159 | 167 |
|
160 | 168 | private Decay(StreamInput in) throws IOException { |
@@ -282,18 +290,17 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua |
282 | 290 |
|
283 | 291 | DataType valueDataType = value.dataType(); |
284 | 292 |
|
285 | | - Map<String, Object> decayOptions = new HashMap<>(); |
286 | 293 | Options.populateMapWithExpressionsMultipleDataTypesAllowed( |
287 | 294 | (MapExpression) options, |
288 | | - decayOptions, |
| 295 | + resolvedOptions, |
289 | 296 | source(), |
290 | 297 | FOURTH, |
291 | 298 | ALLOWED_OPTIONS |
292 | 299 | ); |
293 | 300 |
|
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); |
297 | 304 |
|
298 | 305 | EvalOperator.ExpressionEvaluator.Factory scaleFactory = getScaleFactory(toEvaluator, valueDataType); |
299 | 306 | EvalOperator.ExpressionEvaluator.Factory offsetFactory = getOffsetFactory(toEvaluator, valueDataType, offset); |
@@ -374,6 +381,26 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua |
374 | 381 | }; |
375 | 382 | } |
376 | 383 |
|
| 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 | + |
377 | 404 | @Evaluator(extraName = "Int") |
378 | 405 | static double process(int value, int origin, int scale, int offset, double decay, BytesRef functionType) { |
379 | 406 | return switch (functionType.utf8ToString()) { |
@@ -520,49 +547,27 @@ private EvalOperator.ExpressionEvaluator.Factory getScaleFactory(ToEvaluator toE |
520 | 547 | } |
521 | 548 |
|
522 | 549 | 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 | | - } |
529 | 550 | Object foldedScale = scale.fold(toEvaluator.foldCtx()); |
530 | 551 | long scaleMillis = ((Duration) foldedScale).toMillis(); |
531 | | - scaleFactory = EvalOperator.LongFactory(scaleMillis); |
532 | | - return scaleFactory; |
| 552 | + |
| 553 | + return EvalOperator.LongFactory(scaleMillis); |
533 | 554 | } |
534 | 555 |
|
535 | 556 | 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 | | - } |
541 | 557 | Object foldedScale = scale.fold(toEvaluator.foldCtx()); |
| 558 | + |
542 | 559 | Duration scaleDuration = (Duration) foldedScale; |
543 | 560 | long scaleNanos = scaleDuration.toNanos(); |
544 | 561 | return EvalOperator.LongFactory(scaleNanos); |
545 | 562 | } |
546 | 563 |
|
547 | 564 | 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 | | - } |
554 | 565 | Object foldedOffset = offset.fold(toEvaluator.foldCtx()); |
555 | 566 | long offsetMillis = ((Duration) foldedOffset).toMillis(); |
556 | | - offsetFactory = EvalOperator.LongFactory(offsetMillis); |
557 | | - return offsetFactory; |
| 567 | + return EvalOperator.LongFactory(offsetMillis); |
558 | 568 | } |
559 | 569 |
|
560 | 570 | 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 | | - } |
566 | 571 | Object foldedOffset = offset.fold(toEvaluator.foldCtx()); |
567 | 572 | Duration offsetDuration = (Duration) foldedOffset; |
568 | 573 |
|
|
0 commit comments