1use maplit::btreemap;
11use mz_adapter_types::connection::ConnectionId;
12use mz_cluster_client::ReplicaId;
13use mz_compute_types::ComputeInstanceId;
14use mz_compute_types::dataflows::DataflowDescription;
15use mz_compute_types::plan::LirRelationExpr;
16use mz_ore::collections::CollectionExt;
17use mz_ore::instrument;
18use mz_repr::GlobalId;
19use mz_repr::Timestamp;
20use mz_repr::explain::{ExprHumanizerExt, TransientItem};
21use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
22use mz_sql::plan::{self, QueryWhen, SubscribeFrom};
23use mz_sql::session::metadata::SessionMetadata;
24use std::collections::BTreeSet;
25use timely::progress::Antichain;
26use tokio::sync::mpsc;
27use tracing::{Instrument, Span};
28use uuid::Uuid;
29
30use crate::active_compute_sink::{ActiveComputeSink, ActiveSubscribe};
31use crate::command::ExecuteResponse;
32use crate::coord::appends::BuiltinTableAppendNotify;
33use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
34use crate::coord::sequencer::{check_log_reads, emit_optimizer_notices};
35use crate::coord::{
36 Coordinator, ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged,
37 SubscribeExplain, SubscribeFinish, SubscribeLinearizeTimestamp, SubscribeOptimizeMir,
38 SubscribeStage, SubscribeTimestampOptimizeLir, TargetCluster,
39};
40use crate::error::AdapterError;
41use crate::explain::optimizer_trace::OptimizerTrace;
42use crate::optimize::Optimize;
43use crate::session::{Session, TransactionOps};
44use crate::{
45 AdapterNotice, ExecuteContext, ExecuteContextGuard, ReadHolds, TimelineContext, optimize,
46};
47
48impl Staged for SubscribeStage {
49 type Ctx = ExecuteContext;
50
51 fn validity(&mut self) -> &mut PlanValidity {
52 match self {
53 SubscribeStage::OptimizeMir(stage) => &mut stage.validity,
54 SubscribeStage::LinearizeTimestamp(stage) => &mut stage.validity,
55 SubscribeStage::TimestampOptimizeLir(stage) => &mut stage.validity,
56 SubscribeStage::Finish(stage) => &mut stage.validity,
57 SubscribeStage::Explain(stage) => &mut stage.validity,
58 }
59 }
60
61 async fn stage(
62 self,
63 coord: &mut Coordinator,
64 ctx: &mut ExecuteContext,
65 ) -> Result<StageResult<Box<Self>>, AdapterError> {
66 match self {
67 SubscribeStage::OptimizeMir(stage) => coord.subscribe_optimize_mir(stage),
68 SubscribeStage::LinearizeTimestamp(stage) => {
69 coord
70 .subscribe_linearize_timestamp(ctx.session(), stage)
71 .await
72 }
73 SubscribeStage::TimestampOptimizeLir(stage) => {
74 coord.subscribe_timestamp_optimize_lir(ctx, stage).await
75 }
76 SubscribeStage::Finish(stage) => coord.subscribe_finish(ctx, stage).await,
77 SubscribeStage::Explain(stage) => coord.subscribe_explain(ctx.session(), stage).await,
78 }
79 }
80
81 fn message(self, ctx: ExecuteContext, span: Span) -> Message {
82 Message::SubscribeStageReady {
83 ctx,
84 span,
85 stage: self,
86 }
87 }
88
89 fn cancel_enabled(&self) -> bool {
90 !matches!(self, SubscribeStage::Finish(_))
94 }
95}
96
97impl Coordinator {
98 #[instrument]
99 pub(crate) async fn sequence_subscribe(
100 &mut self,
101 mut ctx: ExecuteContext,
102 plan: plan::SubscribePlan,
103 target_cluster: TargetCluster,
104 ) {
105 let stage = return_if_err!(
106 self.subscribe_validate(
107 ctx.session_mut(),
108 plan,
109 target_cluster,
110 ExplainContext::None
111 ),
112 ctx
113 );
114 self.sequence_staged(ctx, Span::current(), stage).await;
115 }
116
117 #[instrument]
118 pub(crate) async fn explain_subscribe(
119 &mut self,
120 mut ctx: ExecuteContext,
121 plan::ExplainPlanPlan {
122 stage,
123 format,
124 config,
125 explainee,
126 }: plan::ExplainPlanPlan,
127 target_cluster: TargetCluster,
128 ) {
129 let plan::Explainee::Statement(stmt) = explainee else {
130 unreachable!()
133 };
134 let plan::ExplaineeStatement::Subscribe { broken, plan } = stmt else {
135 unreachable!()
138 };
139
140 let desc = match &plan.from {
141 SubscribeFrom::Id(_) => None,
142 SubscribeFrom::Query { desc, .. } => Some(desc.clone()),
143 };
144
145 let optimizer_trace = OptimizerTrace::new(stage.paths());
148
149 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
150 broken,
151 config,
152 format,
153 stage,
154 replan: None,
155 desc,
156 optimizer_trace,
157 });
158 let stage = return_if_err!(
159 self.subscribe_validate(ctx.session_mut(), plan, target_cluster, explain_ctx),
160 ctx
161 );
162 self.sequence_staged(ctx, Span::current(), stage).await;
163 }
164
165 #[instrument]
166 fn subscribe_validate(
167 &self,
168 session: &mut Session,
169 plan: plan::SubscribePlan,
170 target_cluster: TargetCluster,
171 explain_ctx: ExplainContext,
172 ) -> Result<SubscribeStage, AdapterError> {
173 let plan::SubscribePlan { from, when, .. } = &plan;
174
175 let cluster = self
176 .catalog()
177 .resolve_target_cluster(target_cluster, session)?;
178 let cluster_id = cluster.id;
179
180 if explain_ctx.needs_cluster() && cluster.replicas().next().is_none() {
182 return Err(AdapterError::NoClusterReplicasAvailable {
183 name: cluster.name.clone(),
184 is_managed: cluster.is_managed(),
185 });
186 }
187
188 let mut replica_id = session
189 .vars()
190 .cluster_replica()
191 .map(|name| {
192 cluster
193 .replica_id(name)
194 .ok_or(AdapterError::UnknownClusterReplica {
195 cluster_name: cluster.name.clone(),
196 replica_name: name.to_string(),
197 })
198 })
199 .transpose()?;
200
201 if explain_ctx.needs_cluster() && when == &QueryWhen::Immediately {
204 session.add_transaction_ops(TransactionOps::Subscribe)?;
207 }
208
209 let depends_on = from.depends_on();
210
211 let notices = check_log_reads(
213 self.catalog(),
214 cluster,
215 &depends_on,
216 &mut replica_id,
217 session.vars(),
218 )?;
219 session.add_notices(notices);
220
221 let mut timeline = self
223 .catalog()
224 .validate_timeline_context(depends_on.iter().copied())?;
225 if matches!(timeline, TimelineContext::TimestampIndependent) && from.contains_temporal() {
226 timeline = TimelineContext::TimestampDependent;
229 }
230
231 let dependencies = depends_on
232 .iter()
233 .map(|id| self.catalog().resolve_item_id(id))
234 .collect();
235 let validity = PlanValidity::new(
236 self.catalog(),
237 dependencies,
238 Some(cluster_id),
239 replica_id,
240 session.role_metadata().clone(),
241 );
242
243 Ok(SubscribeStage::OptimizeMir(SubscribeOptimizeMir {
244 validity,
245 plan,
246 timeline,
247 dependency_ids: depends_on,
248 cluster_id,
249 replica_id,
250 explain_ctx,
251 }))
252 }
253
254 #[instrument]
255 fn subscribe_optimize_mir(
256 &self,
257 SubscribeOptimizeMir {
258 mut validity,
259 plan,
260 timeline,
261 dependency_ids,
262 cluster_id,
263 replica_id,
264 explain_ctx,
265 }: SubscribeOptimizeMir,
266 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
267 let plan::SubscribePlan {
268 with_snapshot,
269 up_to,
270 ..
271 } = &plan;
272
273 let compute_instance = self
275 .instance_snapshot(cluster_id)
276 .expect("compute instance does not exist");
277 let (_, view_id) = self.allocate_transient_id();
278 let (_, sink_id) = self.allocate_transient_id();
279 let debug_name = format!("subscribe-{}", sink_id);
280 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
281 .override_from(&self.catalog.get_cluster(cluster_id).config.features())
282 .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
283 .override_from(&explain_ctx);
284
285 let mut optimizer = optimize::subscribe::Optimizer::new(
287 self.owned_catalog(),
288 compute_instance,
289 view_id,
290 sink_id,
291 *with_snapshot,
292 *up_to,
293 debug_name,
294 optimizer_config,
295 self.optimizer_metrics(),
296 );
297 let catalog = self.owned_catalog();
298
299 let span = Span::current();
300 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
301 || "optimize subscribe (mir)",
302 move || {
303 span.in_scope(|| {
304 let _dispatch_guard = explain_ctx.dispatch_guard();
305
306 let global_mir_plan = optimizer.catch_unwind_optimize(plan.clone())?;
308 validity.extend_dependencies(
310 &catalog,
311 global_mir_plan
312 .id_bundle(optimizer.cluster_id())
313 .iter()
314 .map(|id| catalog.resolve_item_id(&id)),
315 );
316
317 let stage = SubscribeStage::LinearizeTimestamp(SubscribeLinearizeTimestamp {
318 validity,
319 plan,
320 timeline,
321 optimizer,
322 global_mir_plan,
323 dependency_ids,
324 replica_id,
325 explain_ctx,
326 });
327 Ok(Box::new(stage))
328 })
329 },
330 )))
331 }
332
333 #[instrument]
336 async fn subscribe_linearize_timestamp(
337 &self,
338 session: &Session,
339 SubscribeLinearizeTimestamp {
340 validity,
341 plan,
342 timeline,
343 optimizer,
344 global_mir_plan,
345 dependency_ids,
346 replica_id,
347 explain_ctx,
348 }: SubscribeLinearizeTimestamp,
349 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
350 let oracle = self.linearized_read_ts_oracle(session, &timeline, &plan.when);
351
352 let build_stage = move |oracle_read_ts: Option<Timestamp>| {
353 SubscribeStage::TimestampOptimizeLir(SubscribeTimestampOptimizeLir {
354 validity,
355 plan,
356 timeline,
357 optimizer,
358 global_mir_plan,
359 dependency_ids,
360 replica_id,
361 oracle_read_ts,
362 explain_ctx,
363 })
364 };
365
366 Ok(spawn_linearized_read_ts(
367 oracle,
368 "subscribe linearize timestamp",
369 build_stage,
370 ))
371 }
372
373 #[instrument]
374 async fn subscribe_timestamp_optimize_lir(
375 &mut self,
376 ctx: &ExecuteContext,
377 SubscribeTimestampOptimizeLir {
378 validity,
379 plan,
380 timeline,
381 mut optimizer,
382 global_mir_plan,
383 dependency_ids,
384 replica_id,
385 oracle_read_ts,
386 explain_ctx,
387 }: SubscribeTimestampOptimizeLir,
388 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
389 let plan::SubscribePlan { when, .. } = &plan;
390
391 let bundle = &global_mir_plan.id_bundle(optimizer.cluster_id());
394 let (determination, read_holds) = self.determine_timestamp(
395 ctx.session(),
396 bundle,
397 when,
398 optimizer.cluster_id(),
399 &timeline,
400 oracle_read_ts,
401 None,
402 )?;
403
404 let as_of = determination.timestamp_context.timestamp_or_default();
405
406 if let Some(id) = ctx.extra().contents() {
407 self.set_statement_execution_timestamp(id, as_of);
408 }
409 if let Some(up_to) = optimizer.up_to() {
410 if as_of == up_to {
411 ctx.session()
412 .add_notice(AdapterNotice::EqualSubscribeBounds { bound: up_to });
413 } else if as_of > up_to {
414 return Err(AdapterError::AbsurdSubscribeBounds { as_of, up_to });
415 }
416 }
417
418 self.store_transaction_read_holds(ctx.session().conn_id().clone(), read_holds);
419
420 let global_mir_plan = global_mir_plan.resolve(Antichain::from_elem(as_of));
421
422 let span = Span::current();
424 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
425 || "optimize subscribe (lir)",
426 move || {
427 span.in_scope(|| {
428 let _dispatch_guard = explain_ctx.dispatch_guard();
429
430 let cluster_id = optimizer.cluster_id();
431
432 let mut pipeline = || -> Result<_, AdapterError> {
433 let global_lir_plan =
435 optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
436 Ok(global_lir_plan)
437 };
438
439 let stage = match pipeline() {
440 Ok(global_lir_plan) => {
441 if let ExplainContext::Plan(explain_ctx) = explain_ctx {
442 let (_, df_meta) = global_lir_plan.unapply();
443 SubscribeStage::Explain(SubscribeExplain {
444 validity,
445 optimizer,
446 df_meta,
447 cluster_id,
448 explain_ctx,
449 })
450 } else {
451 SubscribeStage::Finish(SubscribeFinish {
452 validity,
453 cluster_id,
454 plan,
455 global_lir_plan,
456 dependency_ids,
457 replica_id,
458 })
459 }
460 }
461 Err(err) => {
462 let ExplainContext::Plan(explain_ctx) = explain_ctx else {
463 return Err(err);
464 };
465
466 if explain_ctx.broken {
467 tracing::error!("error while handling EXPLAIN statement: {}", err);
468 SubscribeStage::Explain(SubscribeExplain {
469 validity,
470 optimizer,
471 df_meta: Default::default(),
472 cluster_id,
473 explain_ctx,
474 })
475 } else {
476 return Err(err);
477 }
478 }
479 };
480
481 Ok(Box::new(stage))
482 })
483 },
484 )))
485 }
486
487 #[instrument]
488 async fn subscribe_finish(
489 &mut self,
490 ctx: &mut ExecuteContext,
491 SubscribeFinish {
492 validity: _,
493 cluster_id,
494 plan,
495 global_lir_plan,
496 dependency_ids,
497 replica_id,
498 }: SubscribeFinish,
499 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
500 let (df_desc, df_meta) = global_lir_plan.unapply();
501 emit_optimizer_notices(&*self.catalog, ctx.session(), &df_meta.optimizer_notices);
502 let conn_id = ctx.session.conn_id().clone();
503 let session_uuid = ctx.session().uuid();
504 let txn_read_holds = self
505 .txn_read_holds
506 .remove(&conn_id)
507 .expect("must have previously installed read holds");
508 let (resp, write_notify) = self
509 .implement_subscribe(
510 ctx.extra_mut(),
511 df_desc,
512 dependency_ids,
513 cluster_id,
514 replica_id,
515 conn_id,
516 session_uuid,
517 txn_read_holds,
518 plan,
519 )
520 .await?;
521 let span = Span::current();
525 Ok(StageResult::HandleRetire(mz_ore::task::spawn(
526 || "subscribe_finish::await_bookkeeping",
527 async move {
528 write_notify.await;
529 Ok(resp)
530 }
531 .instrument(span),
532 )))
533 }
534
535 #[instrument]
536 pub(crate) async fn implement_subscribe(
537 &mut self,
538 ctx_extra: &mut ExecuteContextGuard,
539 df_desc: DataflowDescription<LirRelationExpr>,
540 dependency_ids: BTreeSet<GlobalId>,
541 cluster_id: ComputeInstanceId,
542 replica_id: Option<ReplicaId>,
543 conn_id: ConnectionId,
544 session_uuid: Uuid,
545 read_holds: ReadHolds,
546 plan: plan::SubscribePlan,
547 ) -> Result<(ExecuteResponse, BuiltinTableAppendNotify), AdapterError> {
548 let sink_id = df_desc.sink_id();
549
550 let (tx, rx) = mpsc::unbounded_channel();
551 let active_subscribe = ActiveSubscribe {
552 conn_id: conn_id.clone(),
553 session_uuid,
554 channel: tx,
555 emit_progress: plan.emit_progress,
556 as_of: df_desc
557 .as_of
558 .as_ref()
559 .and_then(|t| t.as_option())
560 .copied()
561 .expect("set to Some in an earlier stage"),
562 arity: df_desc
563 .sink_exports
564 .values()
565 .into_element()
566 .from_desc
567 .arity(),
568 cluster_id,
569 depends_on: dependency_ids,
570 start_time: self.now(),
571 output: plan.output,
572 internal: false,
573 };
574 active_subscribe.initialize();
575
576 let write_notify =
583 self.add_active_compute_sink(sink_id, ActiveComputeSink::Subscribe(active_subscribe));
584
585 if let Err(e) = self
590 .try_ship_dataflow(df_desc, cluster_id, replica_id)
591 .await
592 {
593 self.remove_active_compute_sink(sink_id).await;
599 return Err(AdapterError::concurrent_dependency_drop_from_dataflow_creation_error(e));
600 }
601
602 drop(read_holds);
604
605 let resp = ExecuteResponse::Subscribing {
606 rx,
607 ctx_extra: std::mem::take(ctx_extra),
608 instance_id: cluster_id,
609 };
610 let resp = match plan.copy_to {
611 None => resp,
612 Some(format) => ExecuteResponse::CopyTo {
613 format,
614 resp: Box::new(resp),
615 },
616 };
617 Ok((resp, write_notify))
618 }
619
620 #[instrument]
621 async fn subscribe_explain(
622 &self,
623 session: &Session,
624 SubscribeExplain {
625 optimizer,
626 df_meta,
627 cluster_id,
628 explain_ctx:
629 ExplainPlanContext {
630 config,
631 format,
632 stage,
633 optimizer_trace,
634 desc,
635 ..
636 },
637 ..
638 }: SubscribeExplain,
639 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
640 let session_catalog = self.catalog().for_session(session);
641
642 let expr_humanizer = {
643 let transient_items = btreemap! {
644 optimizer.sink_id() => TransientItem::new(
645 Some(vec![GlobalId::Explain.to_string()]),
646 desc.map(|d| d.iter_names().map(|c| c.to_string()).collect()),
647 )
648 };
649 ExprHumanizerExt::new(transient_items, &session_catalog)
650 };
651
652 let target_cluster = self.catalog().get_cluster(cluster_id);
653
654 let features = OptimizerFeatures::from(self.catalog().system_config())
655 .override_from(&target_cluster.config.features())
656 .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
657 .override_from(&config.features);
658
659 let rows = optimizer_trace
660 .into_rows(
661 format,
662 &config,
663 &features,
664 &expr_humanizer,
665 None,
666 Some(target_cluster),
667 df_meta,
668 stage,
669 plan::ExplaineeStatementKind::Subscribe,
670 None,
671 )
672 .await?;
673
674 Ok(StageResult::Response(Self::send_immediate_rows(rows)))
675 }
676}