Skip to main content

mz_adapter/coord/sequencer/inner/
subscribe.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use futures::StreamExt;
11use maplit::btreemap;
12use mz_adapter_types::connection::ConnectionId;
13use mz_adapter_types::dyncfgs::SUBSCRIBE_MAX_BUFFERED_BYTES;
14use mz_cluster_client::ReplicaId;
15use mz_compute_types::ComputeInstanceId;
16use mz_compute_types::dataflows::LirDataflowDescription;
17use mz_ore::collections::CollectionExt;
18use mz_ore::instrument;
19use mz_repr::GlobalId;
20use mz_repr::Timestamp;
21use mz_repr::explain::{ExprHumanizerExt, TransientItem};
22use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
23use mz_sql::plan::{self, QueryWhen, SubscribeFrom};
24use mz_sql::session::metadata::SessionMetadata;
25use std::collections::BTreeSet;
26use std::sync::{Arc, Mutex};
27use timely::PartialOrder;
28use timely::progress::Antichain;
29use tokio::sync::mpsc;
30use tokio_stream::wrappers::UnboundedReceiverStream;
31use tracing::{Instrument, Span};
32use uuid::Uuid;
33
34use crate::active_compute_sink::{
35    ActiveComputeSink, ActiveComputeSinkRetireReason, ActiveSubscribe, ActiveSubscribeOwner,
36    SubscribeBacklogAccounting,
37};
38use crate::command::ExecuteResponse;
39use crate::coord::appends::BuiltinTableAppendNotify;
40use crate::coord::peek::PeekResponseUnary;
41use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
42use crate::coord::sequencer::{check_log_reads, emit_optimizer_notices};
43use crate::coord::{
44    Coordinator, ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged,
45    SubscribeExplain, SubscribeFinish, SubscribeLinearizeTimestamp, SubscribeOptimizeMir,
46    SubscribeStage, SubscribeTimestampOptimizeLir, TargetCluster,
47};
48use crate::error::AdapterError;
49use crate::explain::optimizer_trace::OptimizerTrace;
50use crate::optimize::Optimize;
51use crate::session::{Session, TransactionOps};
52use crate::{
53    AdapterNotice, ExecuteContext, ExecuteContextGuard, ReadHolds, TimelineContext, optimize,
54};
55
56impl Staged for SubscribeStage {
57    type Ctx = ExecuteContext;
58
59    fn validity(&mut self) -> &mut PlanValidity {
60        match self {
61            SubscribeStage::OptimizeMir(stage) => &mut stage.validity,
62            SubscribeStage::LinearizeTimestamp(stage) => &mut stage.validity,
63            SubscribeStage::TimestampOptimizeLir(stage) => &mut stage.validity,
64            SubscribeStage::Finish(stage) => &mut stage.validity,
65            SubscribeStage::Explain(stage) => &mut stage.validity,
66        }
67    }
68
69    async fn stage(
70        self,
71        coord: &mut Coordinator,
72        ctx: &mut ExecuteContext,
73    ) -> Result<StageResult<Box<Self>>, AdapterError> {
74        match self {
75            SubscribeStage::OptimizeMir(stage) => coord.subscribe_optimize_mir(stage),
76            SubscribeStage::LinearizeTimestamp(stage) => {
77                coord
78                    .subscribe_linearize_timestamp(ctx.session(), stage)
79                    .await
80            }
81            SubscribeStage::TimestampOptimizeLir(stage) => {
82                coord.subscribe_timestamp_optimize_lir(ctx, stage).await
83            }
84            SubscribeStage::Finish(stage) => coord.subscribe_finish(ctx, stage).await,
85            SubscribeStage::Explain(stage) => coord.subscribe_explain(ctx.session(), stage).await,
86        }
87    }
88
89    fn message(self, ctx: ExecuteContext, span: Span) -> Message {
90        Message::SubscribeStageReady {
91            ctx,
92            span,
93            stage: self,
94        }
95    }
96
97    fn cancel_enabled(&self) -> bool {
98        // `Finish` installs the sink before it waits for the builtin-table write
99        // off-loop. If cancellation won during that wait, the active sink would
100        // outlive the canceled execution.
101        !matches!(self, SubscribeStage::Finish(_))
102    }
103}
104
105impl Coordinator {
106    #[instrument]
107    pub(crate) async fn sequence_subscribe(
108        &mut self,
109        mut ctx: ExecuteContext,
110        plan: plan::SubscribePlan,
111        target_cluster: TargetCluster,
112    ) {
113        let stage = return_if_err!(
114            self.subscribe_validate(
115                ctx.session_mut(),
116                plan,
117                target_cluster,
118                ExplainContext::None
119            ),
120            ctx
121        );
122        self.sequence_staged(ctx, Span::current(), stage).await;
123    }
124
125    #[instrument]
126    pub(crate) async fn explain_subscribe(
127        &mut self,
128        mut ctx: ExecuteContext,
129        plan::ExplainPlanPlan {
130            stage,
131            format,
132            config,
133            explainee,
134        }: plan::ExplainPlanPlan,
135        target_cluster: TargetCluster,
136    ) {
137        let plan::Explainee::Statement(stmt) = explainee else {
138            // This is currently asserted in the `sequence_explain_plan` code that
139            // calls this method.
140            unreachable!()
141        };
142        let plan::ExplaineeStatement::Subscribe { broken, plan } = stmt else {
143            // This is currently asserted in the `sequence_explain_plan` code that
144            // calls this method.
145            unreachable!()
146        };
147
148        let desc = match &plan.from {
149            SubscribeFrom::Id(_) => None,
150            SubscribeFrom::Query { desc, .. } => Some(desc.clone()),
151        };
152
153        // Create an OptimizerTrace instance to collect plans emitted when
154        // executing the optimizer pipeline.
155        let optimizer_trace = OptimizerTrace::new(stage.paths());
156
157        let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
158            broken,
159            config,
160            format,
161            stage,
162            replan: None,
163            desc,
164            optimizer_trace,
165        });
166        let stage = return_if_err!(
167            self.subscribe_validate(ctx.session_mut(), plan, target_cluster, explain_ctx),
168            ctx
169        );
170        self.sequence_staged(ctx, Span::current(), stage).await;
171    }
172
173    #[instrument]
174    fn subscribe_validate(
175        &self,
176        session: &mut Session,
177        plan: plan::SubscribePlan,
178        target_cluster: TargetCluster,
179        explain_ctx: ExplainContext,
180    ) -> Result<SubscribeStage, AdapterError> {
181        let plan::SubscribePlan { from, when, .. } = &plan;
182
183        let cluster = self
184            .catalog()
185            .resolve_target_cluster(target_cluster, session)?;
186        let cluster_id = cluster.id;
187
188        // Only check cluster replicas if we're not in explain mode.
189        if explain_ctx.needs_cluster() && cluster.replicas().next().is_none() {
190            return Err(AdapterError::NoClusterReplicasAvailable {
191                name: cluster.name.clone(),
192                is_managed: cluster.is_managed(),
193            });
194        }
195
196        let mut replica_id = session
197            .vars()
198            .cluster_replica()
199            .map(|name| {
200                cluster
201                    .replica_id(name)
202                    .ok_or(AdapterError::UnknownClusterReplica {
203                        cluster_name: cluster.name.clone(),
204                        replica_name: name.to_string(),
205                    })
206            })
207            .transpose()?;
208
209        // SUBSCRIBE AS OF, similar to peeks, doesn't need to worry about transaction
210        // timestamp semantics.
211        if explain_ctx.needs_cluster() && when == &QueryWhen::Immediately {
212            // If this isn't a SUBSCRIBE AS OF, the SUBSCRIBE can be in a transaction if it's the
213            // only operation.
214            session.add_transaction_ops(TransactionOps::Subscribe)?;
215        }
216
217        let depends_on = from.depends_on();
218
219        // Run `check_log_reads` and emit notices.
220        let notices = check_log_reads(
221            self.catalog(),
222            cluster,
223            &depends_on,
224            &mut replica_id,
225            session.vars(),
226        )?;
227        session.add_notices(notices);
228
229        // Determine timeline.
230        let mut timeline = self
231            .catalog()
232            .validate_timeline_context(depends_on.iter().copied())?;
233        if matches!(timeline, TimelineContext::TimestampIndependent) && from.contains_temporal() {
234            // If the from IDs are timestamp independent but the query contains temporal functions
235            // then the timeline context needs to be upgraded to timestamp dependent.
236            timeline = TimelineContext::TimestampDependent;
237        }
238
239        let dependencies = depends_on
240            .iter()
241            .map(|id| self.catalog().resolve_item_id(id))
242            .collect();
243        let validity = PlanValidity::new(
244            self.catalog(),
245            dependencies,
246            Some(cluster_id),
247            replica_id,
248            session.role_metadata().clone(),
249        );
250
251        Ok(SubscribeStage::OptimizeMir(SubscribeOptimizeMir {
252            validity,
253            plan,
254            timeline,
255            dependency_ids: depends_on,
256            cluster_id,
257            replica_id,
258            explain_ctx,
259        }))
260    }
261
262    #[instrument]
263    fn subscribe_optimize_mir(
264        &self,
265        SubscribeOptimizeMir {
266            mut validity,
267            plan,
268            timeline,
269            dependency_ids,
270            cluster_id,
271            replica_id,
272            explain_ctx,
273        }: SubscribeOptimizeMir,
274    ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
275        let plan::SubscribePlan {
276            with_snapshot,
277            up_to,
278            ..
279        } = &plan;
280
281        // Collect optimizer parameters.
282        let compute_instance = self
283            .instance_snapshot(cluster_id)
284            .expect("compute instance does not exist");
285        let (_, view_id) = self.allocate_transient_id();
286        let (_, sink_id) = self.allocate_transient_id();
287        let debug_name = format!("subscribe-{}", sink_id);
288        let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
289            .override_from(&self.catalog.get_cluster(cluster_id).config.features())
290            .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
291            .override_from(&explain_ctx);
292
293        // Build an optimizer for this SUBSCRIBE.
294        let mut optimizer = optimize::subscribe::Optimizer::new(
295            self.owned_catalog(),
296            compute_instance,
297            view_id,
298            sink_id,
299            *with_snapshot,
300            *up_to,
301            debug_name,
302            optimizer_config,
303            self.optimizer_metrics(),
304        );
305        let catalog = self.owned_catalog();
306
307        let span = Span::current();
308        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
309            || "optimize subscribe (mir)",
310            move || {
311                span.in_scope(|| {
312                    let _dispatch_guard = explain_ctx.dispatch_guard();
313
314                    // MIR ⇒ MIR optimization (global)
315                    let global_mir_plan = optimizer.catch_unwind_optimize(plan.clone())?;
316                    // Add introduced indexes as validity dependencies.
317                    validity.extend_dependencies(
318                        &catalog,
319                        global_mir_plan
320                            .id_bundle(optimizer.cluster_id())
321                            .iter()
322                            .map(|id| catalog.resolve_item_id(&id)),
323                    );
324
325                    let stage = SubscribeStage::LinearizeTimestamp(SubscribeLinearizeTimestamp {
326                        validity,
327                        plan,
328                        timeline,
329                        optimizer,
330                        global_mir_plan,
331                        dependency_ids,
332                        replica_id,
333                        explain_ctx,
334                    });
335                    Ok(Box::new(stage))
336                })
337            },
338        )))
339    }
340
341    /// Possibly linearize a timestamp from a `TimestampOracle`, off the
342    /// coordinator loop.
343    #[instrument]
344    async fn subscribe_linearize_timestamp(
345        &self,
346        session: &Session,
347        SubscribeLinearizeTimestamp {
348            validity,
349            plan,
350            timeline,
351            optimizer,
352            global_mir_plan,
353            dependency_ids,
354            replica_id,
355            explain_ctx,
356        }: SubscribeLinearizeTimestamp,
357    ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
358        let oracle = self.linearized_read_ts_oracle(session, &timeline, &plan.when);
359
360        let build_stage = move |oracle_read_ts: Option<Timestamp>| {
361            SubscribeStage::TimestampOptimizeLir(SubscribeTimestampOptimizeLir {
362                validity,
363                plan,
364                timeline,
365                optimizer,
366                global_mir_plan,
367                dependency_ids,
368                replica_id,
369                oracle_read_ts,
370                explain_ctx,
371            })
372        };
373
374        Ok(spawn_linearized_read_ts(
375            oracle,
376            "subscribe linearize timestamp",
377            build_stage,
378        ))
379    }
380
381    #[instrument]
382    async fn subscribe_timestamp_optimize_lir(
383        &mut self,
384        ctx: &ExecuteContext,
385        SubscribeTimestampOptimizeLir {
386            validity,
387            plan,
388            timeline,
389            mut optimizer,
390            global_mir_plan,
391            dependency_ids,
392            replica_id,
393            oracle_read_ts,
394            explain_ctx,
395        }: SubscribeTimestampOptimizeLir,
396    ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
397        let plan::SubscribePlan { when, .. } = &plan;
398
399        // Timestamp selection. The linearized read timestamp was already
400        // obtained off the coordinator loop in the preceding stage.
401        let bundle = &global_mir_plan.id_bundle(optimizer.cluster_id());
402        let (determination, read_holds) = self.determine_timestamp(
403            ctx.session(),
404            bundle,
405            when,
406            optimizer.cluster_id(),
407            &timeline,
408            oracle_read_ts,
409            None,
410        )?;
411
412        let as_of = determination.timestamp_context.timestamp_or_default();
413
414        if let Some(id) = ctx.extra().contents() {
415            self.set_statement_execution_timestamp(id, as_of);
416        }
417        if let Some(up_to) = optimizer.up_to() {
418            if as_of == up_to {
419                ctx.session()
420                    .add_notice(AdapterNotice::EqualSubscribeBounds { bound: up_to });
421            } else if as_of > up_to {
422                return Err(AdapterError::AbsurdSubscribeBounds { as_of, up_to });
423            }
424        }
425
426        self.store_transaction_read_holds(ctx.session().conn_id().clone(), read_holds);
427
428        let global_mir_plan = global_mir_plan.resolve(Antichain::from_elem(as_of));
429
430        // Optimize LIR
431        let span = Span::current();
432        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
433            || "optimize subscribe (lir)",
434            move || {
435                span.in_scope(|| {
436                    let _dispatch_guard = explain_ctx.dispatch_guard();
437
438                    let cluster_id = optimizer.cluster_id();
439
440                    let mut pipeline = || -> Result<_, AdapterError> {
441                        // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global)
442                        let global_lir_plan =
443                            optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
444                        Ok(global_lir_plan)
445                    };
446
447                    let stage = match pipeline() {
448                        Ok(global_lir_plan) => {
449                            if let ExplainContext::Plan(explain_ctx) = explain_ctx {
450                                let (_, df_meta) = global_lir_plan.unapply();
451                                SubscribeStage::Explain(SubscribeExplain {
452                                    validity,
453                                    optimizer,
454                                    df_meta,
455                                    cluster_id,
456                                    explain_ctx,
457                                })
458                            } else {
459                                SubscribeStage::Finish(SubscribeFinish {
460                                    validity,
461                                    cluster_id,
462                                    plan,
463                                    global_lir_plan,
464                                    dependency_ids,
465                                    replica_id,
466                                })
467                            }
468                        }
469                        Err(err) => {
470                            let ExplainContext::Plan(explain_ctx) = explain_ctx else {
471                                return Err(err);
472                            };
473
474                            if explain_ctx.broken {
475                                tracing::error!("error while handling EXPLAIN statement: {}", err);
476                                SubscribeStage::Explain(SubscribeExplain {
477                                    validity,
478                                    optimizer,
479                                    df_meta: Default::default(),
480                                    cluster_id,
481                                    explain_ctx,
482                                })
483                            } else {
484                                return Err(err);
485                            }
486                        }
487                    };
488
489                    Ok(Box::new(stage))
490                })
491            },
492        )))
493    }
494
495    #[instrument]
496    async fn subscribe_finish(
497        &mut self,
498        ctx: &mut ExecuteContext,
499        SubscribeFinish {
500            validity: _,
501            cluster_id,
502            plan,
503            global_lir_plan,
504            dependency_ids,
505            replica_id,
506        }: SubscribeFinish,
507    ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
508        let (df_desc, df_meta) = global_lir_plan.unapply();
509        emit_optimizer_notices(&*self.catalog, ctx.session(), &df_meta.optimizer_notices);
510        let conn_id = ctx.session.conn_id().clone();
511        let session_uuid = ctx.session().uuid();
512        let txn_read_holds = self
513            .txn_read_holds
514            .remove(&conn_id)
515            .expect("must have previously installed read holds");
516        let (resp, write_notify) = self
517            .implement_subscribe(
518                ctx.extra_mut(),
519                df_desc,
520                dependency_ids,
521                cluster_id,
522                replica_id,
523                conn_id,
524                session_uuid,
525                txn_read_holds,
526                plan,
527            )
528            .await?;
529        // Wait for the `mz_subscriptions` bookkeeping write off the coordinator
530        // loop before returning the `SUBSCRIBE` response to the subscribing
531        // session.
532        let span = Span::current();
533        Ok(StageResult::HandleRetire(mz_ore::task::spawn(
534            || "subscribe_finish::await_bookkeeping",
535            async move {
536                write_notify.await;
537                Ok(resp)
538            }
539            .instrument(span),
540        )))
541    }
542
543    #[instrument]
544    pub(crate) async fn implement_subscribe(
545        &mut self,
546        ctx_extra: &mut ExecuteContextGuard,
547        df_desc: LirDataflowDescription,
548        dependency_ids: BTreeSet<GlobalId>,
549        cluster_id: ComputeInstanceId,
550        replica_id: Option<ReplicaId>,
551        conn_id: ConnectionId,
552        session_uuid: Uuid,
553        read_holds: ReadHolds,
554        plan: plan::SubscribePlan,
555    ) -> Result<(ExecuteResponse, BuiltinTableAppendNotify), AdapterError> {
556        let sink_id = df_desc.sink_id();
557
558        let (tx, rx) = mpsc::unbounded_channel::<PeekResponseUnary>();
559        let backlog_accounting = Arc::new(Mutex::new(SubscribeBacklogAccounting::default()));
560        let max_buffered_bytes =
561            SUBSCRIBE_MAX_BUFFERED_BYTES.get(self.catalog().system_config().dyncfgs());
562        let active_subscribe = ActiveSubscribe {
563            owner: ActiveSubscribeOwner::Session {
564                conn_id: conn_id.clone(),
565                session_uuid,
566            },
567            channel: tx,
568            backlog_accounting: Arc::clone(&backlog_accounting),
569            max_buffered_bytes,
570            emit_progress: plan.emit_progress,
571            as_of: df_desc
572                .as_of
573                .as_ref()
574                .and_then(|t| t.as_option())
575                .copied()
576                .expect("set to Some in an earlier stage"),
577            arity: df_desc
578                .sink_exports
579                .values()
580                .into_element()
581                .from_desc
582                .arity(),
583            cluster_id,
584            depends_on: dependency_ids,
585            start_time: self.now(),
586            output: plan.output,
587            internal: false,
588        };
589        active_subscribe.initialize();
590
591        // A subscribe whose `up_to` is not beyond its `as_of` covers an empty range and
592        // produces no data beyond the progress row `initialize` already emitted. It must
593        // not install a sink: the sink only finishes once its input frontier reaches
594        // `up_to`, which a live input never does at `MAX`, so `AS OF MAX UP TO MAX` would
595        // never complete. Retiring `active_subscribe` drops its channel sender, which
596        // completes the subscribe for the client.
597        let is_empty = df_desc
598            .as_of
599            .as_ref()
600            .is_some_and(|as_of| PartialOrder::less_equal(&df_desc.until, as_of));
601        let write_notify: BuiltinTableAppendNotify = if is_empty {
602            active_subscribe.retire(ActiveComputeSinkRetireReason::Finished);
603            // No `mz_subscriptions` bookkeeping was deferred.
604            Box::pin(std::future::ready(()))
605        } else {
606            // Register bookkeeping for the new SUBSCRIBE and ship its dataflow. The
607            // `mz_subscriptions` write is deferred to a group commit (see
608            // `add_active_compute_sink`) rather than committed inline, so it does not block
609            // the coordinator loop on a timestamp-oracle round trip. We hand the notify back
610            // so the caller can wait before returning the `SUBSCRIBE` response to the
611            // subscribing session.
612            let write_notify = self
613                .add_active_compute_sink(sink_id, ActiveComputeSink::Subscribe(active_subscribe));
614
615            // Ship the dataflow, handling errors gracefully. With the frontend subscribe
616            // sequencing, a dependency can be dropped between sequencing (on the session
617            // task) and here. The read holds acquired during sequencing don't prevent that:
618            // they hold back compaction, not drops.
619            if let Err(e) = self
620                .try_ship_dataflow(df_desc, cluster_id, replica_id)
621                .await
622            {
623                // Clean up the active compute sink that was added above, since the dataflow
624                // was never created. If we don't do this, the sink_id remains in
625                // `drop_sinks` but no collection exists in the compute controller, causing
626                // a panic when the connection terminates. This also retracts the deferred
627                // `mz_subscriptions` write, so `write_notify` can be dropped.
628                self.remove_active_compute_sink(sink_id).await;
629                return Err(
630                    AdapterError::concurrent_dependency_drop_from_dataflow_creation_error(e),
631                );
632            }
633            write_notify
634        };
635
636        // Explicitly drop read holds, just to make it obvious what's happening.
637        drop(read_holds);
638
639        // Wrap the receiver so draining a message releases its footprint from the
640        // shared accounting. FIFO delivery keeps the queue aligned with the
641        // channel, so popping the oldest footprint matches the message just
642        // drained. This keeps the accounting equal to the currently buffered
643        // depth, which the coordinator watches to bound this subscribe.
644        let rx = UnboundedReceiverStream::new(rx).map(move |response| {
645            backlog_accounting
646                .lock()
647                .expect("subscribe backlog accounting poisoned")
648                .pop();
649            response
650        });
651        let resp = ExecuteResponse::Subscribing {
652            rx: Box::new(rx),
653            ctx_extra: std::mem::take(ctx_extra),
654            instance_id: cluster_id,
655        };
656        let resp = match plan.copy_to {
657            None => resp,
658            Some(format) => ExecuteResponse::CopyTo {
659                format,
660                resp: Box::new(resp),
661            },
662        };
663        Ok((resp, write_notify))
664    }
665
666    #[instrument]
667    async fn subscribe_explain(
668        &self,
669        session: &Session,
670        SubscribeExplain {
671            optimizer,
672            df_meta,
673            cluster_id,
674            explain_ctx:
675                ExplainPlanContext {
676                    config,
677                    format,
678                    stage,
679                    optimizer_trace,
680                    desc,
681                    ..
682                },
683            ..
684        }: SubscribeExplain,
685    ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
686        let session_catalog = self.catalog().for_session(session);
687
688        let expr_humanizer = {
689            let transient_items = btreemap! {
690                optimizer.sink_id() => TransientItem::new(
691                    Some(vec![GlobalId::Explain.to_string()]),
692                    desc.map(|d| d.iter_names().map(|c| c.to_string()).collect()),
693                )
694            };
695            ExprHumanizerExt::new(transient_items, &session_catalog)
696        };
697
698        let target_cluster = self.catalog().get_cluster(cluster_id);
699
700        let features = OptimizerFeatures::from(self.catalog().system_config())
701            .override_from(&target_cluster.config.features())
702            .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
703            .override_from(&config.features);
704
705        let rows = optimizer_trace
706            .into_rows(
707                format,
708                &config,
709                &features,
710                &expr_humanizer,
711                None,
712                Some(target_cluster),
713                df_meta,
714                stage,
715                plan::ExplaineeStatementKind::Subscribe,
716                None,
717            )
718            .await?;
719
720        Ok(StageResult::Response(Self::send_immediate_rows(rows)))
721    }
722}