1use std::collections::BTreeMap;
11use std::sync::{Arc, Weak};
12
13use differential_dataflow::consolidation::consolidate;
14use mz_compute_client::controller::error::{CollectionMissing, InstanceMissing};
15use mz_compute_client::controller::instance_client::InstanceClient;
16use mz_compute_client::controller::instance_client::{AcquireReadHoldsError, InstanceShutDown};
17use mz_compute_client::protocol::command::PeekTarget;
18use mz_compute_types::ComputeInstanceId;
19use mz_expr::row::RowCollection;
20use mz_ore::cast::CastFrom;
21use mz_ore::soft_panic_or_log;
22use mz_persist_client::PersistClient;
23use mz_repr::GlobalId;
24use mz_repr::Timestamp;
25use mz_repr::global_id::TransientIdGen;
26use mz_repr::{RelationDesc, Row};
27use mz_sql::ast::{Raw, Statement};
28use mz_sql::optimizer_metrics::OptimizerMetrics;
29use mz_sql::plan::Params;
30use mz_sql::session::metadata::SessionMetadata;
31use mz_sql_parser::ast::{CopyRelation, CopyStatement, SubscribeStatement};
32use mz_storage_types::sources::Timeline;
33use mz_timestamp_oracle::TimestampOracle;
34use prometheus::Histogram;
35use qcell::QCell;
36use thiserror::Error;
37use timely::progress::Antichain;
38use tokio::sync::{Semaphore, oneshot};
39use uuid::Uuid;
40
41use crate::catalog::Catalog;
42use crate::command::{CatalogSnapshot, Command, ExecuteResponse};
43use crate::coord::appends::GroupCommitNotifier;
44use crate::coord::peek::FastPathPlan;
45use crate::coord::{Coordinator, ExecuteContextExtra, ExecuteContextGuard, Message};
46use crate::metrics::Metrics;
47use crate::session::{LifecycleTimestamps, Session};
48use crate::statement_logging::{
49 FrontendStatementLoggingEvent, PreparedStatementEvent, PreparedStatementLoggingInfo,
50 StatementLoggingFrontend, StatementLoggingId, WatchSetCreation,
51};
52use crate::{AdapterError, Client, CollectionIdBundle, ReadHolds, metrics, statement_logging};
53
54pub type StorageCollectionsHandle =
56 Arc<dyn mz_storage_client::storage_collections::StorageCollections + Send + Sync>;
57
58#[derive(Debug)]
60pub struct PeekClient {
61 coordinator_client: CoordinatorClient,
62 catalog_cache: Weak<Catalog>,
69 compute_instances: BTreeMap<ComputeInstanceId, InstanceClient>,
74 pub storage_collections: StorageCollectionsHandle,
76 pub transient_id_gen: Arc<TransientIdGen>,
78 pub optimizer_metrics: OptimizerMetrics,
79 oracles: BTreeMap<Timeline, Arc<dyn TimestampOracle<Timestamp> + Send + Sync>>,
81 persist_client: PersistClient,
82 pub statement_logging_frontend: StatementLoggingFrontend,
84 pub occ_write_semaphore: Arc<Semaphore>,
86 pub frontend_read_then_write_enabled: bool,
88 pub(crate) group_commit_notifier: GroupCommitNotifier,
91 pub read_only: bool,
93}
94
95#[derive(Debug, Clone)]
97pub(crate) enum CoordinatorClient {
98 Session(Client),
99 Background {
100 tx: tokio::sync::mpsc::UnboundedSender<Message>,
101 metrics: Metrics,
102 },
103}
104
105impl CoordinatorClient {
106 pub(crate) fn send(&self, command: Command) {
115 if self.try_send(command) {
116 return;
117 }
118 match self {
119 CoordinatorClient::Session(_) => panic!("coordinator unexpectedly gone"),
120 CoordinatorClient::Background { .. } => {
121 tracing::debug!("dropping background command, coordinator is gone")
122 }
123 }
124 }
125
126 pub(crate) fn try_send(&self, command: Command) -> bool {
127 match self {
128 CoordinatorClient::Session(client) => client.try_send(command),
129 CoordinatorClient::Background { tx, .. } => tx
130 .send(Message::Command(
131 mz_ore::tracing::OpenTelemetryContext::obtain(),
132 command,
133 ))
134 .is_ok(),
135 }
136 }
137
138 pub(crate) fn metrics(&self) -> &Metrics {
139 match self {
140 CoordinatorClient::Session(client) => client.metrics(),
141 CoordinatorClient::Background { metrics, .. } => metrics,
142 }
143 }
144}
145
146impl PeekClient {
147 #[allow(clippy::too_many_arguments)]
152 pub(crate) fn new(
153 coordinator_client: CoordinatorClient,
154 catalog: &Arc<Catalog>,
155 storage_collections: StorageCollectionsHandle,
156 transient_id_gen: Arc<TransientIdGen>,
157 optimizer_metrics: OptimizerMetrics,
158 persist_client: PersistClient,
159 statement_logging_frontend: StatementLoggingFrontend,
160 occ_write_semaphore: Arc<Semaphore>,
161 frontend_read_then_write_enabled: bool,
162 group_commit_notifier: GroupCommitNotifier,
163 read_only: bool,
164 ) -> Self {
165 Self {
166 coordinator_client,
167 catalog_cache: Arc::downgrade(catalog),
168 compute_instances: Default::default(), storage_collections,
170 transient_id_gen,
171 optimizer_metrics,
172 statement_logging_frontend,
173 oracles: Default::default(), persist_client,
175 occ_write_semaphore,
176 frontend_read_then_write_enabled,
177 group_commit_notifier,
178 read_only,
179 }
180 }
181
182 pub async fn ensure_compute_instance_client(
183 &mut self,
184 compute_instance: ComputeInstanceId,
185 ) -> Result<InstanceClient, CollectionLookupError> {
186 if !self.compute_instances.contains_key(&compute_instance) {
187 let client = self
188 .call_coordinator(|tx| Command::GetComputeInstanceClient {
189 instance_id: compute_instance,
190 tx,
191 })
192 .await
193 .map_err(|_| CollectionLookupError::InstanceShutDown)??;
194 self.compute_instances.insert(compute_instance, client);
195 }
196 Ok(self
197 .compute_instances
198 .get(&compute_instance)
199 .expect("ensured above")
200 .clone())
201 }
202
203 pub async fn ensure_oracle(
204 &mut self,
205 timeline: Timeline,
206 ) -> Result<&mut Arc<dyn TimestampOracle<Timestamp> + Send + Sync>, AdapterError> {
207 if !self.oracles.contains_key(&timeline) {
208 let oracle = self
209 .call_coordinator(|tx| Command::GetOracle {
210 timeline: timeline.clone(),
211 tx,
212 })
213 .await??;
214 self.oracles.insert(timeline.clone(), oracle);
215 }
216 Ok(self.oracles.get_mut(&timeline).expect("ensured above"))
217 }
218
219 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
232 let cached = self
238 .catalog_cache
239 .upgrade()
240 .filter(|catalog| catalog.transient_revision_is_current());
241 if let Some(catalog) = cached {
242 self.coordinator_client
243 .metrics()
244 .catalog_snapshot_cache
245 .with_label_values(&[context, "hit"])
246 .inc();
247 return catalog;
248 }
249
250 let start = std::time::Instant::now();
253 let CatalogSnapshot { catalog } = self
257 .call_coordinator(|tx| Command::CatalogSnapshot { tx })
258 .await
259 .expect("coordinator unexpectedly dropped catalog snapshot response");
260 let metrics = self.coordinator_client.metrics();
261 metrics
262 .catalog_snapshot_seconds
263 .with_label_values(&[context])
264 .observe(start.elapsed().as_secs_f64());
265 metrics
266 .catalog_snapshot_cache
267 .with_label_values(&[context, "miss"])
268 .inc();
269 self.catalog_cache = Arc::downgrade(&catalog);
270 catalog
271 }
272
273 pub(crate) async fn call_coordinator<T, F>(&self, f: F) -> Result<T, AdapterError>
275 where
276 F: FnOnce(oneshot::Sender<T>) -> Command,
277 {
278 let (tx, rx) = oneshot::channel();
279 self.coordinator_client.send(f(tx));
280 Ok(rx.await?)
281 }
282
283 pub(crate) fn coordinator_client(&self) -> &CoordinatorClient {
285 &self.coordinator_client
286 }
287
288 pub async fn acquire_read_holds_and_least_valid_write(
301 &mut self,
302 id_bundle: &CollectionIdBundle,
303 ) -> Result<(ReadHolds, Antichain<Timestamp>), CollectionLookupError> {
304 let mut read_holds = ReadHolds::new();
305 let mut upper = Antichain::new();
306
307 if !id_bundle.storage_ids.is_empty() {
308 let desired_storage: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
309 let storage_read_holds = self
310 .storage_collections
311 .acquire_read_holds(desired_storage)?;
312 read_holds.storage_holds = storage_read_holds
313 .into_iter()
314 .map(|hold| (hold.id(), hold))
315 .collect();
316
317 let storage_ids: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
318 for f in self
319 .storage_collections
320 .collections_frontiers(storage_ids)?
321 {
322 upper.extend(f.write_frontier);
323 }
324 }
325
326 for (&instance_id, collection_ids) in &id_bundle.compute_ids {
327 let client = self.ensure_compute_instance_client(instance_id).await?;
328
329 for (id, read_hold, write_frontier) in client
330 .acquire_read_holds_and_collection_write_frontiers(
331 collection_ids.iter().copied().collect(),
332 )
333 .await?
334 {
335 let prev = read_holds
336 .compute_holds
337 .insert((instance_id, id), read_hold);
338 assert!(
339 prev.is_none(),
340 "duplicate compute ID in id_bundle {id_bundle:?}"
341 );
342
343 upper.extend(write_frontier);
344 }
345 }
346
347 Ok((read_holds, upper))
348 }
349
350 pub(crate) async fn implement_fast_path_peek_plan(
365 &mut self,
366 fast_path: FastPathPlan,
367 timestamp: Timestamp,
368 finishing: mz_expr::RowSetFinishing,
369 compute_instance: ComputeInstanceId,
370 target_replica: Option<mz_cluster_client::ReplicaId>,
371 intermediate_result_type: mz_repr::SqlRelationType,
372 max_result_size: u64,
373 max_returned_query_size: Option<u64>,
374 row_set_finishing_seconds: Histogram,
375 input_read_holds: ReadHolds,
376 peek_stash_read_batch_size_bytes: usize,
377 peek_stash_read_memory_budget_bytes: usize,
378 conn_id: mz_adapter_types::connection::ConnectionId,
379 depends_on: std::collections::BTreeSet<mz_repr::GlobalId>,
380 watch_set: Option<WatchSetCreation>,
381 logging: &mut ExecutionLogging,
382 ) -> Result<crate::ExecuteResponse, AdapterError> {
383 if let FastPathPlan::Constant(rows_res, _) = fast_path {
385 if let Some(ref ws) = watch_set {
388 self.log_lifecycle_event(
389 ws.logging_id,
390 statement_logging::StatementLifecycleEvent::StorageDependenciesFinished,
391 );
392 self.log_lifecycle_event(
393 ws.logging_id,
394 statement_logging::StatementLifecycleEvent::ComputeDependenciesFinished,
395 );
396 }
397
398 let mut rows = match rows_res {
399 Ok(rows) => rows,
400 Err(e) => return Err(e.into()),
401 };
402 consolidate(&mut rows);
403
404 let mut results = Vec::new();
405 for (row, count) in rows {
406 let count = match u64::try_from(count.into_inner()) {
407 Ok(u) => usize::cast_from(u),
408 Err(_) => {
409 return Err(AdapterError::Unstructured(anyhow::anyhow!(
410 "Negative multiplicity in constant result: {}",
411 count
412 )));
413 }
414 };
415 match std::num::NonZeroUsize::new(count) {
416 Some(nzu) => {
417 results.push((row, nzu));
418 }
419 None => {
420 }
422 };
423 }
424 let row_collection = RowCollection::new(results, &finishing.order_by);
425 return match finishing.finish(
426 row_collection,
427 max_result_size,
428 max_returned_query_size,
429 &row_set_finishing_seconds,
430 ) {
431 Ok((rows, _bytes)) => Ok(Coordinator::send_immediate_rows(rows)),
432 Err(e) => Err(AdapterError::ResultSize(e)),
434 };
435 }
436
437 let (peek_target, target_read_hold, literal_constraints, mfp, strategy) = match fast_path {
438 FastPathPlan::PeekExisting(_coll_id, idx_id, literal_constraints, mfp) => {
439 let peek_target = PeekTarget::Index { id: idx_id };
440 let target_read_hold = input_read_holds
441 .compute_holds
442 .get(&(compute_instance, idx_id))
443 .expect("missing compute read hold on PeekExisting peek target")
444 .clone();
445 let strategy = statement_logging::StatementExecutionStrategy::FastPath;
446 (
447 peek_target,
448 target_read_hold,
449 literal_constraints,
450 mfp,
451 strategy,
452 )
453 }
454 FastPathPlan::PeekPersist(coll_id, literal_constraint, mfp) => {
455 let literal_constraints = literal_constraint.map(|r| vec![r]);
456 let metadata = self
457 .storage_collections
458 .collection_metadata(coll_id)
459 .map_err(AdapterError::concurrent_dependency_drop_from_collection_missing)?
460 .clone();
461 let peek_target = PeekTarget::Persist {
462 id: coll_id,
463 metadata,
464 };
465 let target_read_hold = input_read_holds
466 .storage_holds
467 .get(&coll_id)
468 .expect("missing storage read hold on PeekPersist peek target")
469 .clone();
470 let strategy = statement_logging::StatementExecutionStrategy::PersistFastPath;
471 (
472 peek_target,
473 target_read_hold,
474 literal_constraints,
475 mfp,
476 strategy,
477 )
478 }
479 FastPathPlan::Constant(..) => {
480 unreachable!()
482 }
483 };
484
485 let (rows_tx, rows_rx) = oneshot::channel();
486 let uuid = Uuid::new_v4();
487
488 let cols = (0..intermediate_result_type.arity()).map(|i| format!("peek_{i}"));
491 let result_desc = RelationDesc::new(intermediate_result_type.clone(), cols);
492
493 let client = self
494 .ensure_compute_instance_client(compute_instance)
495 .await
496 .map_err(|error| {
497 AdapterError::concurrent_dependency_drop_from_collection_lookup_error(
498 error,
499 compute_instance,
500 )
501 })?;
502
503 self.call_coordinator(|tx| Command::RegisterFrontendPeek {
508 uuid,
509 conn_id: conn_id.clone(),
510 cluster_id: compute_instance,
511 depends_on,
512 is_fast_path: true,
513 watch_set,
514 tx,
515 })
516 .await??;
517
518 logging.defuse();
524
525 fail::fail_point!("peek_after_register_before_issue");
530
531 let finishing_for_instance = finishing.clone();
532 let peek_result = client
533 .peek(
534 peek_target,
535 literal_constraints,
536 uuid,
537 timestamp,
538 result_desc,
539 finishing_for_instance,
540 mfp,
541 target_read_hold,
542 target_replica,
543 rows_tx,
544 )
545 .await;
546
547 if let Err(err) = peek_result {
548 let err = AdapterError::concurrent_dependency_drop_from_instance_peek_error(
549 err,
550 compute_instance,
551 );
552 let _ = self
558 .call_coordinator(|tx| Command::UnregisterFrontendPeek {
559 uuid,
560 reason: statement_logging::StatementEndedExecutionReason::Errored {
561 error: err.to_string(),
562 },
563 tx,
564 })
565 .await;
566 return Err(err);
567 }
568
569 let peek_response_stream = Coordinator::create_peek_response_stream(
570 rows_rx,
571 finishing,
572 max_result_size,
573 max_returned_query_size,
574 row_set_finishing_seconds,
575 self.persist_client.clone(),
576 peek_stash_read_batch_size_bytes,
577 peek_stash_read_memory_budget_bytes,
578 );
579
580 Ok(crate::ExecuteResponse::SendingRowsStreaming {
581 rows: Box::pin(peek_response_stream),
582 instance_id: compute_instance,
583 strategy,
584 })
585 }
586
587 fn begin_statement_logging(
592 &self,
593 session: &mut Session,
594 params: &Params,
595 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
596 catalog: &Catalog,
597 lifecycle_timestamps: Option<LifecycleTimestamps>,
598 ) -> StatementLoggingGuard {
599 let result = self.statement_logging_frontend.begin_statement_execution(
600 session,
601 params,
602 logging,
603 catalog.system_config(),
604 lifecycle_timestamps,
605 );
606
607 let id = result.map(
608 |(logging_id, began_execution, mseh_update, prepared_statement)| {
609 self.log_began_execution(began_execution, mseh_update, prepared_statement);
610 logging_id
611 },
612 );
613
614 StatementLoggingGuard {
615 id,
616 coordinator_client: self.coordinator_client.clone(),
617 now: self.statement_logging_frontend.now.clone(),
618 }
619 }
620
621 pub(crate) fn log_began_execution(
623 &self,
624 record: statement_logging::StatementBeganExecutionRecord,
625 mseh_update: Row,
626 prepared_statement: Option<PreparedStatementEvent>,
627 ) {
628 self.coordinator_client
629 .send(Command::FrontendStatementLogging(
630 FrontendStatementLoggingEvent::BeganExecution {
631 record,
632 mseh_update,
633 prepared_statement,
634 },
635 ));
636 }
637
638 pub(crate) fn log_set_cluster(
640 &self,
641 id: StatementLoggingId,
642 cluster_id: mz_controller_types::ClusterId,
643 cluster_name: String,
644 ) {
645 self.coordinator_client
646 .send(Command::FrontendStatementLogging(
647 FrontendStatementLoggingEvent::SetCluster {
648 id,
649 cluster_id,
650 cluster_name,
651 },
652 ));
653 }
654
655 pub(crate) fn log_set_timestamp(&self, id: StatementLoggingId, timestamp: mz_repr::Timestamp) {
657 self.coordinator_client
658 .send(Command::FrontendStatementLogging(
659 FrontendStatementLoggingEvent::SetTimestamp { id, timestamp },
660 ));
661 }
662
663 pub(crate) fn log_set_transient_index_id(
665 &self,
666 id: StatementLoggingId,
667 transient_index_id: mz_repr::GlobalId,
668 ) {
669 self.coordinator_client
670 .send(Command::FrontendStatementLogging(
671 FrontendStatementLoggingEvent::SetTransientIndex {
672 id,
673 transient_index_id,
674 },
675 ));
676 }
677
678 pub(crate) fn log_lifecycle_event(
680 &self,
681 id: StatementLoggingId,
682 event: statement_logging::StatementLifecycleEvent,
683 ) {
684 let when = (self.statement_logging_frontend.now)();
685 self.coordinator_client
686 .send(Command::FrontendStatementLogging(
687 FrontendStatementLoggingEvent::Lifecycle { id, event, when },
688 ));
689 }
690}
691
692#[must_use = "StatementLoggingGuard must be explicitly retired or handed off; \
708 otherwise `Drop` will log the statement as Aborted"]
709struct StatementLoggingGuard {
710 id: Option<StatementLoggingId>,
712 coordinator_client: CoordinatorClient,
713 now: mz_ore::now::NowFn,
714}
715
716impl StatementLoggingGuard {
717 fn adopt(outer: ExecuteContextGuard, peek_client: &PeekClient) -> Self {
720 Self {
721 id: outer.defuse().retire(),
722 coordinator_client: peek_client.coordinator_client.clone(),
723 now: peek_client.statement_logging_frontend.now.clone(),
724 }
725 }
726
727 fn id(&self) -> Option<StatementLoggingId> {
729 self.id
730 }
731
732 fn retire(mut self, reason: statement_logging::StatementEndedExecutionReason) {
735 self.emit(reason);
736 }
737
738 fn release(mut self) -> ExecuteContextExtra {
741 ExecuteContextExtra::new(self.id.take())
742 }
743
744 fn defuse(&mut self) {
748 self.id = None;
749 }
750
751 fn emit(&mut self, reason: statement_logging::StatementEndedExecutionReason) {
752 let Some(id) = self.id.take() else {
753 return;
754 };
755 let ended_at = (self.now)();
756 let record = statement_logging::StatementEndedExecutionRecord {
757 id: id.0,
758 reason,
759 ended_at,
760 };
761 let _ = self
765 .coordinator_client
766 .try_send(Command::FrontendStatementLogging(
767 FrontendStatementLoggingEvent::EndedExecution(record),
768 ));
769 }
770}
771
772impl Drop for StatementLoggingGuard {
773 fn drop(&mut self) {
774 self.emit(statement_logging::StatementEndedExecutionReason::Aborted);
777 }
778}
779
780pub(crate) struct ExecutionLogging {
791 guard: Option<StatementLoggingGuard>,
792 coordinator_must_not_run: bool,
796}
797
798pub(crate) enum TakeOver {
800 StatementToRun,
802 UnrolledExecute,
811}
812
813impl ExecutionLogging {
814 pub(crate) fn adopt(outer: Option<ExecuteContextGuard>, peek_client: &PeekClient) -> Self {
818 Self {
819 guard: outer.map(|outer| StatementLoggingGuard::adopt(outer, peek_client)),
820 coordinator_must_not_run: false,
821 }
822 }
823
824 pub(crate) fn id(&self) -> Option<StatementLoggingId> {
827 self.guard.as_ref().and_then(|guard| guard.id())
828 }
829
830 pub(crate) fn take_over(
839 &mut self,
840 peek_client: &PeekClient,
841 session: &mut Session,
842 stmt: Option<&Statement<Raw>>,
843 params: &Params,
844 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
845 catalog: &Catalog,
846 lifecycle_timestamps: Option<LifecycleTimestamps>,
847 taking_over: TakeOver,
848 ) -> Option<StatementLoggingId> {
849 self.begin_or_inherit(
850 peek_client,
851 session,
852 params,
853 logging,
854 catalog,
855 lifecycle_timestamps,
856 );
857 count_statement(session, stmt);
858 if matches!(taking_over, TakeOver::StatementToRun) {
859 self.coordinator_must_not_run = true;
860 }
861 self.id()
862 }
863
864 fn begin_or_inherit(
867 &mut self,
868 peek_client: &PeekClient,
869 session: &mut Session,
870 params: &Params,
871 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
872 catalog: &Catalog,
873 lifecycle_timestamps: Option<LifecycleTimestamps>,
874 ) {
875 if self.guard.is_none() {
876 self.guard = Some(peek_client.begin_statement_logging(
877 session,
878 params,
879 logging,
880 catalog,
881 lifecycle_timestamps,
882 ));
883 }
884 }
885
886 #[must_use]
891 pub(crate) fn release(&mut self) -> Option<ExecuteContextExtra> {
892 if self.coordinator_must_not_run {
893 soft_panic_or_log!(
894 "statement handed to the coordinator after the session task took it over: \
895 its per-statement metrics are counted twice"
896 );
897 }
898 self.guard.take().map(|guard| guard.release())
899 }
900
901 pub(crate) fn retire(self, result: &Result<ExecuteResponse, AdapterError>) {
903 let Some(guard) = self.guard else {
904 return;
905 };
906 if guard.id().is_none() {
910 return;
911 }
912 guard.retire(end_reason(result));
913 }
914
915 pub(crate) fn defuse(&mut self) {
918 if let Some(guard) = self.guard.as_mut() {
919 guard.defuse();
920 }
921 }
922}
923
924fn end_reason(
931 result: &Result<ExecuteResponse, AdapterError>,
932) -> statement_logging::StatementEndedExecutionReason {
933 if let Ok(response) = result {
934 if terminates_elsewhere(response) {
935 soft_panic_or_log!(
936 "frontend-sequenced statement still owed an end event while returning {:?}",
937 crate::command::ExecuteResponseKind::from(response)
938 );
939 return statement_logging::StatementEndedExecutionReason::Aborted;
940 }
941 }
942 result.into()
943}
944
945fn count_statement(session: &Session, stmt: Option<&Statement<Raw>>) {
951 let Some(stmt) = stmt else {
952 return;
953 };
954 let session_type = metrics::session_type_label_value(session.user());
955 session
956 .metrics()
957 .query_total(&[session_type, metrics::statement_type_label_value(stmt)])
958 .inc();
959 if let Statement::Subscribe(SubscribeStatement { output, .. })
960 | Statement::Copy(CopyStatement {
961 relation: CopyRelation::Subscribe(SubscribeStatement { output, .. }),
962 ..
963 }) = stmt
964 {
965 session
966 .metrics()
967 .subscribe_outputs(&[session_type, metrics::subscribe_output_label_value(output)])
968 .inc();
969 }
970}
971
972fn terminates_elsewhere(response: &ExecuteResponse) -> bool {
977 match response {
978 ExecuteResponse::SendingRowsStreaming { .. }
979 | ExecuteResponse::Subscribing { .. }
980 | ExecuteResponse::Fetch { .. }
981 | ExecuteResponse::CopyFrom { .. } => true,
982 ExecuteResponse::CopyTo { resp, .. } => {
985 !matches!(**resp, ExecuteResponse::SendingRowsImmediate { .. })
986 }
987 _ => false,
988 }
989}
990
991#[derive(Error, Debug)]
993pub enum CollectionLookupError {
994 #[error("instance does not exist: {0}")]
996 InstanceMissing(ComputeInstanceId),
997 #[error("the instance has shut down")]
999 InstanceShutDown,
1000 #[error("collection does not exist: {0}")]
1002 CollectionMissing(GlobalId),
1003}
1004
1005impl From<InstanceMissing> for CollectionLookupError {
1006 fn from(error: InstanceMissing) -> Self {
1007 Self::InstanceMissing(error.0)
1008 }
1009}
1010
1011impl From<InstanceShutDown> for CollectionLookupError {
1012 fn from(_error: InstanceShutDown) -> Self {
1013 Self::InstanceShutDown
1014 }
1015}
1016
1017impl From<CollectionMissing> for CollectionLookupError {
1018 fn from(error: CollectionMissing) -> Self {
1019 Self::CollectionMissing(error.0)
1020 }
1021}
1022
1023impl From<AcquireReadHoldsError> for CollectionLookupError {
1024 fn from(error: AcquireReadHoldsError) -> Self {
1025 match error {
1026 AcquireReadHoldsError::CollectionMissing(id) => Self::CollectionMissing(id),
1027 AcquireReadHoldsError::InstanceShutDown => Self::InstanceShutDown,
1028 }
1029 }
1030}