diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithFiltersIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithFiltersIT.java index 2fe25eae7bc94..91f1cc12851dc 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithFiltersIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithFiltersIT.java @@ -9,8 +9,10 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.client.internal.Client; +import org.elasticsearch.cluster.RemoteException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.test.transport.MockTransportService; @@ -275,13 +277,13 @@ public void testFilterWithMissingIndex() { ); assertThat(e.getDetailedMessage(), containsString("Unknown index [missing]")); // Local index missing + wildcards - // FIXME: planner does not catch this now - // e = expectThrows(VerificationException.class, () -> runQuery("from missing,logs*", randomBoolean(), filter).close()); - // assertThat(e.getDetailedMessage(), containsString("Unknown index [missing]")); + // FIXME: planner does not catch this now, it should be VerificationException but for now it's runtime IndexNotFoundException + var ie = expectThrows(IndexNotFoundException.class, () -> runQuery("from missing,logs*", randomBoolean(), filter).close()); + assertThat(ie.getDetailedMessage(), containsString("no such index [missing]")); // Local index missing + existing index - // FIXME: planner does not catch this now - // e = expectThrows(VerificationException.class, () -> runQuery("from missing,logs-1", randomBoolean(), filter).close()); - // assertThat(e.getDetailedMessage(), containsString("Unknown index [missing]")); + // FIXME: planner does not catch this now, it should be VerificationException but for now it's runtime IndexNotFoundException + ie = expectThrows(IndexNotFoundException.class, () -> runQuery("from missing,logs-1", randomBoolean(), filter).close()); + assertThat(ie.getDetailedMessage(), containsString("no such index [missing]")); // Local index missing + existing remote e = expectThrows(VerificationException.class, () -> runQuery("from missing,cluster-a:logs-2", randomBoolean(), filter).close()); assertThat(e.getDetailedMessage(), containsString("Unknown index [missing]")); @@ -318,15 +320,19 @@ public void testFilterWithMissingRemoteIndex() { ); assertThat(e.getDetailedMessage(), containsString("Unknown index [cluster-a:missing]")); // Local index missing + wildcards - // FIXME: planner does not catch this now - // e = expectThrows(VerificationException.class, () -> runQuery("from cluster-a:missing,cluster-a:logs*", randomBoolean(), - // filter).close()); - // assertThat(e.getDetailedMessage(), containsString("Unknown index [cluster-a:missing]")); + // FIXME: planner does not catch this now, it should be VerificationException but for now it's runtime RemoteException + var ie = expectThrows( + RemoteException.class, + () -> runQuery("from cluster-a:missing,cluster-a:logs*", randomBoolean(), filter).close() + ); + assertThat(ie.getDetailedMessage(), containsString("no such index [missing]")); // Local index missing + existing index - // FIXME: planner does not catch this now - // e = expectThrows(VerificationException.class, () -> runQuery("from cluster-a:missing,cluster-a:logs-2", randomBoolean(), - // filter).close()); - // assertThat(e.getDetailedMessage(), containsString("Unknown index [cluster-a:missing]")); + // FIXME: planner does not catch this now, it should be VerificationException but for now it's runtime RemoteException + ie = expectThrows( + RemoteException.class, + () -> runQuery("from cluster-a:missing,cluster-a:logs-2", randomBoolean(), filter).close() + ); + assertThat(ie.getDetailedMessage(), containsString("no such index [missing]")); // Local index + missing remote e = expectThrows(VerificationException.class, () -> runQuery("from logs-1,cluster-a:missing", randomBoolean(), filter).close()); assertThat(e.getDetailedMessage(), containsString("Unknown index [cluster-a:missing]")); diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithPartialResultsIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithPartialResultsIT.java index 463dbb81304a1..c6d45486549e2 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithPartialResultsIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryWithPartialResultsIT.java @@ -161,6 +161,34 @@ public void testOneRemoteClusterPartial() throws Exception { } } + public void testLocalIndexMissing() throws Exception { + populateIndices(); + EsqlQueryRequest request = new EsqlQueryRequest(); + request.query("FROM ok-local,no_such_index | LIMIT 1"); + request.includeCCSMetadata(randomBoolean()); + for (boolean allowPartial : Set.of(true, false)) { + request.allowPartialResults(allowPartial); + Exception error = expectThrows(Exception.class, () -> runQuery(request).close()); + error = EsqlTestUtils.unwrapIfWrappedInRemoteException(error); + assertThat(error.getMessage(), containsString("no such index")); + assertThat(error.getMessage(), containsString("[no_such_index]")); + } + } + + public void testRemoteIndexMissing() throws Exception { + populateIndices(); + EsqlQueryRequest request = new EsqlQueryRequest(); + request.query("FROM cluster-a:ok-cluster1,cluster-a:no_such_index | LIMIT 1"); + request.includeCCSMetadata(randomBoolean()); + for (boolean allowPartial : Set.of(true, false)) { + request.allowPartialResults(allowPartial); + Exception error = expectThrows(Exception.class, () -> runQuery(request).close()); + error = EsqlTestUtils.unwrapIfWrappedInRemoteException(error); + assertThat(error.getMessage(), containsString("no such index")); + assertThat(error.getMessage(), containsString("[no_such_index]")); + } + } + public void testFailToReceiveClusterResponse() throws Exception { populateIndices(); Exception simulatedFailure = randomFailure(); diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterUsageTelemetryIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterUsageTelemetryIT.java index fdfbe9c6bf9d5..50976c751c100 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterUsageTelemetryIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterUsageTelemetryIT.java @@ -138,32 +138,6 @@ public void testFailed() throws Exception { assertThat(telemetry.getByRemoteCluster().size(), equalTo(0)); } - // TODO: enable when skip-un patch is merged - // public void testSkipAllRemotes() throws Exception { - // var telemetry = getTelemetryFromQuery("from logs-*,c*:no_such_index | stats sum (v)", "unknown"); - // - // assertThat(telemetry.getTotalCount(), equalTo(1L)); - // assertThat(telemetry.getSuccessCount(), equalTo(1L)); - // assertThat(telemetry.getFailureReasons().size(), equalTo(0)); - // assertThat(telemetry.getTook().count(), equalTo(1L)); - // assertThat(telemetry.getTookMrtFalse().count(), equalTo(0L)); - // assertThat(telemetry.getTookMrtTrue().count(), equalTo(0L)); - // assertThat(telemetry.getRemotesPerSearchAvg(), equalTo(2.0)); - // assertThat(telemetry.getRemotesPerSearchMax(), equalTo(2L)); - // assertThat(telemetry.getSearchCountWithSkippedRemotes(), equalTo(1L)); - // assertThat(telemetry.getClientCounts().size(), equalTo(0)); - // - // var perCluster = telemetry.getByRemoteCluster(); - // assertThat(perCluster.size(), equalTo(3)); - // for (String clusterAlias : remoteClusterAlias()) { - // var clusterData = perCluster.get(clusterAlias); - // assertThat(clusterData.getCount(), equalTo(0L)); - // assertThat(clusterData.getSkippedCount(), equalTo(1L)); - // assertThat(clusterData.getTook().count(), equalTo(0L)); - // } - // assertPerClusterCount(perCluster.get(LOCAL_CLUSTER), 1L); - // } - public void testRemoteOnly() throws Exception { setupClusters(); var telemetry = getTelemetryFromQuery("from c*:logs-* | stats sum (v)", "kibana"); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ClusterComputeHandler.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ClusterComputeHandler.java index 9cbe37a2f2d63..94e06f77c7571 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ClusterComputeHandler.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ClusterComputeHandler.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.plugin; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListenerResponseHandler; import org.elasticsearch.action.OriginalIndices; @@ -16,6 +17,7 @@ import org.elasticsearch.compute.operator.exchange.ExchangeSourceHandler; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskCancelledException; @@ -88,12 +90,18 @@ void startComputeOnRemoteCluster( if (receivedResults == false && EsqlCCSUtils.shouldIgnoreRuntimeError(executionInfo, clusterAlias, e)) { EsqlCCSUtils.markClusterWithFinalStateAndNoShards(executionInfo, clusterAlias, EsqlExecutionInfo.Cluster.Status.SKIPPED, e); l.onResponse(List.of()); - } else if (configuration.allowPartialResults()) { - EsqlCCSUtils.markClusterWithFinalStateAndNoShards(executionInfo, clusterAlias, EsqlExecutionInfo.Cluster.Status.PARTIAL, e); - l.onResponse(List.of()); - } else { - l.onFailure(e); - } + } else if (configuration.allowPartialResults() + && (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) == false) { + EsqlCCSUtils.markClusterWithFinalStateAndNoShards( + executionInfo, + clusterAlias, + EsqlExecutionInfo.Cluster.Status.PARTIAL, + e + ); + l.onResponse(List.of()); + } else { + l.onFailure(e); + } }); ExchangeService.openExchange( transportService, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java index 7a7526302a587..5a89cd5df06b3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.plugin; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.action.search.SearchRequest; @@ -27,6 +28,7 @@ import org.elasticsearch.core.Releasables; import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; +import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -274,7 +276,8 @@ public void execute( ); dataNodesListener.onResponse(r.getProfiles()); }, e -> { - if (configuration.allowPartialResults()) { + if (configuration.allowPartialResults() + && (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) == false) { execInfo.swapCluster( LOCAL_CLUSTER, (k, v) -> new EsqlExecutionInfo.Cluster.Builder(v).setStatus(