2626import org .elasticsearch .compute .operator .exchange .ExchangeSink ;
2727import org .elasticsearch .compute .operator .exchange .ExchangeSinkHandler ;
2828import org .elasticsearch .compute .operator .exchange .ExchangeSourceHandler ;
29+ import org .elasticsearch .core .Nullable ;
2930import org .elasticsearch .core .RefCounted ;
3031import org .elasticsearch .core .Releasable ;
3132import org .elasticsearch .core .Releasables ;
4950import org .elasticsearch .transport .TransportService ;
5051import org .elasticsearch .xpack .esql .action .EsqlExecutionInfo ;
5152import org .elasticsearch .xpack .esql .action .EsqlQueryAction ;
53+ import org .elasticsearch .xpack .esql .action .stream .EsqlQueryResponseStream ;
5254import org .elasticsearch .xpack .esql .core .expression .Attribute ;
5355import org .elasticsearch .xpack .esql .core .expression .FoldContext ;
5456import org .elasticsearch .xpack .esql .enrich .EnrichLookupService ;
@@ -189,6 +191,7 @@ public void execute(
189191 Configuration configuration ,
190192 FoldContext foldContext ,
191193 EsqlExecutionInfo execInfo ,
194+ EsqlQueryResponseStream responseStream ,
192195 ActionListener <Result > listener
193196 ) {
194197 assert ThreadPool .assertCurrentThreadPool (
@@ -204,12 +207,31 @@ public void execute(
204207
205208 // we have no sub plans, so we can just execute the given plan
206209 if (subplans == null || subplans .isEmpty ()) {
207- executePlan (sessionId , rootTask , flags , physicalPlan , configuration , foldContext , execInfo , null , listener , null );
210+ responseStream .startResponse (physicalPlan .output ());
211+
212+ executePlan (
213+ sessionId ,
214+ rootTask ,
215+ flags ,
216+ physicalPlan ,
217+ configuration ,
218+ foldContext ,
219+ execInfo ,
220+ null ,
221+ listener ,
222+ responseStream ,
223+ null
224+ );
208225 return ;
209226 }
210227
211228 final List <Page > collectedPages = Collections .synchronizedList (new ArrayList <>());
212- PhysicalPlan mainPlan = new OutputExec (subplansAndMainPlan .v2 (), collectedPages ::add );
229+ PhysicalPlan mainPlan = new OutputExec (subplansAndMainPlan .v2 (), page -> {
230+ collectedPages .add (page );
231+ responseStream .sendPages (List .of (page ));
232+ });
233+
234+ responseStream .startResponse (mainPlan .output ());
213235
214236 listener = listener .delegateResponse ((l , e ) -> {
215237 collectedPages .forEach (p -> Releasables .closeExpectNoException (p ::releaseBlocks ));
@@ -279,6 +301,7 @@ public void execute(
279301 exchangeService .finishSinkHandler (childSessionId , e );
280302 subPlanListener .onFailure (e );
281303 }),
304+ null ,
282305 () -> exchangeSink .createExchangeSink (() -> {})
283306 );
284307 }
@@ -294,9 +317,10 @@ public void executePlan(
294317 Configuration configuration ,
295318 FoldContext foldContext ,
296319 EsqlExecutionInfo execInfo ,
297- String profileQualifier ,
320+ @ Nullable String profileQualifier ,
298321 ActionListener <Result > listener ,
299- Supplier <ExchangeSink > exchangeSinkSupplier
322+ @ Nullable EsqlQueryResponseStream responseStream ,
323+ @ Nullable Supplier <ExchangeSink > exchangeSinkSupplier
300324 ) {
301325 Tuple <PhysicalPlan , PhysicalPlan > coordinatorAndDataNodePlan = PlannerUtils .breakPlanBetweenCoordinatorAndDataNode (
302326 physicalPlan ,
@@ -310,7 +334,11 @@ public void executePlan(
310334 PhysicalPlan coordinatorPlan = coordinatorAndDataNodePlan .v1 ();
311335
312336 if (exchangeSinkSupplier == null ) {
313- coordinatorPlan = new OutputExec (coordinatorAndDataNodePlan .v1 (), collectedPages ::add );
337+ assert responseStream != null : "responseStream must not be null when exchangeSinkSupplier is null" ;
338+ coordinatorPlan = new OutputExec (coordinatorAndDataNodePlan .v1 (), page -> {
339+ collectedPages .add (page );
340+ responseStream .sendPages (List .of (page ));
341+ });
314342 }
315343
316344 PhysicalPlan dataNodePlan = coordinatorAndDataNodePlan .v2 ();
0 commit comments