Skip to content

Commit 7e75746

Browse files
authored
ESQL: introduce a new interface to declare functions depending on the @timestamp attribute (#137040)
This updates the way the `@timestamp` field is injected into the functions that require it implicitly: these functions no longer need to declare an attribute themselves, the function registry will do it for them. In a follow-up, this attribute be traced from the source and eventually wired into the functions (so that renames no longer be problematic). Closes #136772
1 parent 772e6a1 commit 7e75746

File tree

24 files changed

+291
-342
lines changed

24 files changed

+291
-342
lines changed

docs/changelog/137040.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pr: 137040
2+
summary: Introduce a new interface to declare functions depending on the `@timestamp`
3+
attribute
4+
area: ES|QL
5+
type: enhancement
6+
issues:
7+
- 136772

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/TypeResolutions.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ public enum ParamOrdinal {
3535
SECOND,
3636
THIRD,
3737
FOURTH,
38-
FIFTH;
38+
FIFTH,
39+
IMPLICIT;
3940

4041
public static ParamOrdinal fromIndex(int index) {
41-
return switch (index) {
42+
return index < 0 ? IMPLICIT : switch (index) {
4243
case 0 -> FIRST;
4344
case 1 -> SECOND;
4445
case 2 -> THIRD;

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedAttribute.java

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
public class UnresolvedAttribute extends Attribute implements Unresolvable {
3333
private final boolean customMessage;
3434
protected final String unresolvedMsg;
35-
// TODO: unused and always null, remove this.
36-
protected final Object resolutionMetadata;
3735

3836
// TODO: Check usage of constructors without qualifiers, that's likely where qualifiers need to be plugged into resolution logic.
3937
public UnresolvedAttribute(Source source, String name) {
@@ -45,17 +43,11 @@ public UnresolvedAttribute(Source source, String name, @Nullable String unresolv
4543
}
4644

4745
public UnresolvedAttribute(Source source, @Nullable String qualifier, String name, @Nullable String unresolvedMessage) {
48-
this(source, qualifier, name, null, unresolvedMessage, null);
46+
this(source, qualifier, name, null, unresolvedMessage);
4947
}
5048

51-
public UnresolvedAttribute(
52-
Source source,
53-
String name,
54-
@Nullable NameId id,
55-
@Nullable String unresolvedMessage,
56-
Object resolutionMetadata
57-
) {
58-
this(source, null, name, id, unresolvedMessage, resolutionMetadata);
49+
public UnresolvedAttribute(Source source, String name, @Nullable NameId id, @Nullable String unresolvedMessage) {
50+
this(source, null, name, id, unresolvedMessage);
5951
}
6052

6153
@SuppressWarnings("this-escape")
@@ -64,15 +56,11 @@ public UnresolvedAttribute(
6456
@Nullable String qualifier,
6557
String name,
6658
@Nullable NameId id,
67-
@Nullable String unresolvedMessage,
68-
Object resolutionMetadata
59+
@Nullable String unresolvedMessage
6960
) {
7061
super(source, qualifier, name, id);
7162
this.customMessage = unresolvedMessage != null;
72-
this.unresolvedMsg = unresolvedMessage == null
73-
? errorMessage(qualifier() != null ? qualifiedName() : name(), null)
74-
: unresolvedMessage;
75-
this.resolutionMetadata = resolutionMetadata;
63+
this.unresolvedMsg = unresolvedMessage != null ? unresolvedMessage : defaultUnresolvedMessage(null);
7664
}
7765

7866
@Override
@@ -87,11 +75,7 @@ public String getWriteableName() {
8775

8876
@Override
8977
protected NodeInfo<? extends UnresolvedAttribute> info() {
90-
return NodeInfo.create(this, UnresolvedAttribute::new, qualifier(), name(), id(), unresolvedMsg, resolutionMetadata);
91-
}
92-
93-
public Object resolutionMetadata() {
94-
return resolutionMetadata;
78+
return NodeInfo.create(this, UnresolvedAttribute::new, qualifier(), name(), id(), unresolvedMsg);
9579
}
9680

9781
public boolean customMessage() {
@@ -120,11 +104,11 @@ protected Attribute clone(
120104
// Cannot just use the super method because that requires a data type.
121105
@Override
122106
public UnresolvedAttribute withId(NameId id) {
123-
return new UnresolvedAttribute(source(), qualifier(), name(), id, unresolvedMessage(), resolutionMetadata());
107+
return new UnresolvedAttribute(source(), qualifier(), name(), id, unresolvedMessage());
124108
}
125109

126110
public UnresolvedAttribute withUnresolvedMessage(String unresolvedMessage) {
127-
return new UnresolvedAttribute(source(), qualifier(), name(), id(), unresolvedMessage, resolutionMetadata());
111+
return new UnresolvedAttribute(source(), qualifier(), name(), id(), unresolvedMessage);
128112
}
129113

130114
@Override
@@ -169,6 +153,10 @@ public String unresolvedMessage() {
169153
return unresolvedMsg;
170154
}
171155

156+
public String defaultUnresolvedMessage(@Nullable List<String> potentialMatches) {
157+
return errorMessage(qualifier() != null ? qualifiedName() : name(), potentialMatches);
158+
}
159+
172160
public static String errorMessage(String name, List<String> potentialMatches) {
173161
String msg = "Unknown column [" + name + "]";
174162
if (CollectionUtils.isEmpty(potentialMatches) == false) {
@@ -181,14 +169,12 @@ public static String errorMessage(String name, List<String> potentialMatches) {
181169

182170
@Override
183171
protected int innerHashCode(boolean ignoreIds) {
184-
return Objects.hash(super.innerHashCode(ignoreIds), resolutionMetadata, unresolvedMsg);
172+
return Objects.hash(super.innerHashCode(ignoreIds), unresolvedMsg);
185173
}
186174

187175
@Override
188176
protected boolean innerEquals(Object o, boolean ignoreIds) {
189177
var other = (UnresolvedAttribute) o;
190-
return super.innerEquals(o, ignoreIds)
191-
&& Objects.equals(resolutionMetadata, other.resolutionMetadata)
192-
&& Objects.equals(unresolvedMsg, other.unresolvedMsg);
178+
return super.innerEquals(o, ignoreIds) && Objects.equals(unresolvedMsg, other.unresolvedMsg);
193179
}
194180
}

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/UnresolvedTimestamp.java

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,65 +7,43 @@
77

88
package org.elasticsearch.xpack.esql.core.expression;
99

10+
import org.elasticsearch.core.Nullable;
1011
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
1112
import org.elasticsearch.xpack.esql.core.tree.Source;
1213

13-
import java.util.Objects;
14+
import java.util.List;
1415

1516
public class UnresolvedTimestamp extends UnresolvedAttribute {
16-
private final String errorMessage;
1717

18-
public UnresolvedTimestamp(Source source, String errorMessage) {
19-
this(source, null, MetadataAttribute.TIMESTAMP_FIELD, null, null, null, errorMessage);
18+
public static final String UNRESOLVED_SUFFIX = "requires the ["
19+
+ MetadataAttribute.TIMESTAMP_FIELD
20+
+ "] field, which was either not present in the source index, or has been dropped"
21+
+ " or renamed"; // TODO: track the name change
22+
23+
public UnresolvedTimestamp(Source source) {
24+
this(source, null, MetadataAttribute.TIMESTAMP_FIELD, null, null);
2025
}
2126

22-
public UnresolvedTimestamp(
23-
Source source,
24-
String qualifier,
25-
String name,
26-
NameId id,
27-
String unresolvedMessage,
28-
Object resolutionMetadata,
29-
String errorMessage
30-
) {
31-
super(source, qualifier, name, id, unresolvedMessage, resolutionMetadata);
32-
this.errorMessage = errorMessage;
27+
public UnresolvedTimestamp(Source source, String qualifier, String name, NameId id, String unresolvedMessage) {
28+
super(source, qualifier, name, id, unresolvedMessage);
3329
}
3430

3531
@Override
3632
protected NodeInfo<UnresolvedTimestamp> info() {
37-
return NodeInfo.create(
38-
this,
39-
UnresolvedTimestamp::new,
40-
qualifier(),
41-
name(),
42-
id(),
43-
super.unresolvedMessage(),
44-
resolutionMetadata(),
45-
errorMessage
46-
);
33+
return NodeInfo.create(this, UnresolvedTimestamp::new, qualifier(), name(), id(), super.unresolvedMessage());
4734
}
4835

4936
@Override
5037
public UnresolvedTimestamp withUnresolvedMessage(String unresolvedMessage) {
51-
return new UnresolvedTimestamp(source(), qualifier(), name(), id(), unresolvedMessage, resolutionMetadata(), errorMessage);
38+
return new UnresolvedTimestamp(source(), qualifier(), name(), id(), unresolvedMessage);
5239
}
5340

5441
@Override
55-
public String unresolvedMessage() {
56-
if (super.unresolvedMessage() != null) {
57-
return errorMessage;
58-
}
59-
return null;
42+
public String defaultUnresolvedMessage(@Nullable List<String> ignored) {
43+
return "[" + sourceText() + "] " + UNRESOLVED_SUFFIX;
6044
}
6145

62-
@Override
63-
protected int innerHashCode(boolean ignoreIds) {
64-
return Objects.hash(super.innerHashCode(ignoreIds), errorMessage);
65-
}
66-
67-
@Override
68-
protected boolean innerEquals(Object o, boolean ignoreIds) {
69-
return super.innerEquals(o, ignoreIds) && Objects.equals(errorMessage, ((UnresolvedTimestamp) o).errorMessage);
46+
public static UnresolvedTimestamp withSource(Source source) {
47+
return new UnresolvedTimestamp(source);
7048
}
7149
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -501,63 +501,23 @@ protected LogicalPlan rule(LogicalPlan plan, AnalyzerContext context) {
501501
childrenOutput.addAll(output);
502502
}
503503

504-
if (plan instanceof Aggregate aggregate) {
505-
return resolveAggregate(aggregate, childrenOutput);
506-
}
507-
508-
if (plan instanceof Completion c) {
509-
return resolveCompletion(c, childrenOutput);
510-
}
511-
512-
if (plan instanceof Drop d) {
513-
return resolveDrop(d, childrenOutput);
514-
}
515-
516-
if (plan instanceof Rename r) {
517-
return resolveRename(r, childrenOutput);
518-
}
519-
520-
if (plan instanceof Keep p) {
521-
return resolveKeep(p, childrenOutput);
522-
}
523-
524-
if (plan instanceof Fork f) {
525-
return resolveFork(f, context);
526-
}
527-
528-
if (plan instanceof Eval p) {
529-
return resolveEval(p, childrenOutput);
530-
}
531-
532-
if (plan instanceof Enrich p) {
533-
return resolveEnrich(p, childrenOutput);
534-
}
535-
536-
if (plan instanceof MvExpand p) {
537-
return resolveMvExpand(p, childrenOutput);
538-
}
539-
540-
if (plan instanceof Lookup l) {
541-
return resolveLookup(l, childrenOutput);
542-
}
543-
544-
if (plan instanceof LookupJoin j) {
545-
return resolveLookupJoin(j, context);
546-
}
547-
548-
if (plan instanceof Insist i) {
549-
return resolveInsist(i, childrenOutput, context);
550-
}
551-
552-
if (plan instanceof Fuse fuse) {
553-
return resolveFuse(fuse, childrenOutput);
554-
}
555-
556-
if (plan instanceof Rerank r) {
557-
return resolveRerank(r, childrenOutput);
558-
}
559-
560-
return plan.transformExpressionsOnly(UnresolvedAttribute.class, ua -> maybeResolveAttribute(ua, childrenOutput));
504+
return switch (plan) {
505+
case Aggregate a -> resolveAggregate(a, childrenOutput);
506+
case Completion c -> resolveCompletion(c, childrenOutput);
507+
case Drop d -> resolveDrop(d, childrenOutput);
508+
case Rename r -> resolveRename(r, childrenOutput);
509+
case Keep p -> resolveKeep(p, childrenOutput);
510+
case Fork f -> resolveFork(f, context);
511+
case Eval p -> resolveEval(p, childrenOutput);
512+
case Enrich p -> resolveEnrich(p, childrenOutput);
513+
case MvExpand p -> resolveMvExpand(p, childrenOutput);
514+
case Lookup l -> resolveLookup(l, childrenOutput);
515+
case LookupJoin j -> resolveLookupJoin(j, context);
516+
case Insist i -> resolveInsist(i, childrenOutput, context);
517+
case Fuse fuse -> resolveFuse(fuse, childrenOutput);
518+
case Rerank r -> resolveRerank(r, childrenOutput);
519+
default -> plan.transformExpressionsOnly(UnresolvedAttribute.class, ua -> maybeResolveAttribute(ua, childrenOutput));
520+
};
561521
}
562522

563523
private Aggregate resolveAggregate(Aggregate aggregate, List<Attribute> childrenOutput) {
@@ -715,8 +675,7 @@ private LogicalPlan resolveLookup(Lookup l, List<Attribute> childrenOutput) {
715675
+ joinedAttribute.dataType().typeName()
716676
+ "] and original column was ["
717677
+ attr.dataType().typeName()
718-
+ "]",
719-
null
678+
+ "]"
720679
);
721680
}
722681
}
@@ -1431,7 +1390,7 @@ private static List<Attribute> resolveAgainstList(UnresolvedNamePattern up, Coll
14311390

14321391
private static List<Attribute> resolveAgainstList(UnresolvedAttribute ua, Collection<Attribute> attrList) {
14331392
var matches = AnalyzerRules.maybeResolveAgainstList(ua, attrList, a -> Analyzer.handleSpecialFields(ua, a));
1434-
return potentialCandidatesIfNoMatchesFound(ua, matches, attrList, list -> UnresolvedAttribute.errorMessage(ua.name(), list));
1393+
return potentialCandidatesIfNoMatchesFound(ua, matches, attrList, ua::defaultUnresolvedMessage);
14351394
}
14361395

14371396
private static List<Attribute> potentialCandidatesIfNoMatchesFound(
@@ -1703,7 +1662,7 @@ private static Expression processScalarOrGroupingFunction(
17031662
if (arg.foldable() && ((arg instanceof EsqlScalarFunction) == false)) {
17041663
if (i < targetDataTypes.size()) {
17051664
targetDataType = targetDataTypes.get(i);
1706-
}
1665+
} // else the last type applies to all elements in a possible list (variadic)
17071666
if (targetDataType != NULL && targetDataType != UNSUPPORTED) {
17081667
Expression e = castStringLiteral(arg, targetDataType);
17091668
if (e != arg) {

0 commit comments

Comments
 (0)