Skip to content

Commit 6a02e8d

Browse files
alex-spiescbuescher
authored andcommitted
ESQL: Fix for overzealous validation in case of invalid mapped fields (elastic#111475)
Fix validation of fields mapped to different types in different indices and align with validation of fields of unsupported type. * Allow using multi-typed fields in KEEP and DROP, just like unsupported fields. * Explicitly invalidate using both these field kinds in RENAME. * Map both kinds of fields to UnsupportedAttribute to enforce consistency. * Consider convert functions containing valid multi-typed fields as resolved to avoid weird workarounds when resolving STATS. * Add a bunch of tests.
1 parent 89f4d34 commit 6a02e8d

File tree

19 files changed

+738
-118
lines changed

19 files changed

+738
-118
lines changed

docs/changelog/111475.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 111475
2+
summary: "ESQL: Fix for overzealous validation in case of invalid mapped fields"
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 111452

docs/reference/esql/esql-multi-index.asciidoc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ In addition, if the query refers to this unsupported field directly, the query f
9797
[source.merge.styled,esql]
9898
----
9999
FROM events_*
100-
| KEEP @timestamp, client_ip, event_duration, message
101-
| SORT @timestamp DESC
100+
| SORT client_ip DESC
102101
----
103102

104103
[source,bash]
@@ -118,9 +117,8 @@ experimental::[]
118117
{esql} has a way to handle <<esql-multi-index-invalid-mapping, field type mismatches>>. When the same field is mapped to multiple types in multiple indices,
119118
the type of the field is understood to be a _union_ of the various types in the index mappings.
120119
As seen in the preceding examples, this _union type_ cannot be used in the results,
121-
and cannot be referred to by the query
122-
-- except when it's passed to a type conversion function that accepts all the types in the _union_ and converts the field
123-
to a single type. {esql} offers a suite of <<esql-type-conversion-functions,type conversion functions>> to achieve this.
120+
and cannot be referred to by the query -- except in `KEEP`, `DROP` or when it's passed to a type conversion function that accepts all the types in
121+
the _union_ and converts the field to a single type. {esql} offers a suite of <<esql-type-conversion-functions,type conversion functions>> to achieve this.
124122

125123
In the above examples, the query can use a command like `EVAL client_ip = TO_IP(client_ip)` to resolve
126124
the union of `ip` and `keyword` to just `ip`.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ public Nullability nullable() {
109109
return child.nullable();
110110
}
111111

112+
@Override
113+
protected TypeResolution resolveType() {
114+
return child.resolveType();
115+
}
116+
112117
@Override
113118
public DataType dataType() {
114119
return child.dataType();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public boolean synthetic() {
5555
return synthetic;
5656
}
5757

58+
/**
59+
* Try to return either {@code this} if it is an {@link Attribute}, or a {@link ReferenceAttribute} to it otherwise.
60+
* Return an {@link UnresolvedAttribute} if this is unresolved.
61+
*/
5862
public abstract Attribute toAttribute();
5963

6064
@Override

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

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.elasticsearch.xpack.esql.core.expression.Expression.TypeResolution;
1010
import org.elasticsearch.xpack.esql.core.type.DataType;
1111
import org.elasticsearch.xpack.esql.core.type.EsField;
12+
import org.elasticsearch.xpack.esql.core.type.InvalidMappedField;
1213

1314
import java.util.Locale;
1415
import java.util.StringJoiner;
@@ -176,19 +177,60 @@ public static TypeResolution isType(
176177
ParamOrdinal paramOrd,
177178
String... acceptedTypes
178179
) {
179-
return predicate.test(e.dataType()) || e.dataType() == NULL
180-
? TypeResolution.TYPE_RESOLVED
181-
: new TypeResolution(
182-
format(
183-
null,
184-
"{}argument of [{}] must be [{}], found value [{}] type [{}]",
185-
paramOrd == null || paramOrd == DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",
186-
operationName,
187-
acceptedTypesForErrorMsg(acceptedTypes),
188-
name(e),
189-
e.dataType().typeName()
190-
)
191-
);
180+
return isType(e, predicate, operationName, paramOrd, false, acceptedTypes);
181+
}
182+
183+
public static TypeResolution isTypeOrUnionType(
184+
Expression e,
185+
Predicate<DataType> predicate,
186+
String operationName,
187+
ParamOrdinal paramOrd,
188+
String... acceptedTypes
189+
) {
190+
return isType(e, predicate, operationName, paramOrd, true, acceptedTypes);
191+
}
192+
193+
public static TypeResolution isType(
194+
Expression e,
195+
Predicate<DataType> predicate,
196+
String operationName,
197+
ParamOrdinal paramOrd,
198+
boolean allowUnionTypes,
199+
String... acceptedTypes
200+
) {
201+
if (predicate.test(e.dataType()) || e.dataType() == NULL) {
202+
return TypeResolution.TYPE_RESOLVED;
203+
}
204+
205+
// TODO: Shouldn't we perform widening of small numerical types here?
206+
if (allowUnionTypes
207+
&& e instanceof FieldAttribute fa
208+
&& fa.field() instanceof InvalidMappedField imf
209+
&& imf.types().stream().allMatch(predicate)) {
210+
return TypeResolution.TYPE_RESOLVED;
211+
}
212+
213+
return new TypeResolution(
214+
errorStringIncompatibleTypes(operationName, paramOrd, name(e), e.dataType(), acceptedTypesForErrorMsg(acceptedTypes))
215+
);
216+
}
217+
218+
private static String errorStringIncompatibleTypes(
219+
String operationName,
220+
ParamOrdinal paramOrd,
221+
String argumentName,
222+
DataType foundType,
223+
String... acceptedTypes
224+
) {
225+
return format(
226+
null,
227+
"{}argument of [{}] must be [{}], found value [{}] type [{}]",
228+
paramOrd == null || paramOrd == DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",
229+
operationName,
230+
acceptedTypesForErrorMsg(acceptedTypes),
231+
argumentName,
232+
foundType.typeName()
233+
);
192234
}
193235

194236
private static String acceptedTypesForErrorMsg(String... acceptedTypes) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public UnresolvedAttribute withUnresolvedMessage(String unresolvedMessage) {
7777
return new UnresolvedAttribute(source(), name(), id(), unresolvedMessage, resolutionMetadata());
7878
}
7979

80+
@Override
81+
protected TypeResolution resolveType() {
82+
return new TypeResolution("unresolved attribute [" + name() + "]");
83+
}
84+
8085
@Override
8186
public DataType dataType() {
8287
throw new UnresolvedException("dataType", this);

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/InvalidMappedField.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Objects;
1818
import java.util.Set;
1919
import java.util.TreeMap;
20+
import java.util.stream.Collectors;
2021

2122
/**
2223
* Representation of field mapped differently across indices.
@@ -64,6 +65,10 @@ private InvalidMappedField(StreamInput in) throws IOException {
6465
this(in.readString(), in.readString(), in.readImmutableMap(StreamInput::readString, i -> i.readNamedWriteable(EsField.class)));
6566
}
6667

68+
public Set<DataType> types() {
69+
return typesToIndices.keySet().stream().map(DataType::fromTypeName).collect(Collectors.toSet());
70+
}
71+
6772
@Override
6873
public void writeTo(StreamOutput out) throws IOException {
6974
out.writeString(getName());

x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats.csv-spec

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,6 +2018,19 @@ M
20182018
null
20192019
;
20202020

2021+
shadowingInternalWithGroup2#[skip:-8.14.1,reason:implemented in 8.14]
2022+
FROM employees
2023+
| STATS x = MAX(emp_no), y = count(x) BY x = emp_no, x = gender
2024+
| SORT x ASC
2025+
;
2026+
2027+
y:long | x:keyword
2028+
33 | F
2029+
57 | M
2030+
0 | null
2031+
;
2032+
2033+
20212034
shadowingTheGroup
20222035
FROM employees
20232036
| STATS gender = MAX(emp_no), gender = MIN(emp_no) BY gender

0 commit comments

Comments
 (0)