Skip to content

Commit 3afd809

Browse files
committed
Fix + test ROW | ENRICH
1 parent 73b05d9 commit 3afd809

File tree

6 files changed

+85
-23
lines changed

6 files changed

+85
-23
lines changed

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/AllSupportedFieldsTestCase.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,19 @@ public void testRowEnrich() throws IOException {
556556
String policyName = e.getKey() + "_policy";
557557
String query = "ROW " + LOOKUP_ID_FIELD + " = 123 | ENRICH " + policyName + " ON " + LOOKUP_ID_FIELD + " | LIMIT 1";
558558
var responseAndCoordinatorVersion = runQuery(query);
559-
TransportVersion expectedMinimumVersion = minVersion(true);
560-
561-
assertMinimumVersion(expectedMinimumVersion, responseAndCoordinatorVersion);
562-
563559
Map<String, Object> response = responseAndCoordinatorVersion.v1();
564560
TransportVersion coordinatorVersion = responseAndCoordinatorVersion.v2();
561+
TransportVersion expectedMinimumVersion = minVersion(true);
562+
563+
Map<String, Object> profile = (Map<String, Object>) response.get("profile");
564+
Integer actualMinimumVersion = (Integer) profile.get("minimumTransportVersion");
565+
if (minVersion(true).supports(ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION)
566+
// Some nodes don't send back the minimum transport version because they're too old to do that.
567+
// In this case, the determined minimum version will be that of the coordinator.
568+
|| (coordinatorVersion.supports(ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION)
569+
&& actualMinimumVersion != coordinatorVersion.id())) {
570+
assertMinimumVersion(expectedMinimumVersion, responseAndCoordinatorVersion);
571+
}
565572

566573
assertNoPartialResponse(response);
567574

@@ -645,17 +652,18 @@ protected void assertMinimumVersion(
645652
Map<String, Object> profile = (Map<String, Object>) responseMap.get("profile");
646653
Integer minimumVersion = (Integer) profile.get("minimumTransportVersion");
647654
assertNotNull(minimumVersion);
655+
int minimumVersionInt = minimumVersion;
648656
if (expectedMinimumVersion.supports(RESOLVE_FIELDS_RESPONSE_CREATED_TV)) {
649657
// All nodes are new enough that their field caps responses should contain the minimum transport version
650658
// of matching clusters.
651-
assertEquals(expectedMinimumVersion.id(), minimumVersion.intValue());
659+
assertEquals(expectedMinimumVersion.id(), minimumVersionInt);
652660
} else {
653661
// One node is old enough that it doesn't provide version information in the field caps response. We must assume
654662
// the oldest compatible version.
655663
// Since we got minimumVersion in the profile, the coordinator is on a new version.
656664
// Thus, it's on the same version as this code (bwc tests only use 2 different versions) and the oldest compatible version
657665
// to the coordinator is given by TransportVersion.minimumCompatible().
658-
assertEquals(TransportVersion.minimumCompatible().id(), minimumVersion.intValue());
666+
assertEquals(TransportVersion.minimumCompatible().id(), minimumVersionInt);
659667
}
660668
}
661669
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@
4242
import org.elasticsearch.xpack.esql.analysis.EnrichResolution;
4343
import org.elasticsearch.xpack.esql.core.type.DataType;
4444
import org.elasticsearch.xpack.esql.core.type.EsField;
45+
import org.elasticsearch.xpack.esql.core.util.Holder;
4546
import org.elasticsearch.xpack.esql.core.util.StringUtils;
4647
import org.elasticsearch.xpack.esql.index.EsIndex;
4748
import org.elasticsearch.xpack.esql.index.IndexResolution;
4849
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
4950
import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
5051
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
5152
import org.elasticsearch.xpack.esql.session.IndexResolver;
53+
import org.elasticsearch.xpack.esql.session.Versioned;
5254

5355
import java.io.IOException;
5456
import java.util.ArrayList;
@@ -77,7 +79,6 @@
7779
public class EnrichPolicyResolver {
7880
private static final String RESOLVE_ACTION_NAME = "cluster:monitor/xpack/enrich/esql/resolve_policy";
7981

80-
// NOCOMMIT: rename to something that represents the overall change
8182
public static final TransportVersion ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION = TransportVersion.fromName(
8283
"esql_use_minimum_version_for_enrich_resolution"
8384
);
@@ -128,10 +129,10 @@ public void resolvePolicies(
128129
List<Enrich> enriches,
129130
EsqlExecutionInfo executionInfo,
130131
TransportVersion minimumVersion,
131-
ActionListener<EnrichResolution> listener
132+
ActionListener<Versioned<EnrichResolution>> listener
132133
) {
133134
if (enriches.isEmpty()) {
134-
listener.onResponse(new EnrichResolution());
135+
listener.onResponse(new Versioned<>(new EnrichResolution(), minimumVersion));
135136
return;
136137
}
137138

@@ -149,10 +150,10 @@ protected void doResolvePolicies(
149150
Collection<UnresolvedPolicy> unresolvedPolicies,
150151
EsqlExecutionInfo executionInfo,
151152
TransportVersion minimumVersion,
152-
ActionListener<EnrichResolution> listener
153+
ActionListener<Versioned<EnrichResolution>> listener
153154
) {
154155
if (unresolvedPolicies.isEmpty()) {
155-
listener.onResponse(new EnrichResolution());
156+
listener.onResponse(new Versioned<>(new EnrichResolution(), minimumVersion));
156157
return;
157158
}
158159

@@ -178,6 +179,14 @@ protected void doResolvePolicies(
178179
}
179180
}
180181

182+
// Propagate the minimum version observed during policy resolution back to the planning pipeline.
183+
// This is only really required for `ROW | ENRICH` queries, where the main index resolution doesn't
184+
// provide the minimum version of the local cluster.
185+
TransportVersion updatedMinimumVersion = minimumVersion;
186+
for (LookupResponse response : lookupResponsesToProcess.values()) {
187+
updatedMinimumVersion = TransportVersion.min(updatedMinimumVersion, response.minimumVersion);
188+
}
189+
181190
for (UnresolvedPolicy unresolved : unresolvedPolicies) {
182191
Tuple<ResolvedEnrichPolicy, String> resolved = mergeLookupResults(
183192
unresolved,
@@ -192,7 +201,7 @@ protected void doResolvePolicies(
192201
enrichResolution.addError(unresolved.name, unresolved.mode, resolved.v2());
193202
}
194203
}
195-
return enrichResolution;
204+
return new Versioned<>(enrichResolution, updatedMinimumVersion);
196205
}));
197206
}
198207

