diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/AttributeMap.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/AttributeMap.java index 9a3e487bca3f9..473882d90dad7 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/AttributeMap.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/AttributeMap.java @@ -244,7 +244,6 @@ private E delete(Object key) { public Set attributeNames() { Set s = Sets.newLinkedHashSetWithExpectedSize(size()); - for (AttributeWrapper aw : delegate.keySet()) { s.add(aw.attr.name()); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java index b62da0bce2564..2e535fe47f852 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java @@ -401,6 +401,15 @@ public void analyzedPlan( .andThenApply(enrichResolution -> FieldNameUtils.resolveFieldNames(parsed, enrichResolution)) .andThen((l, r) -> resolveInferences(parsed, r, l)) .andThen((l, r) -> preAnalyzeMainIndices(preAnalysis, executionInfo, r, requestFilter, l)) + .andThenApply(r -> { + if (r.indices.isValid() + && executionInfo.isCrossClusterSearch() + && executionInfo.getRunningClusterAliases().findAny().isEmpty()) { + LOGGER.debug("No more clusters to search, ending analysis stage"); + throw new NoClustersToSearchException(); + } + return r; + }) .andThen((l, r) -> preAnalyzeLookupIndices(preAnalysis.lookupIndices().iterator(), r, executionInfo, l)) .andThen((l, r) -> analyzeWithRetry(parsed, requestFilter, preAnalysis, executionInfo, r, l)) .addListener(logicalPlanListener); @@ -657,7 +666,8 @@ private void preAnalyzeMainIndices( default -> requestFilter; }, preAnalysis.indexMode() == IndexMode.TIME_SERIES, - listener.delegateFailure((l, indexResolution) -> { + listener.delegateFailureAndWrap((l, indexResolution) -> { + EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, indexResolution.failures()); l.onResponse(result.withIndexResolution(indexResolution)); }) ); @@ -676,21 +686,8 @@ private void analyzeWithRetry( PreAnalysisResult result, ActionListener listener ) { - if (result.indices.isValid()) { - EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, result.indices.failures()); - if (executionInfo.isCrossClusterSearch() - && executionInfo.getClusterStates(EsqlExecutionInfo.Cluster.Status.RUNNING).findAny().isEmpty()) { - // for a CCS, if all clusters have been marked as SKIPPED, nothing to search so send a sentinel Exception - // to let the LogicalPlanActionListener decide how to proceed - LOGGER.debug("No more clusters to search, ending analysis stage"); - listener.onFailure(new NoClustersToSearchException()); - return; - } - } - var description = requestFilter == null ? "the only attempt without filter" : "first attempt with filter"; LOGGER.debug("Analyzing the plan ({})", description); - try { if (result.indices.isValid() || requestFilter != null) { // We won't run this check with no filter and no valid indices since this may lead to false positive - missing index report @@ -715,7 +712,6 @@ private void analyzeWithRetry( try { // the order here is tricky - if the cluster has been filtered and later became unavailable, // do we want to declare it successful or skipped? For now, unavailability takes precedence. - EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, r.indices.failures()); EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, r.indices, false); LogicalPlan plan = analyzedPlan(parsed, r, executionInfo); LOGGER.debug("Analyzed plan (second attempt without filter):\n{}", plan);