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};
46use crate::session::{LifecycleTimestamps, Session};
47use crate::statement_logging::{
48 FrontendStatementLoggingEvent, PreparedStatementEvent, PreparedStatementLoggingInfo,
49 StatementLoggingFrontend, StatementLoggingId, WatchSetCreation,
50};
51use crate::{AdapterError, Client, CollectionIdBundle, ReadHolds, metrics, statement_logging};
52
53pub type StorageCollectionsHandle =
55 Arc<dyn mz_storage_client::storage_collections::StorageCollections + Send + Sync>;
56
57#[derive(Debug)]
59pub struct PeekClient {
60 coordinator_client: Client,
61 catalog_cache: Weak<Catalog>,
68 compute_instances: BTreeMap<ComputeInstanceId, InstanceClient>,
73 pub storage_collections: StorageCollectionsHandle,
75 pub transient_id_gen: Arc<TransientIdGen>,
77 pub optimizer_metrics: OptimizerMetrics,
78 oracles: BTreeMap<Timeline, Arc<dyn TimestampOracle<Timestamp> + Send + Sync>>,
80 persist_client: PersistClient,
81 pub statement_logging_frontend: StatementLoggingFrontend,
83 pub occ_write_semaphore: Arc<Semaphore>,
85 pub frontend_read_then_write_enabled: bool,
87 pub(crate) group_commit_notifier: GroupCommitNotifier,
90 pub read_only: bool,
92}
93
94impl PeekClient {
95 pub fn new(
100 coordinator_client: Client,
101 catalog: &Arc<Catalog>,
102 storage_collections: StorageCollectionsHandle,
103 transient_id_gen: Arc<TransientIdGen>,
104 optimizer_metrics: OptimizerMetrics,
105 persist_client: PersistClient,
106 statement_logging_frontend: StatementLoggingFrontend,
107 occ_write_semaphore: Arc<Semaphore>,
108 frontend_read_then_write_enabled: bool,
109 group_commit_notifier: GroupCommitNotifier,
110 read_only: bool,
111 ) -> Self {
112 Self {
113 coordinator_client,
114 catalog_cache: Arc::downgrade(catalog),
115 compute_instances: Default::default(), storage_collections,
117 transient_id_gen,
118 optimizer_metrics,
119 statement_logging_frontend,
120 oracles: Default::default(), persist_client,
122 occ_write_semaphore,
123 frontend_read_then_write_enabled,
124 group_commit_notifier,
125 read_only,
126 }
127 }
128
129 pub async fn ensure_compute_instance_client(
130 &mut self,
131 compute_instance: ComputeInstanceId,
132 ) -> Result<InstanceClient, InstanceMissing> {
133 if !self.compute_instances.contains_key(&compute_instance) {
134 let client = self
135 .call_coordinator(|tx| Command::GetComputeInstanceClient {
136 instance_id: compute_instance,
137 tx,
138 })
139 .await?;
140 self.compute_instances.insert(compute_instance, client);
141 }
142 Ok(self
143 .compute_instances
144 .get(&compute_instance)
145 .expect("ensured above")
146 .clone())
147 }
148
149 pub async fn ensure_oracle(
150 &mut self,
151 timeline: Timeline,
152 ) -> Result<&mut Arc<dyn TimestampOracle<Timestamp> + Send + Sync>, AdapterError> {
153 if !self.oracles.contains_key(&timeline) {
154 let oracle = self
155 .call_coordinator(|tx| Command::GetOracle {
156 timeline: timeline.clone(),
157 tx,
158 })
159 .await?;
160 self.oracles.insert(timeline.clone(), oracle);
161 }
162 Ok(self.oracles.get_mut(&timeline).expect("ensured above"))
163 }
164
165 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
178 let cached = self
184 .catalog_cache
185 .upgrade()
186 .filter(|catalog| catalog.transient_revision_is_current());
187 if let Some(catalog) = cached {
188 self.coordinator_client
189 .metrics()
190 .catalog_snapshot_cache
191 .with_label_values(&[context, "hit"])
192 .inc();
193 return catalog;
194 }
195
196 let start = std::time::Instant::now();
199 let CatalogSnapshot { catalog } = self
200 .call_coordinator(|tx| Command::CatalogSnapshot { tx })
201 .await;
202 let metrics = self.coordinator_client.metrics();
203 metrics
204 .catalog_snapshot_seconds
205 .with_label_values(&[context])
206 .observe(start.elapsed().as_secs_f64());
207 metrics
208 .catalog_snapshot_cache
209 .with_label_values(&[context, "miss"])
210 .inc();
211 self.catalog_cache = Arc::downgrade(&catalog);
212 catalog
213 }
214
215 pub(crate) async fn call_coordinator<T, F>(&self, f: F) -> T
216 where
217 F: FnOnce(oneshot::Sender<T>) -> Command,
218 {
219 let (tx, rx) = oneshot::channel();
220 self.coordinator_client.send(f(tx));
221 rx.await
222 .expect("if the coordinator is still alive, it shouldn't have dropped our call")
223 }
224
225 pub(crate) fn coordinator_client(&self) -> &crate::Client {
227 &self.coordinator_client
228 }
229
230 pub async fn acquire_read_holds_and_least_valid_write(
243 &mut self,
244 id_bundle: &CollectionIdBundle,
245 ) -> Result<(ReadHolds, Antichain<Timestamp>), CollectionLookupError> {
246 let mut read_holds = ReadHolds::new();
247 let mut upper = Antichain::new();
248
249 if !id_bundle.storage_ids.is_empty() {
250 let desired_storage: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
251 let storage_read_holds = self
252 .storage_collections
253 .acquire_read_holds(desired_storage)?;
254 read_holds.storage_holds = storage_read_holds
255 .into_iter()
256 .map(|hold| (hold.id(), hold))
257 .collect();
258
259 let storage_ids: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
260 for f in self
261 .storage_collections
262 .collections_frontiers(storage_ids)?
263 {
264 upper.extend(f.write_frontier);
265 }
266 }
267
268 for (&instance_id, collection_ids) in &id_bundle.compute_ids {
269 let client = self.ensure_compute_instance_client(instance_id).await?;
270
271 for (id, read_hold, write_frontier) in client
272 .acquire_read_holds_and_collection_write_frontiers(
273 collection_ids.iter().copied().collect(),
274 )
275 .await?
276 {
277 let prev = read_holds
278 .compute_holds
279 .insert((instance_id, id), read_hold);
280 assert!(
281 prev.is_none(),
282 "duplicate compute ID in id_bundle {id_bundle:?}"
283 );
284
285 upper.extend(write_frontier);
286 }
287 }
288
289 Ok((read_holds, upper))
290 }
291
292 pub(crate) async fn implement_fast_path_peek_plan(
307 &mut self,
308 fast_path: FastPathPlan,
309 timestamp: Timestamp,
310 finishing: mz_expr::RowSetFinishing,
311 compute_instance: ComputeInstanceId,
312 target_replica: Option<mz_cluster_client::ReplicaId>,
313 intermediate_result_type: mz_repr::SqlRelationType,
314 max_result_size: u64,
315 max_returned_query_size: Option<u64>,
316 row_set_finishing_seconds: Histogram,
317 input_read_holds: ReadHolds,
318 peek_stash_read_batch_size_bytes: usize,
319 peek_stash_read_memory_budget_bytes: usize,
320 conn_id: mz_adapter_types::connection::ConnectionId,
321 depends_on: std::collections::BTreeSet<mz_repr::GlobalId>,
322 watch_set: Option<WatchSetCreation>,
323 logging: &mut ExecutionLogging,
324 ) -> Result<crate::ExecuteResponse, AdapterError> {
325 if let FastPathPlan::Constant(rows_res, _) = fast_path {
327 if let Some(ref ws) = watch_set {
330 self.log_lifecycle_event(
331 ws.logging_id,
332 statement_logging::StatementLifecycleEvent::StorageDependenciesFinished,
333 );
334 self.log_lifecycle_event(
335 ws.logging_id,
336 statement_logging::StatementLifecycleEvent::ComputeDependenciesFinished,
337 );
338 }
339
340 let mut rows = match rows_res {
341 Ok(rows) => rows,
342 Err(e) => return Err(e.into()),
343 };
344 consolidate(&mut rows);
345
346 let mut results = Vec::new();
347 for (row, count) in rows {
348 let count = match u64::try_from(count.into_inner()) {
349 Ok(u) => usize::cast_from(u),
350 Err(_) => {
351 return Err(AdapterError::Unstructured(anyhow::anyhow!(
352 "Negative multiplicity in constant result: {}",
353 count
354 )));
355 }
356 };
357 match std::num::NonZeroUsize::new(count) {
358 Some(nzu) => {
359 results.push((row, nzu));
360 }
361 None => {
362 }
364 };
365 }
366 let row_collection = RowCollection::new(results, &finishing.order_by);
367 return match finishing.finish(
368 row_collection,
369 max_result_size,
370 max_returned_query_size,
371 &row_set_finishing_seconds,
372 ) {
373 Ok((rows, _bytes)) => Ok(Coordinator::send_immediate_rows(rows)),
374 Err(e) => Err(AdapterError::ResultSize(e)),
376 };
377 }
378
379 let (peek_target, target_read_hold, literal_constraints, mfp, strategy) = match fast_path {
380 FastPathPlan::PeekExisting(_coll_id, idx_id, literal_constraints, mfp) => {
381 let peek_target = PeekTarget::Index { id: idx_id };
382 let target_read_hold = input_read_holds
383 .compute_holds
384 .get(&(compute_instance, idx_id))
385 .expect("missing compute read hold on PeekExisting peek target")
386 .clone();
387 let strategy = statement_logging::StatementExecutionStrategy::FastPath;
388 (
389 peek_target,
390 target_read_hold,
391 literal_constraints,
392 mfp,
393 strategy,
394 )
395 }
396 FastPathPlan::PeekPersist(coll_id, literal_constraint, mfp) => {
397 let literal_constraints = literal_constraint.map(|r| vec![r]);
398 let metadata = self
399 .storage_collections
400 .collection_metadata(coll_id)
401 .map_err(AdapterError::concurrent_dependency_drop_from_collection_missing)?
402 .clone();
403 let peek_target = PeekTarget::Persist {
404 id: coll_id,
405 metadata,
406 };
407 let target_read_hold = input_read_holds
408 .storage_holds
409 .get(&coll_id)
410 .expect("missing storage read hold on PeekPersist peek target")
411 .clone();
412 let strategy = statement_logging::StatementExecutionStrategy::PersistFastPath;
413 (
414 peek_target,
415 target_read_hold,
416 literal_constraints,
417 mfp,
418 strategy,
419 )
420 }
421 FastPathPlan::Constant(..) => {
422 unreachable!()
424 }
425 };
426
427 let (rows_tx, rows_rx) = oneshot::channel();
428 let uuid = Uuid::new_v4();
429
430 let cols = (0..intermediate_result_type.arity()).map(|i| format!("peek_{i}"));
433 let result_desc = RelationDesc::new(intermediate_result_type.clone(), cols);
434
435 let client = self
436 .ensure_compute_instance_client(compute_instance)
437 .await
438 .map_err(AdapterError::concurrent_dependency_drop_from_instance_missing)?;
439
440 self.call_coordinator(|tx| Command::RegisterFrontendPeek {
445 uuid,
446 conn_id: conn_id.clone(),
447 cluster_id: compute_instance,
448 depends_on,
449 is_fast_path: true,
450 watch_set,
451 tx,
452 })
453 .await?;
454
455 logging.defuse();
461
462 fail::fail_point!("peek_after_register_before_issue");
467
468 let finishing_for_instance = finishing.clone();
469 let peek_result = client
470 .peek(
471 peek_target,
472 literal_constraints,
473 uuid,
474 timestamp,
475 result_desc,
476 finishing_for_instance,
477 mfp,
478 target_read_hold,
479 target_replica,
480 rows_tx,
481 )
482 .await;
483
484 if let Err(err) = peek_result {
485 let err = AdapterError::concurrent_dependency_drop_from_instance_peek_error(
486 err,
487 compute_instance,
488 );
489 self.call_coordinator(|tx| Command::UnregisterFrontendPeek {
495 uuid,
496 reason: statement_logging::StatementEndedExecutionReason::Errored {
497 error: err.to_string(),
498 },
499 tx,
500 })
501 .await;
502 return Err(err);
503 }
504
505 let peek_response_stream = Coordinator::create_peek_response_stream(
506 rows_rx,
507 finishing,
508 max_result_size,
509 max_returned_query_size,
510 row_set_finishing_seconds,
511 self.persist_client.clone(),
512 peek_stash_read_batch_size_bytes,
513 peek_stash_read_memory_budget_bytes,
514 );
515
516 Ok(crate::ExecuteResponse::SendingRowsStreaming {
517 rows: Box::pin(peek_response_stream),
518 instance_id: compute_instance,
519 strategy,
520 })
521 }
522
523 fn begin_statement_logging(
528 &self,
529 session: &mut Session,
530 params: &Params,
531 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
532 catalog: &Catalog,
533 lifecycle_timestamps: Option<LifecycleTimestamps>,
534 ) -> StatementLoggingGuard {
535 let result = self.statement_logging_frontend.begin_statement_execution(
536 session,
537 params,
538 logging,
539 catalog.system_config(),
540 lifecycle_timestamps,
541 );
542
543 let id = result.map(
544 |(logging_id, began_execution, mseh_update, prepared_statement)| {
545 self.log_began_execution(began_execution, mseh_update, prepared_statement);
546 logging_id
547 },
548 );
549
550 StatementLoggingGuard {
551 id,
552 coordinator_client: self.coordinator_client.clone(),
553 now: self.statement_logging_frontend.now.clone(),
554 }
555 }
556
557 pub(crate) fn log_began_execution(
559 &self,
560 record: statement_logging::StatementBeganExecutionRecord,
561 mseh_update: Row,
562 prepared_statement: Option<PreparedStatementEvent>,
563 ) {
564 self.coordinator_client
565 .send(Command::FrontendStatementLogging(
566 FrontendStatementLoggingEvent::BeganExecution {
567 record,
568 mseh_update,
569 prepared_statement,
570 },
571 ));
572 }
573
574 pub(crate) fn log_set_cluster(
576 &self,
577 id: StatementLoggingId,
578 cluster_id: mz_controller_types::ClusterId,
579 cluster_name: String,
580 ) {
581 self.coordinator_client
582 .send(Command::FrontendStatementLogging(
583 FrontendStatementLoggingEvent::SetCluster {
584 id,
585 cluster_id,
586 cluster_name,
587 },
588 ));
589 }
590
591 pub(crate) fn log_set_timestamp(&self, id: StatementLoggingId, timestamp: mz_repr::Timestamp) {
593 self.coordinator_client
594 .send(Command::FrontendStatementLogging(
595 FrontendStatementLoggingEvent::SetTimestamp { id, timestamp },
596 ));
597 }
598
599 pub(crate) fn log_set_transient_index_id(
601 &self,
602 id: StatementLoggingId,
603 transient_index_id: mz_repr::GlobalId,
604 ) {
605 self.coordinator_client
606 .send(Command::FrontendStatementLogging(
607 FrontendStatementLoggingEvent::SetTransientIndex {
608 id,
609 transient_index_id,
610 },
611 ));
612 }
613
614 pub(crate) fn log_lifecycle_event(
616 &self,
617 id: StatementLoggingId,
618 event: statement_logging::StatementLifecycleEvent,
619 ) {
620 let when = (self.statement_logging_frontend.now)();
621 self.coordinator_client
622 .send(Command::FrontendStatementLogging(
623 FrontendStatementLoggingEvent::Lifecycle { id, event, when },
624 ));
625 }
626}
627
628#[must_use = "StatementLoggingGuard must be explicitly retired or handed off; \
644 otherwise `Drop` will log the statement as Aborted"]
645struct StatementLoggingGuard {
646 id: Option<StatementLoggingId>,
648 coordinator_client: Client,
649 now: mz_ore::now::NowFn,
650}
651
652impl StatementLoggingGuard {
653 fn adopt(outer: ExecuteContextGuard, peek_client: &PeekClient) -> Self {
656 Self {
657 id: outer.defuse().retire(),
658 coordinator_client: peek_client.coordinator_client.clone(),
659 now: peek_client.statement_logging_frontend.now.clone(),
660 }
661 }
662
663 fn id(&self) -> Option<StatementLoggingId> {
665 self.id
666 }
667
668 fn retire(mut self, reason: statement_logging::StatementEndedExecutionReason) {
671 self.emit(reason);
672 }
673
674 fn release(mut self) -> ExecuteContextExtra {
677 ExecuteContextExtra::new(self.id.take())
678 }
679
680 fn defuse(&mut self) {
684 self.id = None;
685 }
686
687 fn emit(&mut self, reason: statement_logging::StatementEndedExecutionReason) {
688 let Some(id) = self.id.take() else {
689 return;
690 };
691 let ended_at = (self.now)();
692 let record = statement_logging::StatementEndedExecutionRecord {
693 id: id.0,
694 reason,
695 ended_at,
696 };
697 let _ = self
701 .coordinator_client
702 .try_send(Command::FrontendStatementLogging(
703 FrontendStatementLoggingEvent::EndedExecution(record),
704 ));
705 }
706}
707
708impl Drop for StatementLoggingGuard {
709 fn drop(&mut self) {
710 self.emit(statement_logging::StatementEndedExecutionReason::Aborted);
713 }
714}
715
716pub(crate) struct ExecutionLogging {
727 guard: Option<StatementLoggingGuard>,
728 coordinator_must_not_run: bool,
732}
733
734pub(crate) enum TakeOver {
736 StatementToRun,
738 UnrolledExecute,
747}
748
749impl ExecutionLogging {
750 pub(crate) fn adopt(outer: Option<ExecuteContextGuard>, peek_client: &PeekClient) -> Self {
754 Self {
755 guard: outer.map(|outer| StatementLoggingGuard::adopt(outer, peek_client)),
756 coordinator_must_not_run: false,
757 }
758 }
759
760 pub(crate) fn id(&self) -> Option<StatementLoggingId> {
763 self.guard.as_ref().and_then(|guard| guard.id())
764 }
765
766 pub(crate) fn take_over(
775 &mut self,
776 peek_client: &PeekClient,
777 session: &mut Session,
778 stmt: Option<&Statement<Raw>>,
779 params: &Params,
780 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
781 catalog: &Catalog,
782 lifecycle_timestamps: Option<LifecycleTimestamps>,
783 taking_over: TakeOver,
784 ) -> Option<StatementLoggingId> {
785 self.begin_or_inherit(
786 peek_client,
787 session,
788 params,
789 logging,
790 catalog,
791 lifecycle_timestamps,
792 );
793 count_statement(session, stmt);
794 if matches!(taking_over, TakeOver::StatementToRun) {
795 self.coordinator_must_not_run = true;
796 }
797 self.id()
798 }
799
800 fn begin_or_inherit(
803 &mut self,
804 peek_client: &PeekClient,
805 session: &mut Session,
806 params: &Params,
807 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
808 catalog: &Catalog,
809 lifecycle_timestamps: Option<LifecycleTimestamps>,
810 ) {
811 if self.guard.is_none() {
812 self.guard = Some(peek_client.begin_statement_logging(
813 session,
814 params,
815 logging,
816 catalog,
817 lifecycle_timestamps,
818 ));
819 }
820 }
821
822 #[must_use]
827 pub(crate) fn release(&mut self) -> Option<ExecuteContextExtra> {
828 if self.coordinator_must_not_run {
829 soft_panic_or_log!(
830 "statement handed to the coordinator after the session task took it over: \
831 its per-statement metrics are counted twice"
832 );
833 }
834 self.guard.take().map(|guard| guard.release())
835 }
836
837 pub(crate) fn retire(self, result: &Result<ExecuteResponse, AdapterError>) {
839 let Some(guard) = self.guard else {
840 return;
841 };
842 if guard.id().is_none() {
846 return;
847 }
848 guard.retire(end_reason(result));
849 }
850
851 pub(crate) fn defuse(&mut self) {
854 if let Some(guard) = self.guard.as_mut() {
855 guard.defuse();
856 }
857 }
858}
859
860fn end_reason(
867 result: &Result<ExecuteResponse, AdapterError>,
868) -> statement_logging::StatementEndedExecutionReason {
869 if let Ok(response) = result {
870 if terminates_elsewhere(response) {
871 soft_panic_or_log!(
872 "frontend-sequenced statement still owed an end event while returning {:?}",
873 crate::command::ExecuteResponseKind::from(response)
874 );
875 return statement_logging::StatementEndedExecutionReason::Aborted;
876 }
877 }
878 result.into()
879}
880
881fn count_statement(session: &Session, stmt: Option<&Statement<Raw>>) {
887 let Some(stmt) = stmt else {
888 return;
889 };
890 let session_type = metrics::session_type_label_value(session.user());
891 session
892 .metrics()
893 .query_total(&[session_type, metrics::statement_type_label_value(stmt)])
894 .inc();
895 if let Statement::Subscribe(SubscribeStatement { output, .. })
896 | Statement::Copy(CopyStatement {
897 relation: CopyRelation::Subscribe(SubscribeStatement { output, .. }),
898 ..
899 }) = stmt
900 {
901 session
902 .metrics()
903 .subscribe_outputs(&[session_type, metrics::subscribe_output_label_value(output)])
904 .inc();
905 }
906}
907
908fn terminates_elsewhere(response: &ExecuteResponse) -> bool {
913 match response {
914 ExecuteResponse::SendingRowsStreaming { .. }
915 | ExecuteResponse::Subscribing { .. }
916 | ExecuteResponse::Fetch { .. }
917 | ExecuteResponse::CopyFrom { .. } => true,
918 ExecuteResponse::CopyTo { resp, .. } => {
921 !matches!(**resp, ExecuteResponse::SendingRowsImmediate { .. })
922 }
923 _ => false,
924 }
925}
926
927#[derive(Error, Debug)]
929pub enum CollectionLookupError {
930 #[error("instance does not exist: {0}")]
932 InstanceMissing(ComputeInstanceId),
933 #[error("the instance has shut down")]
935 InstanceShutDown,
936 #[error("collection does not exist: {0}")]
938 CollectionMissing(GlobalId),
939}
940
941impl From<InstanceMissing> for CollectionLookupError {
942 fn from(error: InstanceMissing) -> Self {
943 Self::InstanceMissing(error.0)
944 }
945}
946
947impl From<InstanceShutDown> for CollectionLookupError {
948 fn from(_error: InstanceShutDown) -> Self {
949 Self::InstanceShutDown
950 }
951}
952
953impl From<CollectionMissing> for CollectionLookupError {
954 fn from(error: CollectionMissing) -> Self {
955 Self::CollectionMissing(error.0)
956 }
957}
958
959impl From<AcquireReadHoldsError> for CollectionLookupError {
960 fn from(error: AcquireReadHoldsError) -> Self {
961 match error {
962 AcquireReadHoldsError::CollectionMissing(id) => Self::CollectionMissing(id),
963 AcquireReadHoldsError::InstanceShutDown => Self::InstanceShutDown,
964 }
965 }
966}