1414import org .elasticsearch .action .fieldcaps .FieldCapabilitiesFailure ;
1515import org .elasticsearch .action .search .ShardSearchFailure ;
1616import org .elasticsearch .action .support .SubscribableListener ;
17+ import org .elasticsearch .common .TriConsumer ;
1718import org .elasticsearch .common .collect .Iterators ;
1819import org .elasticsearch .common .unit .ByteSizeValue ;
1920import org .elasticsearch .compute .data .Block ;
3334import org .elasticsearch .logging .LogManager ;
3435import org .elasticsearch .logging .Logger ;
3536import org .elasticsearch .search .SearchShardTarget ;
37+ import org .elasticsearch .search .crossproject .CrossProjectModeDecider ;
3638import org .elasticsearch .threadpool .ThreadPool ;
3739import org .elasticsearch .transport .RemoteClusterAware ;
3840import org .elasticsearch .transport .RemoteClusterService ;
@@ -134,6 +136,7 @@ public interface PlanRunner {
134136 private final RemoteClusterService remoteClusterService ;
135137 private final BlockFactory blockFactory ;
136138 private final ByteSizeValue intermediateLocalRelationMaxSize ;
139+ private final CrossProjectModeDecider crossProjectModeDecider ;
137140 private final String clusterName ;
138141
139142 private boolean explainMode ;
@@ -168,6 +171,7 @@ public EsqlSession(
168171 this .remoteClusterService = services .transportService ().getRemoteClusterService ();
169172 this .blockFactory = services .blockFactoryProvider ().blockFactory ();
170173 this .intermediateLocalRelationMaxSize = services .plannerSettings ().intermediateLocalRelationMaxSize ();
174+ this .crossProjectModeDecider = services .crossProjectModeDecider ();
171175 this .clusterName = services .clusterService ().getClusterName ().value ();
172176 }
173177
@@ -544,27 +548,16 @@ private void resolveIndicesAndAnalyze(
544548 PreAnalysisResult result ,
545549 ActionListener <Versioned <LogicalPlan >> logicalPlanListener
546550 ) {
547- EsqlCCSUtils .initCrossClusterState (
548- indicesExpressionGrouper ,
549- verifier .licenseState (),
550- preAnalysis .indexes ().keySet (),
551- executionInfo
552- );
553-
554- SubscribableListener .<PreAnalysisResult >newForked (
555- // The main index pattern dictates on which nodes the query can be executed, so we use the minimum transport version from this
556- // field
557- // caps request.
558- l -> preAnalyzeMainIndices (preAnalysis .indexes ().entrySet ().iterator (), preAnalysis , executionInfo , result , requestFilter , l )
559- ).andThenApply (r -> {
560- if (r .indexResolution .isEmpty () == false // Rule out ROW case with no FROM clauses
561- && executionInfo .isCrossClusterSearch ()
562- && executionInfo .getRunningClusterAliases ().findAny ().isEmpty ()) {
563- LOGGER .debug ("No more clusters to search, ending analysis stage" );
564- throw new NoClustersToSearchException ();
565- }
566- return r ;
567- })
551+ SubscribableListener .<PreAnalysisResult >newForked (l -> preAnalyzeMainIndices (preAnalysis , executionInfo , result , requestFilter , l ))
552+ .andThenApply (r -> {
553+ if (r .indexResolution .isEmpty () == false // Rule out ROW case with no FROM clauses
554+ && executionInfo .isCrossClusterSearch ()
555+ && executionInfo .getRunningClusterAliases ().findAny ().isEmpty ()) {
556+ LOGGER .debug ("No more clusters to search, ending analysis stage" );
557+ throw new NoClustersToSearchException ();
558+ }
559+ return r ;
560+ })
568561 .<PreAnalysisResult >andThen ((l , r ) -> preAnalyzeLookupIndices (preAnalysis .lookupIndices ().iterator (), r , executionInfo , l ))
569562 .<PreAnalysisResult >andThen ((l , r ) -> {
570563 enrichPolicyResolver .resolvePolicies (preAnalysis .enriches (), executionInfo , l .map (r ::withEnrichResolution ));
@@ -587,13 +580,7 @@ private void preAnalyzeLookupIndices(
587580 EsqlExecutionInfo executionInfo ,
588581 ActionListener <PreAnalysisResult > listener
589582 ) {
590- if (lookupIndices .hasNext ()) {
591- preAnalyzeLookupIndex (lookupIndices .next (), preAnalysisResult , executionInfo , listener .delegateFailureAndWrap ((l , r ) -> {
592- preAnalyzeLookupIndices (lookupIndices , r , executionInfo , l );
593- }));
594- } else {
595- listener .onResponse (preAnalysisResult );
596- }
583+ forAll (lookupIndices , preAnalysisResult , (lookupIndex , r , l ) -> preAnalyzeLookupIndex (lookupIndex , r , executionInfo , l ), listener );
597584 }
598585
599586 private void preAnalyzeLookupIndex (
@@ -805,29 +792,26 @@ private void validateRemoteVersions(EsqlExecutionInfo executionInfo) {
805792 * indices.
806793 */
807794 private void preAnalyzeMainIndices (
808- Iterator <Map .Entry <IndexPattern , IndexMode >> indexPatterns ,
809795 PreAnalyzer .PreAnalysis preAnalysis ,
810796 EsqlExecutionInfo executionInfo ,
811797 PreAnalysisResult result ,
812798 QueryBuilder requestFilter ,
813799 ActionListener <PreAnalysisResult > listener
814800 ) {
815- if (indexPatterns .hasNext ()) {
816- var index = indexPatterns .next ();
817- preAnalyzeMainIndices (
818- index .getKey (),
819- index .getValue (),
820- preAnalysis ,
821- executionInfo ,
822- result ,
823- requestFilter ,
824- listener .delegateFailureAndWrap ((l , r ) -> {
825- preAnalyzeMainIndices (indexPatterns , preAnalysis , executionInfo , r , requestFilter , l );
826- })
827- );
828- } else {
829- listener .onResponse (result );
830- }
801+ EsqlCCSUtils .initCrossClusterState (
802+ indicesExpressionGrouper ,
803+ verifier .licenseState (),
804+ preAnalysis .indexes ().keySet (),
805+ executionInfo
806+ );
807+ // The main index pattern dictates on which nodes the query can be executed,
808+ // so we use the minimum transport version from this field caps request.
809+ forAll (
810+ preAnalysis .indexes ().entrySet ().iterator (),
811+ result ,
812+ (entry , r , l ) -> preAnalyzeMainIndices (entry .getKey (), entry .getValue (), preAnalysis , executionInfo , r , requestFilter , l ),
813+ listener
814+ );
831815 }
832816
833817 private void preAnalyzeMainIndices (
@@ -844,12 +828,9 @@ private void preAnalyzeMainIndices(
844828 ThreadPool .Names .SEARCH_COORDINATION ,
845829 ThreadPool .Names .SYSTEM_READ
846830 );
847- // TODO: This is not yet index specific, but that will not matter as soon as #136804 is dealt with
848831 if (executionInfo .clusterAliases ().isEmpty ()) {
849832 // return empty resolution if the expression is pure CCS and resolved no remote clusters (like no-such-cluster*:index)
850- listener .onResponse (
851- result .withIndices (indexPattern , IndexResolution .valid (new EsIndex (indexPattern .indexPattern (), Map .of (), Map .of ())))
852- );
833+ listener .onResponse (result .withIndices (indexPattern , IndexResolution .empty (indexPattern .indexPattern ())));
853834 } else {
854835 indexResolver .resolveAsMergedMappingAndRetrieveMinimumVersion (
855836 indexPattern .indexPattern (),
@@ -994,6 +975,19 @@ private PhysicalPlan optimizedPhysicalPlan(LogicalPlan optimizedPlan, PhysicalPl
994975 return plan ;
995976 }
996977
978+ private static <T > void forAll (
979+ Iterator <T > iterator ,
980+ PreAnalysisResult result ,
981+ TriConsumer <T , PreAnalysisResult , ActionListener <PreAnalysisResult >> consumer ,
982+ ActionListener <PreAnalysisResult > listener
983+ ) {
984+ if (iterator .hasNext ()) {
985+ consumer .apply (iterator .next (), result , listener .delegateFailureAndWrap ((l , r ) -> forAll (iterator , r , consumer , l )));
986+ } else {
987+ listener .onResponse (result );
988+ }
989+ }
990+
997991 public record PreAnalysisResult (
998992 Set <String > fieldNames ,
999993 Set <String > wildcardJoinIndices ,
0 commit comments