@@ -404,6 +413,10 @@ private static class LookupRequest extends AbstractTransportRequest {
404413
if (in.getTransportVersion().supports(ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION)) {
405414
this.minimumVersion = TransportVersion.readVersion(in);
406415
} else {
416+
// An older coordinator contacted us. Let's assume an old version, otherwise we might send back
417+
// types that it can't deserialize.
418+
// (The only version that knows some new types but doesn't send its transport version here is 9.2.0;
419+
// these types are dense_vector and aggregate_metric_double, and both don't work with ENRICH in 9.2.0, anyway.)
407420
this.minimumVersion = TransportVersion.minimumCompatible();
408421
}
409422
}
@@ -421,12 +434,15 @@ public void writeTo(StreamOutput out) throws IOException {
421434
private static class LookupResponse extends TransportResponse {
422435
final Map<String, ResolvedEnrichPolicy> policies;
423436
final Map<String, String> failures;
437+
// The minimum transport version observed when running field caps requests to resolve the policies
438+
final TransportVersion minimumVersion;
424439
// does not need to be Writable since this indicates a failure to contact a remote cluster, so only set on querying cluster
425440
final transient Exception connectionError;
426441

427-
LookupResponse(Map<String, ResolvedEnrichPolicy> policies, Map<String, String> failures) {
442+
LookupResponse(Map<String, ResolvedEnrichPolicy> policies, Map<String, String> failures, TransportVersion minimumVersion) {
428443
this.policies = policies;
429444
this.failures = failures;
445+
this.minimumVersion = minimumVersion;
430446
this.connectionError = null;
431447
}
432448

@@ -438,13 +454,27 @@ private static class LookupResponse extends TransportResponse {
438454
LookupResponse(Exception connectionError) {
439455
this.policies = Collections.emptyMap();
440456
this.failures = Collections.emptyMap();
457+
this.minimumVersion = TransportVersion.current();
441458
this.connectionError = connectionError;
442459
}
443460

444461
LookupResponse(StreamInput in) throws IOException {
445462
PlanStreamInput planIn = new PlanStreamInput(in, in.namedWriteableRegistry(), null);
446463
this.policies = planIn.readMap(StreamInput::readString, ResolvedEnrichPolicy::new);
447464
this.failures = planIn.readMap(StreamInput::readString, StreamInput::readString);
465+
if (in.getTransportVersion().supports(ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION)) {
466+
this.minimumVersion = TransportVersion.readVersion(in);
467+
} else {
468+
// A pre-9.2.1 node resolved the enrich policy for us, but doesn't say which version its cluster is on.
469+
// We can safely assume this node's current version, even though that's technically wrong.
470+
// Assuming a version that's too old can disable aggregate_metric_double and dense_vector
471+
// data types in the query, that'd be very bad.
472+
// But assuming these types are supported is fine because in 9.2.0,
473+
// they're not supported in enrich policies, anyway, due to bugs.
474+
// https://github.com/elastic/elasticsearch/issues/127350
475+
// https://github.com/elastic/elasticsearch/issues/137699
476+
this.minimumVersion = TransportVersion.minimumCompatible();
477+
}
448478
this.connectionError = null;
449479
}
450480

@@ -453,6 +483,9 @@ public void writeTo(StreamOutput out) throws IOException {
453483
PlanStreamOutput pso = new PlanStreamOutput(out, null);
454484
pso.writeMap(policies, StreamOutput::writeWriteable);
455485
pso.writeMap(failures, StreamOutput::writeString);
486+
if (out.getTransportVersion().supports(ESQL_USE_MINIMUM_VERSION_FOR_ENRICH_RESOLUTION)) {
487+
TransportVersion.writeVersion(minimumVersion, out);
488+
}
456489
}
457490
}
458491

@@ -462,12 +495,17 @@ public void messageReceived(LookupRequest request, TransportChannel channel, Tas
462495
final Map<String, EnrichPolicy> availablePolicies = availablePolicies();
463496
final Map<String, String> failures = ConcurrentCollections.newConcurrentMap();
464497
final Map<String, ResolvedEnrichPolicy> resolvedPolices = ConcurrentCollections.newConcurrentMap();
498+
// We use the coordinator's minimum version as base line.
499+
final Holder<TransportVersion> minimumVersion = new Holder<>(request.minimumVersion);
465500
ThreadContext threadContext = threadPool.getThreadContext();
466501
ActionListener<LookupResponse> listener = ContextPreservingActionListener.wrapPreservingContext(
467502
new ChannelActionListener<>(channel),
468503
threadContext
469504
);
470-
try (var refs = new RefCountingListener(listener.map(unused -> new LookupResponse(resolvedPolices, failures)))) {
505+
try (var refs = new RefCountingListener(listener.map(unused -> {
506+
TransportVersion finalMinimumVersion = minimumVersion.get();
507+
return new LookupResponse(resolvedPolices, failures, finalMinimumVersion);
508+
}))) {
471509
for (String policyName : request.policyNames) {
472510
EnrichPolicy p = availablePolicies.get(policyName);
473511
if (p == null) {
@@ -497,6 +535,11 @@ public void messageReceived(LookupRequest request, TransportChannel channel, Tas
497535
esIndex.mapping()
498536
);
499537
resolvedPolices.put(policyName, resolved);
538+
synchronized (minimumVersion) {
539+
minimumVersion.set(
540+
TransportVersion.min(minimumVersion.get(), versionedIndexResult.minimumVersion())
541+
);
542+
}
500543
} else {
501544
failures.put(policyName, indexResult.toString());
502545
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,10 @@ private void resolveIndicesAndAnalyze(
570570
preAnalysis.enriches(),
571571
executionInfo,
572572
r.minimumTransportVersion(),
573-
l.map(r::withEnrichResolution)
573+
l.map(
574+
versionedResolution -> r.withEnrichResolution(versionedResolution.inner())
575+
.withMinimumTransportVersion(versionedResolution.minimumVersion())
576+
)
574577
);
575578
})
576579
.<PreAnalysisResult>andThen((l, r) -> {
@@ -583,7 +586,9 @@ private void resolveIndicesAndAnalyze(
583586
}
584587

585588
/**
586-
* Perform a field caps request for each lookup index. Does not update the minimum transport version.
589+
* Perform a field caps request for each lookup index.
590+
* <p>
591+
* Updates the minimum transport version to deal with ROW queries, where the main index resolution does not make a field caps request.
587592
*/
588593
private void preAnalyzeLookupIndices(
589594
Iterator<IndexPattern> lookupIndices,

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,12 @@ public void resolveAsMergedMapping(
129129
useAggregateMetricDoubleWhenNotSupported,
130130
useDenseVectorWhenNotSupported
131131
);
132-
// NOCOMMIT: Update logging here as well
133-
LOGGER.debug("minimum transport version {} {}", response.caps().minTransportVersion(), info.effectiveMinTransportVersion());
132+
LOGGER.debug(
133+
"updated minimum transport version from {} to effective version {} using version {} from field caps response",
134+
minimumVersion,
135+
info.effectiveMinTransportVersion(),
136+
response.caps().minTransportVersion()
137+
);
134138
l.onResponse(new Versioned<>(mergedMappings(indexPattern, info), info.effectiveMinTransportVersion()));
135139
})
136140
);

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolverTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.elasticsearch.xpack.esql.analysis.EnrichResolution;
4747
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
4848
import org.elasticsearch.xpack.esql.session.IndexResolver;
49+
import org.elasticsearch.xpack.esql.session.Versioned;
4950
import org.junit.After;
5051
import org.junit.Before;
5152

@@ -453,10 +454,9 @@ EnrichResolution resolvePolicies(Collection<String> clusters, Collection<Unresol
453454
unresolvedPolicies.add(new UnresolvedPolicy("legacy-policy-1", randomFrom(Enrich.Mode.values())));
454455
}
455456
}
456-
PlainActionFuture<EnrichResolution> future = new PlainActionFuture<>();
457-
// NOCOMMIT: We should pass in a sensible transport version in general and also test this with older ones.
457+
PlainActionFuture<Versioned<EnrichResolution>> future = new PlainActionFuture<>();
458458
super.doResolvePolicies(new HashSet<>(clusters), unresolvedPolicies, esqlExecutionInfo, TransportVersion.current(), future);
459-
return future.actionGet(30, TimeUnit.SECONDS);
459+
return future.actionGet(30, TimeUnit.SECONDS).inner();
460460
}
461461

462462
@Override

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.esql.telemetry;
99

10+
import org.elasticsearch.TransportVersion;
1011
import org.elasticsearch.action.ActionListener;
1112
import org.elasticsearch.action.OriginalIndices;
1213
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
@@ -82,8 +83,9 @@ EnrichPolicyResolver mockEnrichResolver() {
8283
EnrichPolicyResolver enrichResolver = mock(EnrichPolicyResolver.class);
8384
doAnswer(invocation -> {
8485
Object[] arguments = invocation.getArguments();
85-
ActionListener<EnrichResolution> listener = (ActionListener<EnrichResolution>) arguments[arguments.length - 1];
86-
listener.onResponse(new EnrichResolution());
86+
ActionListener<Versioned<EnrichResolution>> listener = (ActionListener<Versioned<EnrichResolution>>) arguments[arguments.length
87+
- 1];
88+
listener.onResponse(new Versioned<>(new EnrichResolution(), TransportVersion.current()));
8789
return null;
8890
}).when(enrichResolver).resolvePolicies(any(), any(), any(), any());
8991
return enrichResolver;

0 commit comments

Comments
 (0)