1use std::collections::BTreeMap;
14use std::pin::Pin;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17
18use fail::fail_point;
19use maplit::{btreemap, btreeset};
20use mz_adapter_types::compaction::SINCE_GRANULARITY;
21use mz_adapter_types::connection::ConnectionId;
22use mz_audit_log::VersionedEvent;
23use mz_catalog::SYSTEM_CONN_ID;
24use mz_catalog::memory::objects::Sink;
25use mz_cluster_client::ReplicaId;
26use mz_controller_types::ClusterId;
27use mz_ore::cast::CastFrom;
28use mz_ore::instrument;
29use mz_ore::metrics::MetricsFutureExt;
30use mz_ore::now::to_datetime;
31use mz_ore::retry::Retry;
32use mz_ore::task;
33use mz_ore::tracing::OpenTelemetryContext;
34use mz_repr::adt::numeric::Numeric;
35use mz_repr::{CatalogItemId, GlobalId, Timestamp};
36use mz_sql::catalog::CatalogClusterReplica;
37use mz_sql::session::metadata::SessionMetadata;
38use mz_sql::session::vars::{self, DEFAULT_TIMESTAMP_INTERVAL, SystemVars, Var};
39use mz_storage_client::controller::{CollectionDescription, DataSource, ExportDescription};
40use mz_storage_types::connections::inline::IntoInlineConnection;
41use mz_storage_types::read_policy::ReadPolicy;
42use mz_storage_types::sources::SourceConnection;
43use mz_storage_types::sources::kafka::KAFKA_PROGRESS_DESC;
44use serde_json::json;
45use tracing::{Instrument, Level, event, info_span, warn};
46
47use crate::active_compute_sink::{ActiveComputeSink, ActiveComputeSinkRetireReason};
48use crate::catalog::{DropObjectInfo, Op, TransactionResult};
49use crate::coord::appends::{BuiltinTableAppendCompletion, BuiltinTableAppendNotify};
50use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdate;
51use crate::coord::{Coordinator, DeferredDdl, Message, PendingTxn};
52use crate::session::{Session, TransactionOps};
53use crate::telemetry::{EventDetails, SegmentClientExt};
54use crate::util::ResultExt;
55use crate::{AdapterError, ExecuteContext, catalog, flags};
56
57#[derive(Debug)]
58pub struct PendingSourceStatement {
59 ctx: ExecuteContext,
60 statement: catalog::command::source_statement::SourceStatement,
61}
62
63#[derive(Debug)]
64pub struct PendingDdlCommit {
65 context: DdlCommitContext,
66 commands: Vec<catalog::command::CatalogCommand>,
67 responses: BTreeMap<usize, mz_sql::pure::SourcePreparationResponse>,
68}
69
70#[derive(Debug)]
71enum DdlCommitContext {
72 Transaction(PendingTxn),
73 Statement {
74 ctx: ExecuteContext,
75 response: crate::ExecuteResponse,
76 },
77}
78
79impl PendingDdlCommit {
80 pub(super) fn conn_id(&self) -> &ConnectionId {
81 match &self.context {
82 DdlCommitContext::Transaction(txn) => txn.ctx.session().conn_id(),
83 DdlCommitContext::Statement { ctx, response: _ } => ctx.session().conn_id(),
84 }
85 }
86
87 pub(super) fn transaction(
88 txn: PendingTxn,
89 commands: Vec<catalog::command::CatalogCommand>,
90 ) -> Self {
91 Self {
92 context: DdlCommitContext::Transaction(txn),
93 commands,
94 responses: BTreeMap::new(),
95 }
96 }
97
98 pub(super) fn statement(
99 ctx: ExecuteContext,
100 response: crate::ExecuteResponse,
101 commands: Vec<catalog::command::CatalogCommand>,
102 ) -> Self {
103 Self {
104 context: DdlCommitContext::Statement { ctx, response },
105 commands,
106 responses: BTreeMap::new(),
107 }
108 }
109}
110
111impl DdlCommitContext {
112 fn context_mut(&mut self) -> &mut ExecuteContext {
113 match self {
114 Self::Transaction(txn) => &mut txn.ctx,
115 Self::Statement { ctx, .. } => ctx,
116 }
117 }
118
119 fn finish(self, result: Result<(), AdapterError>) {
120 match self {
121 Self::Transaction(mut txn) => {
122 if let Err(error) = result {
123 txn.response = Err(error);
124 txn.action = crate::session::EndTransactionAction::Rollback;
125 }
126 txn.finish();
127 }
128 Self::Statement { ctx, response } => ctx.retire(result.map(|()| response)),
129 }
130 }
131}
132
133async fn catalog_preparation_test_pause(name: &'static str) {
135 let paused = || {
136 fail::fail_point!(name, |_| true);
137 false
138 };
139 while paused() {
141 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
142 }
143}
144
145impl Coordinator {
146 fn prepare_command_execution(
148 &mut self,
149 ctx: &mut ExecuteContext,
150 plan: &mz_sql::plan::Plan,
151 ) -> Result<(), AdapterError> {
152 ctx.tx_mut()
153 .set_allowed(crate::ExecuteResponse::generated_from(
154 &mz_sql::plan::PlanKind::from(plan),
155 ));
156 if self.controller.read_only() && !plan.allowed_in_read_only() {
157 return Err(AdapterError::ReadOnly);
158 }
159 self.prepare_plan_execution(ctx, plan)?;
160 Ok(())
161 }
162
163 pub(super) fn sequence_catalog_result_command(
164 &mut self,
165 mut ctx: ExecuteContext,
166 statement: Arc<mz_sql::ast::Statement<mz_sql::ast::Raw>>,
167 params: mz_sql::plan::Params,
168 ) {
169 let prepared = {
170 let catalog = self.catalog().for_session(ctx.session());
171 catalog::command::CatalogResultCommand::prepare(
172 catalog.state(),
173 statement,
174 params,
175 ctx.session().meta(),
176 )
177 };
178 let (command, plan) = match prepared {
179 Ok(prepared) => prepared,
180 Err(error) => return ctx.retire(Err(error)),
181 };
182 if let Err(error) = self.prepare_command_execution(&mut ctx, &plan) {
183 return ctx.retire(Err(error));
184 }
185 let row = match catalog::command::CatalogResultCommand::result(&plan) {
186 Ok(row) => row,
187 Err(error) => return ctx.retire(Err(error)),
188 };
189 let command = Arc::new(command);
190 self.emit_command_notices(
191 ctx.session(),
192 &catalog::command::CatalogCommand::CatalogResult(Arc::clone(&command)),
193 );
194 ctx.session_mut().retain_catalog_result(command);
195 ctx.retire(Ok(Self::send_immediate_rows(row)));
196 }
197
198 pub(super) async fn sequence_source_statement(
199 &mut self,
200 ctx: ExecuteContext,
201 statement: Arc<mz_sql::ast::Statement<mz_sql::ast::Raw>>,
202 params: mz_sql::plan::Params,
203 ) {
204 let statement = catalog::command::source_statement::SourceStatement::new(
205 statement,
206 params,
207 ctx.session().meta(),
208 (self.catalog().config().now)(),
209 );
210 self.sequence_source_statement_ready(PendingSourceStatement { ctx, statement }, None)
211 .await;
212 }
213
214 pub(super) async fn sequence_source_statement_ready(
215 &mut self,
216 mut pending: PendingSourceStatement,
217 mut response: Option<mz_sql::pure::SourcePreparationResponse>,
218 ) {
219 use catalog::command::source_statement::{SourceCommand, SourceResolution};
220 if self.controller.read_only() {
221 return pending.ctx.retire(Err(AdapterError::ReadOnly));
222 }
223 let compiled = loop {
224 let state = pending
225 .ctx
226 .session()
227 .transaction()
228 .catalog_state()
229 .unwrap_or_else(|| self.catalog().state());
230 let result = match response.take() {
231 Some(response) => pending.statement.finish(state, response),
232 None => pending.statement.resolve(state),
233 };
234 match result {
235 Err(error) => return pending.ctx.retire(Err(error)),
236 Ok(SourceResolution::Ready(compiled)) => break compiled,
237 Ok(SourceResolution::Allocate(keys)) => {
238 let ids = match self.allocate_user_ids(u64::cast_from(keys.len())).await {
239 Ok(ids) => ids,
240 Err(error) => return pending.ctx.retire(Err(error)),
241 };
242 pending.statement.record_ids(keys, ids);
243 }
244 Ok(SourceResolution::Prepare(request)) => {
245 let conn_id = pending.ctx.session().conn_id().clone();
246 let config = self.controller.storage.config().clone();
247 let tx = self.internal_cmd_tx.clone();
248 let otel_ctx = OpenTelemetryContext::obtain();
249 task::spawn(|| "ddl_statement_preparation", async move {
250 let response = request.execute(&config).await;
251 tracing::debug!(
252 target: "mz_adapter::catalog_preparation",
253 %conn_id, kind = "purification", "catalog preparation complete"
254 );
255 catalog_preparation_test_pause("catalog_purification_ready").await;
256 if let Err(error) = tx.send(Message::SourceStatementReady {
257 pending, response, otel_ctx,
258 }) {
259 warn!("internal_cmd_rx dropped before source preparation completed: {error:?}");
260 }
261 }.instrument(tracing::Span::current()));
262 return;
263 }
264 }
265 };
266 let mut ctx = pending.ctx;
267 if let Err(error) = self.prepare_command_execution(&mut ctx, &compiled.plan) {
268 return ctx.retire(Err(error));
269 }
270 let response = match &compiled.plan {
271 mz_sql::plan::Plan::CreateSources(_) => crate::ExecuteResponse::CreatedSource,
272 mz_sql::plan::Plan::CreateTable(_) => crate::ExecuteResponse::CreatedTable,
273 mz_sql::plan::Plan::AlterSource(_) => {
274 crate::ExecuteResponse::AlteredObject(mz_sql::catalog::ObjectType::Source)
275 }
276 mz_sql::plan::Plan::AlterNoop(plan) => {
277 crate::ExecuteResponse::AlteredObject(plan.object_type)
278 }
279 _ => unreachable!("source command plan"),
280 };
281 if ctx.session().transaction().is_ddl() {
282 let ts = self.get_local_write_ts().await.timestamp;
283 let TransactionOps::DDL {
284 commands: _,
285 state,
286 snapshot,
287 } = &ctx
288 .session()
289 .transaction()
290 .inner()
291 .expect("DDL transaction")
292 .ops
293 else {
294 unreachable!("DDL transaction")
295 };
296 let staged = match self
297 .catalog()
298 .stage_source_statement(
299 pending.statement,
300 *compiled,
301 (state, snapshot),
302 self.active_conns.get(ctx.session().conn_id()),
303 ts,
304 )
305 .await
306 {
307 Ok(staged) => staged,
308 Err(error) => return ctx.retire(Err(error)),
309 };
310 let command = catalog::command::CatalogCommand::Source(Arc::new(staged.command));
311 if let Err(error) = ctx
312 .session_mut()
313 .transaction_mut()
314 .add_ops(TransactionOps::DDL {
315 commands: vec![command.clone()],
316 state: staged.state,
317 snapshot: staged.snapshot,
318 })
319 {
320 return ctx.retire(Err(error));
321 }
322 self.emit_command_notices(ctx.session(), &command);
323 ctx.retire(Ok(response));
324 } else {
325 let command = catalog::command::CatalogCommand::Source(Arc::new(
326 SourceCommand::unobserved(pending.statement, *compiled),
327 ));
328 self.sequence_ddl_commit(PendingDdlCommit::statement(ctx, response, vec![command]))
329 .await;
330 }
331 }
332
333 fn emit_command_notices(&self, session: &Session, command: &catalog::command::CatalogCommand) {
334 session.add_notices(command.notices().iter().cloned().map(Into::into));
335 let catalog::command::CatalogCommand::Source(command) = command else {
336 return;
337 };
338 if let Some(notice) = command.skipped_notice() {
339 session.add_notice(notice);
340 return;
341 }
342 let state = session
343 .transaction()
344 .catalog_state()
345 .unwrap_or_else(|| self.catalog().state());
346 for id in command.item_ids() {
347 let Some(entry) = state.try_get_entry(&id) else {
348 continue;
349 };
350 let Some(source) = entry.source() else {
351 continue;
352 };
353 use mz_catalog::memory::objects::DataSourceDesc;
354 let cluster_id = match &source.data_source {
355 DataSourceDesc::Ingestion { desc, cluster_id }
356 | DataSourceDesc::OldSyntaxIngestion {
357 desc, cluster_id, ..
358 } if desc.connection.prefers_single_replica() => cluster_id,
359 _ => continue,
360 };
361 let Some(cluster) = self.catalog().try_get_cluster(*cluster_id) else {
362 continue;
363 };
364 let name = state.resolve_full_name(entry.name(), None);
365 self.notify_single_replica_sources(session, cluster, Some(&name));
366 }
367 }
368
369 pub(super) async fn sequence_rename_command(
370 &mut self,
371 mut ctx: ExecuteContext,
372 statement: Arc<mz_sql::ast::Statement<mz_sql::ast::Raw>>,
373 params: mz_sql::plan::Params,
374 ) {
375 let prepared = {
376 let catalog = self.catalog().for_session(ctx.session());
377 catalog::command::RenameCommand::prepare(
378 catalog.state(),
379 statement,
380 params,
381 ctx.session().meta(),
382 )
383 };
384 let (command, plan) = match prepared {
385 Ok(prepared) => prepared,
386 Err(error) => return ctx.retire(Err(error)),
387 };
388 if let Err(error) = self.prepare_command_execution(&mut ctx, &plan) {
389 return ctx.retire(Err(error));
390 }
391 let command = Arc::new(command);
392 if !ctx.session().transaction().is_ddl() {
393 let response = crate::ExecuteResponse::AlteredObject(command.object_type());
394 self.sequence_ddl_commit(PendingDdlCommit::statement(
395 ctx,
396 response,
397 vec![catalog::command::CatalogCommand::Rename(command)],
398 ))
399 .await;
400 return;
401 }
402 let TransactionOps::DDL {
403 state, snapshot, ..
404 } = &ctx
405 .session()
406 .transaction()
407 .inner()
408 .expect("DDL transaction")
409 .ops
410 else {
411 unreachable!("standalone command handled above")
412 };
413 let state = state.clone();
414 let snapshot = snapshot.clone();
415 let ts = self.get_local_write_ts().await.timestamp;
416 let result = Box::pin(self.catalog().transact_incremental_dry_run(
417 &state,
418 vec![Op::RenameCommand(Arc::clone(&command))],
419 self.active_conns.get(ctx.session().conn_id()),
420 Some(snapshot),
421 ts,
422 ))
423 .await
424 .and_then(|(state, snapshot)| {
425 ctx.session_mut()
426 .transaction_mut()
427 .add_ops(TransactionOps::DDL {
428 commands: vec![catalog::command::CatalogCommand::Rename(Arc::clone(
429 &command,
430 ))],
431 state,
432 snapshot,
433 })
434 });
435 match result {
436 Ok(()) => {
437 ctx.session()
438 .add_notices(command.notices().iter().cloned().map(Into::into));
439 ctx.retire(Ok(crate::ExecuteResponse::AlteredObject(
440 command.object_type(),
441 )));
442 }
443 Err(error) => ctx.retire(Err(error)),
444 }
445 }
446
447 #[instrument(name = "coord::catalog_transact")]
449 pub(crate) async fn catalog_transact(
450 &mut self,
451 session: Option<&Session>,
452 ops: Vec<catalog::Op>,
453 ) -> Result<(), AdapterError> {
454 if session.is_some_and(|session| session.transaction().is_ddl()) {
455 return Err(AdapterError::Unsupported(
456 "raw operations in a DDL command transaction",
457 ));
458 }
459 let start = Instant::now();
460 let result = self
461 .catalog_transact_with_context(session.map(|session| session.conn_id()), None, ops)
462 .await;
463 self.metrics
464 .catalog_transact_seconds
465 .with_label_values(&["catalog_transact"])
466 .observe(start.elapsed().as_secs_f64());
467 result
468 }
469
470 #[instrument(name = "coord::catalog_transact_with_side_effects")]
479 pub(crate) async fn catalog_transact_with_side_effects<F>(
480 &mut self,
481 mut ctx: Option<&mut ExecuteContext>,
482 ops: Vec<catalog::Op>,
483 side_effect: F,
484 ) -> Result<(), AdapterError>
485 where
486 F: for<'a> FnOnce(
487 &'a mut Coordinator,
488 Option<&'a mut ExecuteContext>,
489 ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
490 + 'static,
491 {
492 if ctx
493 .as_ref()
494 .is_some_and(|ctx| ctx.session().transaction().is_ddl())
495 {
496 return Err(AdapterError::Unsupported(
497 "raw operations in a DDL command transaction",
498 ));
499 }
500 let start = Instant::now();
501
502 let (table_updates, catalog_updates) = Box::pin(
505 self.catalog_transact_inner(ctx.as_ref().map(|ctx| ctx.session().conn_id()), ops),
506 )
507 .await?;
508
509 let apply_implications_res =
512 Box::pin(self.apply_catalog_implications(ctx.as_deref_mut(), catalog_updates)).await;
513
514 apply_implications_res.expect("cannot fail to apply catalog update implications");
518
519 let consistency_start = Instant::now();
526
527 mz_ore::soft_assert_eq_no_log!(
530 self.check_consistency(),
531 Ok(()),
532 "coordinator inconsistency detected"
533 );
534
535 self.metrics
536 .catalog_transact_phase_seconds
537 .with_label_values(&["consistency_check"])
538 .observe(consistency_start.elapsed().as_secs_f64());
539
540 let side_effects_seconds = self
541 .metrics
542 .catalog_transact_phase_seconds
543 .with_label_values(&["side_effects"]);
544 let table_updates_wait = self
549 .metrics
550 .catalog_transact_phase_seconds
551 .with_label_values(&["table_updates_residual_wait"]);
552 let side_effects_fut = side_effect(self, ctx);
553
554 let ((), ()) = futures::future::join(
556 side_effects_fut
557 .wall_time()
558 .observe(side_effects_seconds)
559 .instrument(info_span!(
560 "coord::catalog_transact_with_side_effects::side_effects_fut"
561 )),
562 table_updates
563 .wall_time()
564 .observe(table_updates_wait)
565 .instrument(info_span!(
566 "coord::catalog_transact_with_side_effects::table_updates"
567 )),
568 )
569 .await;
570
571 self.metrics
572 .catalog_transact_seconds
573 .with_label_values(&["catalog_transact_with_side_effects"])
574 .observe(start.elapsed().as_secs_f64());
575
576 Ok(())
577 }
578
579 #[instrument(name = "coord::catalog_transact_with_context")]
587 pub(crate) async fn catalog_transact_with_context(
588 &mut self,
589 conn_id: Option<&ConnectionId>,
590 ctx: Option<&mut ExecuteContext>,
591 ops: Vec<catalog::Op>,
592 ) -> Result<(), AdapterError> {
593 if ctx
594 .as_ref()
595 .is_some_and(|ctx| ctx.session().transaction().is_ddl())
596 {
597 return Err(AdapterError::Unsupported(
598 "raw operations in a DDL command transaction",
599 ));
600 }
601 let start = Instant::now();
602
603 let conn_id = conn_id.or_else(|| ctx.as_ref().map(|ctx| ctx.session().conn_id()));
604
605 let (table_updates, catalog_updates) =
606 Box::pin(self.catalog_transact_inner(conn_id, ops)).await?;
607
608 self.finish_catalog_transaction(ctx, table_updates, catalog_updates)
609 .await;
610
611 self.metrics
612 .catalog_transact_seconds
613 .with_label_values(&["catalog_transact_with_context"])
614 .observe(start.elapsed().as_secs_f64());
615
616 Ok(())
617 }
618
619 pub(super) fn release_ddl_lock(&self, guard: tokio::sync::OwnedMutexGuard<()>) {
620 drop(guard);
621 if !self.serialized_ddl.is_empty() {
622 let _ = self.internal_cmd_tx.send(Message::DeferredStatementReady);
623 }
624 }
625
626 pub(super) async fn sequence_ddl_commit(&mut self, mut pending: PendingDdlCommit) {
627 let guard = self
630 .active_conns
631 .get_mut(pending.conn_id())
632 .expect("connection must exist")
633 .deferred_lock
634 .take();
635 let guard = match guard {
636 Some(guard) => guard,
637 None => match self.serialized_ddl.try_lock_owned() {
638 Ok(guard) => guard,
639 Err(_) => {
640 tracing::debug!(
641 conn_id = %pending.conn_id(),
642 "deferring catalog commit for DDL lock"
643 );
644 self.serialized_ddl.push_back(DeferredDdl::Commit(pending));
645 return;
646 }
647 },
648 };
649 let result = loop {
650 match self
651 .catalog_transact_commands(
652 pending.context.context_mut(),
653 &mut pending.commands,
654 &mut pending.responses,
655 )
656 .await
657 {
658 Ok(()) => break Ok(()),
659 Err(catalog::CommandExecutionError::Error(error)) => break Err(error),
660 Err(catalog::CommandExecutionError::Pending { index, work }) => match work {
661 catalog::CommandWork::Allocate(keys) => {
662 let ids = match self.allocate_user_ids(u64::cast_from(keys.len())).await {
663 Ok(ids) => ids,
664 Err(error) => break Err(error),
665 };
666 match &mut pending.commands[index] {
667 catalog::command::CatalogCommand::Source(command) => {
668 Arc::make_mut(command).record_ids(keys, ids);
669 }
670 catalog::command::CatalogCommand::Rename(_)
671 | catalog::command::CatalogCommand::CatalogResult(_)
672 | catalog::command::CatalogCommand::Description(_) => {
673 unreachable!("renames do not allocate IDs");
674 }
675 }
676 }
677 catalog::CommandWork::Prepare(request) => {
678 self.release_ddl_lock(guard);
679 let conn_id = pending.context.context_mut().session().conn_id().clone();
680 let config = self.controller.storage.config().clone();
681 let tx = self.internal_cmd_tx.clone();
682 let otel_ctx = OpenTelemetryContext::obtain();
683 task::spawn(|| "ddl_command_preparation", async move {
684 let response = request.execute(&config).await;
685 tracing::debug!(
686 target: "mz_adapter::catalog_preparation",
687 %conn_id, kind = "purification", "catalog preparation complete"
688 );
689 catalog_preparation_test_pause("catalog_purification_ready").await;
690 pending.responses.insert(index, response);
691 if let Err(error) =
692 tx.send(Message::DdlCommandsReady { pending, otel_ctx })
693 {
694 warn!(
695 "internal_cmd_rx dropped before DDL preparation completed: {error:?}"
696 );
697 }
698 }.instrument(tracing::Span::current()));
699 return;
700 }
701 },
702 }
703 };
704 self.release_ddl_lock(guard);
705 self.finish_ddl_commit(pending, result);
706 }
707
708 pub(super) fn finish_ddl_commit(
709 &self,
710 mut pending: PendingDdlCommit,
711 result: Result<(), AdapterError>,
712 ) {
713 if result.is_ok() && matches!(pending.context, DdlCommitContext::Statement { .. }) {
714 for command in &pending.commands {
715 self.emit_command_notices(pending.context.context_mut().session(), command);
716 }
717 }
718 pending.context.finish(result);
719 }
720
721 pub(crate) async fn catalog_transact_commands(
722 &mut self,
723 ctx: &mut ExecuteContext,
724 commands: &mut [catalog::command::CatalogCommand],
725 responses: &mut BTreeMap<usize, mz_sql::pure::SourcePreparationResponse>,
726 ) -> Result<(), catalog::CommandExecutionError> {
727 if self.controller.read_only() {
728 return Err(AdapterError::ReadOnly.into());
729 }
730 let start = Instant::now();
731 let phase_seconds = self.metrics.catalog_transact_phase_seconds.clone();
732 let oracle_write_ts = self
733 .get_catalog_write_ts()
734 .wall_time()
735 .observe(phase_seconds.with_label_values(&["write_ts"]))
736 .await;
737 let conn_id = ctx.session().conn_id();
738 let Coordinator {
739 catalog,
740 active_conns,
741 controller,
742 ..
743 } = self;
744 let conn = active_conns.get(conn_id).expect("connection must exist");
745 let TransactionResult {
746 builtin_table_updates,
747 catalog_updates,
748 audit_events,
749 } = Box::pin(Arc::make_mut(catalog).transact_commands(
750 Some(&mut controller.storage_collections),
751 oracle_write_ts,
752 Some(conn),
753 commands,
754 responses,
755 ))
756 .wall_time()
757 .observe(phase_seconds.with_label_values(&["transact"]))
758 .await?;
759
760 self.track_catalog_audit_events(Some(conn_id), audit_events);
761 let stage_start = Instant::now();
762 let table_updates = self.builtin_table_update().execute(builtin_table_updates);
763 phase_seconds
764 .with_label_values(&["stage_builtin"])
765 .observe(stage_start.elapsed().as_secs_f64());
766 self.finish_catalog_transaction(Some(ctx), table_updates, catalog_updates)
767 .await;
768 self.metrics
769 .catalog_transact_seconds
770 .with_label_values(&["catalog_transact_with_context"])
771 .observe(start.elapsed().as_secs_f64());
772 Ok(())
773 }
774
775 async fn finish_catalog_transaction(
776 &mut self,
777 ctx: Option<&mut ExecuteContext>,
778 table_updates: BuiltinTableAppendNotify,
779 catalog_updates: Vec<ParsedStateUpdate>,
780 ) {
781 let table_updates_wait = self
782 .metrics
783 .catalog_transact_phase_seconds
784 .with_label_values(&["table_updates_wait"]);
785 let apply_catalog_implications_fut =
786 Box::pin(self.apply_catalog_implications(ctx, catalog_updates));
787
788 let (combined_apply_res, ()) = futures::future::join(
790 apply_catalog_implications_fut.instrument(info_span!(
791 "coord::catalog_transact_with_context::side_effects_fut"
792 )),
793 table_updates
794 .wall_time()
795 .observe(table_updates_wait)
796 .instrument(info_span!(
797 "coord::catalog_transact_with_context::table_updates"
798 )),
799 )
800 .await;
801
802 combined_apply_res.expect("cannot fail to apply catalog implications");
806
807 let consistency_start = Instant::now();
810
811 mz_ore::soft_assert_eq_no_log!(
814 self.check_consistency(),
815 Ok(()),
816 "coordinator inconsistency detected"
817 );
818
819 self.metrics
820 .catalog_transact_phase_seconds
821 .with_label_values(&["consistency_check"])
822 .observe(consistency_start.elapsed().as_secs_f64());
823 }
824
825 #[instrument(name = "coord::catalog_transact_inner")]
829 pub(crate) async fn catalog_transact_inner(
830 &mut self,
831 conn_id: Option<&ConnectionId>,
832 mut ops: Vec<catalog::Op>,
833 ) -> Result<(BuiltinTableAppendNotify, Vec<ParsedStateUpdate>), AdapterError> {
834 if self.controller.read_only() {
835 return Err(AdapterError::ReadOnly);
836 }
837
838 if let Some(scoped_op) = self.scoped_overrides_create_op(&ops) {
839 ops.push(scoped_op);
840 }
841
842 event!(Level::TRACE, ops = format!("{:?}", ops));
843
844 let phase_seconds = self.metrics.catalog_transact_phase_seconds.clone();
845 let phase_start = Instant::now();
846
847 let mut clusters_to_drop = vec![];
848 let mut cluster_replicas_to_drop = vec![];
849 let mut clusters_to_create = vec![];
850 let mut cluster_replicas_to_create = vec![];
851 let mut update_metrics_config = false;
852 let mut update_tracing_config = false;
853 let mut update_controller_config = false;
854 let mut update_compute_config = false;
855 let mut update_storage_config = false;
856 let mut update_timestamp_oracle_config = false;
857 let mut update_metrics_retention = false;
858 let mut update_secrets_caching_config = false;
859 let mut update_cluster_scheduling_config = false;
860 let mut update_http_config = false;
861 let mut update_advance_timelines_interval = false;
862 let mut update_optimizer_e2e_latency_warning_threshold = false;
863 let mut reconcile_metric_sinks = false;
864
865 for op in &ops {
866 match op {
867 catalog::Op::DropObjects(drop_object_infos) => {
868 for drop_object_info in drop_object_infos {
869 match &drop_object_info {
870 catalog::DropObjectInfo::Item(_) => {
871 }
874 catalog::DropObjectInfo::Cluster(id) => {
875 clusters_to_drop.push(*id);
876 }
877 catalog::DropObjectInfo::ClusterReplica((
878 cluster_id,
879 replica_id,
880 _reason,
881 )) => {
882 cluster_replicas_to_drop.push((*cluster_id, *replica_id));
884 }
885 _ => (),
886 }
887 }
888 }
889 catalog::Op::ResetSystemConfiguration { name }
890 | catalog::Op::UpdateSystemConfiguration { name, .. } => {
891 update_metrics_config |= self
892 .catalog
893 .state()
894 .system_config()
895 .is_metrics_config_var(name);
896 update_tracing_config |= vars::is_tracing_var(name);
897 update_controller_config |= self
898 .catalog
899 .state()
900 .system_config()
901 .is_controller_config_var(name);
902 update_compute_config |= self
903 .catalog
904 .state()
905 .system_config()
906 .is_compute_config_var(name);
907 update_storage_config |= self
908 .catalog
909 .state()
910 .system_config()
911 .is_storage_config_var(name);
912 update_timestamp_oracle_config |= vars::is_timestamp_oracle_config_var(name);
913 update_metrics_retention |= name == vars::METRICS_RETENTION.name();
914 update_secrets_caching_config |= vars::is_secrets_caching_var(name);
915 update_cluster_scheduling_config |= vars::is_cluster_scheduling_var(name);
916 update_http_config |= vars::is_http_config_var(name);
917 update_advance_timelines_interval |= name == DEFAULT_TIMESTAMP_INTERVAL.name();
918 update_optimizer_e2e_latency_warning_threshold |=
919 name == vars::OPTIMIZER_E2E_LATENCY_WARNING_THRESHOLD.name();
920 reconcile_metric_sinks |= name == vars::DISABLED_METRIC_SINKS.name();
921 }
922 catalog::Op::ResetAllSystemConfiguration => {
923 update_tracing_config = true;
927 update_controller_config = true;
928 update_compute_config = true;
929 update_storage_config = true;
930 update_timestamp_oracle_config = true;
931 update_metrics_retention = true;
932 update_secrets_caching_config = true;
933 update_cluster_scheduling_config = true;
934 update_metrics_config = true;
935 update_http_config = true;
936 update_advance_timelines_interval = true;
937 update_optimizer_e2e_latency_warning_threshold = true;
938 reconcile_metric_sinks = true;
939 }
940 catalog::Op::CreateCluster { id, .. } => {
941 clusters_to_create.push(*id);
942 }
943 catalog::Op::CreateClusterReplica {
944 cluster_id,
945 name,
946 config,
947 ..
948 } => {
949 cluster_replicas_to_create.push((
950 *cluster_id,
951 name.clone(),
952 config.location.num_processes(),
953 ));
954 }
955 _ => (),
956 }
957 }
958
959 let validate_res = self
962 .catalog()
963 .state()
964 .validate_resource_limits(&ops, conn_id.unwrap_or(&SYSTEM_CONN_ID));
965 phase_seconds
966 .with_label_values(&["prep"])
967 .observe(phase_start.elapsed().as_secs_f64());
968 validate_res?;
969
970 let oracle_write_ts = self
979 .get_catalog_write_ts()
980 .wall_time()
981 .observe(phase_seconds.with_label_values(&["write_ts"]))
982 .await;
983
984 let Coordinator {
985 catalog,
986 active_conns,
987 controller,
988 cluster_replica_statuses,
989 ..
990 } = self;
991 let catalog = Arc::make_mut(catalog);
992 let conn = conn_id.map(|id| active_conns.get(id).expect("connection must exist"));
993
994 if let Some(conn) = conn {
997 let creates_temp_item = ops.iter().any(
998 |op| matches!(op, catalog::Op::CreateItem { item, .. } if item.is_temporary()),
999 );
1000 if creates_temp_item && !catalog.state().has_temporary_namespace(conn.conn_id()) {
1001 catalog.register_temporary_namespace(conn.conn_id(), conn.uuid());
1002 }
1003 }
1004
1005 let TransactionResult {
1014 builtin_table_updates,
1015 catalog_updates,
1016 audit_events,
1017 } = catalog
1018 .transact(
1019 Some(&mut controller.storage_collections),
1020 oracle_write_ts,
1021 conn,
1022 ops,
1023 )
1024 .wall_time()
1025 .observe(phase_seconds.with_label_values(&["transact"]))
1026 .await?;
1027
1028 for (cluster_id, replica_id) in &cluster_replicas_to_drop {
1029 cluster_replica_statuses.remove_cluster_replica_statuses(cluster_id, replica_id);
1030 }
1031 for cluster_id in &clusters_to_drop {
1032 cluster_replica_statuses.remove_cluster_statuses(cluster_id);
1033 }
1034 for cluster_id in clusters_to_create {
1035 cluster_replica_statuses.initialize_cluster_statuses(cluster_id);
1036 }
1037 let now = to_datetime((catalog.config().now)());
1038 for (cluster_id, replica_name, num_processes) in cluster_replicas_to_create {
1039 let replica_id = catalog
1040 .resolve_replica_in_cluster(&cluster_id, &replica_name)
1041 .expect("just created")
1042 .replica_id();
1043 cluster_replica_statuses.initialize_cluster_replica_statuses(
1044 cluster_id,
1045 replica_id,
1046 num_processes,
1047 now,
1048 );
1049 }
1050
1051 let stage_start = Instant::now();
1054 let builtin_update_notify = self.builtin_table_update().execute(builtin_table_updates);
1055 phase_seconds
1056 .with_label_values(&["stage_builtin"])
1057 .observe(stage_start.elapsed().as_secs_f64());
1058
1059 let finalize_start = Instant::now();
1060
1061 let _: () = async {
1064 if update_metrics_config {
1065 mz_metrics::update_dyncfg(&self.catalog().system_config().dyncfg_updates());
1066 }
1067 if update_controller_config {
1068 self.update_controller_config();
1069 }
1070 if update_compute_config {
1071 self.update_compute_config();
1072 }
1073 if update_storage_config {
1074 self.update_storage_config();
1075 }
1076 if update_timestamp_oracle_config {
1077 self.update_timestamp_oracle_config();
1078 }
1079 if update_metrics_retention {
1080 self.update_metrics_retention();
1081 }
1082 if update_tracing_config {
1083 self.update_tracing_config();
1084 }
1085 if update_secrets_caching_config {
1086 self.update_secrets_caching_config();
1087 }
1088 if update_cluster_scheduling_config {
1089 self.update_cluster_scheduling_config();
1090 }
1091 if update_http_config {
1092 self.update_http_config();
1093 }
1094 if update_advance_timelines_interval {
1095 let new_interval = self.catalog().system_config().default_timestamp_interval();
1096 if new_interval != self.advance_timelines_interval.period() {
1097 self.advance_timelines_interval = tokio::time::interval(new_interval);
1098 }
1099 }
1100 if update_optimizer_e2e_latency_warning_threshold {
1101 let threshold = self
1102 .catalog()
1103 .system_config()
1104 .optimizer_e2e_latency_warning_threshold();
1105 self.optimizer_metrics
1106 .set_e2e_optimization_time_log_threshold(threshold);
1107 }
1108 if reconcile_metric_sinks {
1109 self.reconcile_metric_sinks().await;
1110 }
1111 }
1112 .instrument(info_span!("coord::catalog_transact_with::finalize"))
1113 .await;
1114
1115 self.track_catalog_audit_events(conn_id, audit_events);
1116
1117 phase_seconds
1118 .with_label_values(&["finalize"])
1119 .observe(finalize_start.elapsed().as_secs_f64());
1120
1121 Ok((builtin_update_notify, catalog_updates))
1122 }
1123
1124 fn track_catalog_audit_events(
1125 &self,
1126 conn_id: Option<&ConnectionId>,
1127 audit_events: Vec<VersionedEvent>,
1128 ) {
1129 let conn = conn_id.and_then(|id| self.active_conns.get(id));
1130 if let Some(segment_client) = &self.segment_client {
1131 for VersionedEvent::V1(event) in audit_events {
1132 let event_type = format!(
1133 "{} {}",
1134 event.object_type.as_title_case(),
1135 event.event_type.as_title_case()
1136 );
1137 segment_client.environment_track(
1138 &self.catalog().config().environment_id,
1139 event_type,
1140 json!({ "details": event.details.as_json() }),
1141 EventDetails {
1142 user_id: conn
1143 .and_then(|c| c.user().external_metadata.as_ref())
1144 .map(|m| m.user_id),
1145 application_name: conn.map(|c| c.application_name()),
1146 ..Default::default()
1147 },
1148 );
1149 }
1150 }
1151 }
1152
1153 pub(crate) fn drop_replica(&mut self, cluster_id: ClusterId, replica_id: ReplicaId) {
1154 self.drop_introspection_subscribes(replica_id);
1155 self.drop_metric_sinks(replica_id);
1156
1157 self.controller
1158 .drop_replica(cluster_id, replica_id)
1159 .expect("dropping replica must not fail");
1160 }
1161
1162 pub(crate) fn drop_sources(&mut self, sources: Vec<(CatalogItemId, GlobalId)>) {
1164 for (item_id, _gid) in &sources {
1165 self.active_webhooks.remove(item_id);
1166 }
1167 let storage_metadata = self.catalog.state().storage_metadata();
1168 let source_gids = sources.into_iter().map(|(_id, gid)| gid).collect();
1169 self.controller
1170 .storage
1171 .drop_sources(storage_metadata, source_gids)
1172 .unwrap_or_terminate("cannot fail to drop sources");
1173 }
1174
1175 pub(crate) async fn drop_tables(&mut self, tables: Vec<(CatalogItemId, GlobalId)>) {
1177 for (item_id, _gid) in &tables {
1178 self.active_webhooks.remove(item_id);
1179 }
1180
1181 let table_gids: Vec<_> = tables.into_iter().map(|(_id, gid)| gid).collect();
1182
1183 let forget_ids = self
1185 .controller
1186 .storage
1187 .txns_table_ids(table_gids.clone())
1188 .unwrap_or_terminate("cannot fail to look up txns-registered tables");
1189 if !forget_ids.is_empty() {
1190 self.forget_tables_via_committer(forget_ids).await;
1191 }
1192
1193 let storage_metadata = self.catalog.state().storage_metadata();
1194 self.controller
1195 .storage
1196 .drop_tables(storage_metadata, table_gids)
1197 .unwrap_or_terminate("cannot fail to drop tables");
1198 }
1199
1200 #[must_use]
1206 pub async fn drop_compute_sink(
1207 &mut self,
1208 sink_id: GlobalId,
1209 ) -> Option<(ActiveComputeSink, BuiltinTableAppendNotify)> {
1210 self.drop_compute_sinks([sink_id]).await.remove(&sink_id)
1211 }
1212
1213 #[must_use]
1224 pub async fn drop_compute_sinks(
1225 &mut self,
1226 sink_ids: impl IntoIterator<Item = GlobalId>,
1227 ) -> BTreeMap<GlobalId, (ActiveComputeSink, BuiltinTableAppendNotify)> {
1228 let mut by_id = BTreeMap::new();
1229 let mut by_cluster: BTreeMap<_, Vec<_>> = BTreeMap::new();
1230 for sink_id in sink_ids {
1231 let (sink, write_notify) = match self.remove_active_compute_sink(sink_id).await {
1232 None => {
1233 tracing::debug!(%sink_id, "drop_compute_sinks: sink already removed");
1238 continue;
1239 }
1240 Some(entry) => entry,
1241 };
1242
1243 by_cluster
1244 .entry(sink.cluster_id())
1245 .or_default()
1246 .push(sink_id);
1247 by_id.insert(sink_id, (sink, write_notify));
1248 }
1249 for (cluster_id, ids) in by_cluster {
1250 let compute = &mut self.controller.compute;
1251 if compute.instance_exists(cluster_id) {
1253 compute
1254 .drop_collections(cluster_id, ids)
1255 .unwrap_or_terminate("cannot fail to drop collections");
1256 }
1257 }
1258 by_id
1259 }
1260
1261 pub async fn retire_compute_sinks(
1267 &mut self,
1268 mut reasons: BTreeMap<GlobalId, ActiveComputeSinkRetireReason>,
1269 ) -> BuiltinTableAppendCompletion {
1270 let sink_ids = reasons.keys().cloned();
1271 let to_retire: Vec<_> = self
1272 .drop_compute_sinks(sink_ids)
1273 .await
1274 .into_iter()
1275 .map(|(id, (sink, write_notify))| {
1276 let reason = reasons
1277 .remove(&id)
1278 .expect("all returned IDs are in `reasons`");
1279 (sink, write_notify, reason)
1280 })
1281 .collect();
1282
1283 if to_retire.is_empty() {
1284 return BuiltinTableAppendCompletion::completed();
1285 }
1286
1287 let (done_tx, done_rx) = tokio::sync::oneshot::channel();
1293 task::spawn(|| "retire_compute_sinks", async move {
1294 for (sink, write_notify, reason) in to_retire {
1295 write_notify.await;
1296 sink.retire(reason);
1297 }
1298 let _ = done_tx.send(());
1299 });
1300 BuiltinTableAppendCompletion::new(Box::pin(async move {
1301 let _ = done_rx.await;
1302 }))
1303 }
1304
1305 #[mz_ore::instrument(level = "debug")]
1307 pub(crate) async fn cancel_compute_sinks_for_conn(
1308 &mut self,
1309 conn_id: &ConnectionId,
1310 ) -> BuiltinTableAppendCompletion {
1311 self.retire_compute_sinks_for_conn(conn_id, ActiveComputeSinkRetireReason::Canceled)
1312 .await
1313 }
1314
1315 #[mz_ore::instrument(level = "debug")]
1318 pub(crate) async fn retire_compute_sinks_for_conn(
1319 &mut self,
1320 conn_id: &ConnectionId,
1321 reason: ActiveComputeSinkRetireReason,
1322 ) -> BuiltinTableAppendCompletion {
1323 let drop_sinks = self
1324 .active_conns
1325 .get_mut(conn_id)
1326 .expect("must exist for active session")
1327 .drop_sinks
1328 .iter()
1329 .map(|sink_id| (*sink_id, reason.clone()))
1330 .collect();
1331 self.retire_compute_sinks(drop_sinks).await
1332 }
1333
1334 pub(crate) fn drop_storage_sinks(&mut self, sink_gids: Vec<GlobalId>) {
1335 let storage_metadata = self.catalog.state().storage_metadata();
1336 self.controller
1337 .storage
1338 .drop_sinks(storage_metadata, sink_gids)
1339 .unwrap_or_terminate("cannot fail to drop sinks");
1340 }
1341
1342 pub(crate) fn drop_compute_collections(&mut self, collections: Vec<(ClusterId, GlobalId)>) {
1343 let mut by_cluster: BTreeMap<_, Vec<_>> = BTreeMap::new();
1344 for (cluster_id, gid) in collections {
1345 by_cluster.entry(cluster_id).or_default().push(gid);
1346 }
1347 for (cluster_id, gids) in by_cluster {
1348 let compute = &mut self.controller.compute;
1349 if compute.instance_exists(cluster_id) {
1351 compute
1352 .drop_collections(cluster_id, gids)
1353 .unwrap_or_terminate("cannot fail to drop collections");
1354 }
1355 }
1356 }
1357
1358 pub(crate) fn drop_vpc_endpoints_in_background(&self, vpc_endpoints: Vec<CatalogItemId>) {
1359 let Some(cloud_resource_controller) = self.cloud_resource_controller.as_ref() else {
1363 warn!("dropping VPC endpoints without cloud_resource_controller; skipping cleanup");
1364 return;
1365 };
1366 let cloud_resource_controller = Arc::clone(cloud_resource_controller);
1367 task::spawn(
1375 || "drop_vpc_endpoints",
1376 async move {
1377 for vpc_endpoint in vpc_endpoints {
1378 let _ = Retry::default()
1379 .max_duration(Duration::from_secs(60))
1380 .retry_async(|_state| async {
1381 fail_point!("drop_vpc_endpoint", |r| {
1382 Err(anyhow::anyhow!("Fail point error {:?}", r))
1383 });
1384 match cloud_resource_controller
1385 .delete_vpc_endpoint(vpc_endpoint)
1386 .await
1387 {
1388 Ok(_) => Ok(()),
1389 Err(e) => {
1390 warn!("Dropping VPC Endpoints has encountered an error: {}", e);
1391 Err(e)
1392 }
1393 }
1394 })
1395 .await;
1396 }
1397 }
1398 .instrument(info_span!(
1399 "coord::catalog_transact_inner::drop_vpc_endpoints"
1400 )),
1401 );
1402 }
1403
1404 pub(crate) async fn drop_temp_items(&mut self, conn_id: &ConnectionId) {
1407 let temp_items = self.catalog().state().get_temp_items(conn_id).collect();
1408 let all_items = self.catalog().object_dependents(&temp_items, conn_id);
1409
1410 if all_items.is_empty() {
1411 return;
1412 }
1413 let op = Op::DropObjects(
1414 all_items
1415 .into_iter()
1416 .map(DropObjectInfo::manual_drop_from_object_id)
1417 .collect(),
1418 );
1419
1420 self.catalog_transact_with_context(Some(conn_id), None, vec![op])
1421 .await
1422 .expect("unable to drop temporary items for conn_id");
1423 }
1424
1425 fn update_cluster_scheduling_config(&self) {
1426 let config = flags::orchestrator_scheduling_config(self.catalog.system_config());
1427 self.controller
1428 .update_orchestrator_scheduling_config(config);
1429 }
1430
1431 fn update_secrets_caching_config(&self) {
1432 let config = flags::caching_config(self.catalog.system_config());
1433 self.caching_secrets_reader.set_policy(config);
1434 }
1435
1436 fn update_tracing_config(&self) {
1437 let tracing = flags::tracing_config(self.catalog().system_config());
1438 tracing.apply(&self.tracing_handle);
1439 }
1440
1441 fn update_compute_config(&mut self) {
1442 let config_params = flags::compute_config(self.catalog().system_config());
1443 self.controller.compute.update_configuration(config_params);
1444 }
1445
1446 fn update_storage_config(&mut self) {
1447 let config_params = flags::storage_config(self.catalog().system_config());
1448 self.controller.storage.update_parameters(config_params);
1449 }
1450
1451 fn update_timestamp_oracle_config(&self) {
1452 let config_params = flags::timestamp_oracle_config(self.catalog().system_config());
1453 if let Some(config) = self.timestamp_oracle_config.as_ref() {
1454 config.apply_parameters(config_params)
1455 }
1456 }
1457
1458 fn update_metrics_retention(&self) {
1459 let duration = self.catalog().system_config().metrics_retention();
1460 let policy = ReadPolicy::lag_writes_by(
1461 Timestamp::new(u64::try_from(duration.as_millis()).unwrap_or_else(|_e| {
1462 tracing::error!("Absurd metrics retention duration: {duration:?}.");
1463 u64::MAX
1464 })),
1465 SINCE_GRANULARITY,
1466 );
1467 let storage_policies = self
1468 .catalog()
1469 .entries()
1470 .filter(|entry| {
1471 entry.item().is_retained_metrics_object()
1472 && entry.item().is_compute_object_on_cluster().is_none()
1473 })
1474 .map(|entry| (entry.id(), policy.clone()))
1475 .collect::<Vec<_>>();
1476 let compute_policies = self
1477 .catalog()
1478 .entries()
1479 .filter_map(|entry| {
1480 if let (true, Some(cluster_id)) = (
1481 entry.item().is_retained_metrics_object(),
1482 entry.item().is_compute_object_on_cluster(),
1483 ) {
1484 Some((cluster_id, entry.id(), policy.clone()))
1485 } else {
1486 None
1487 }
1488 })
1489 .collect::<Vec<_>>();
1490 self.update_storage_read_policies(storage_policies);
1491 self.update_compute_read_policies(compute_policies);
1492 }
1493
1494 fn update_controller_config(&mut self) {
1495 let sys_config = self.catalog().system_config();
1496 self.controller
1497 .update_configuration(sys_config.dyncfg_updates());
1498 }
1499
1500 fn update_http_config(&mut self) {
1501 let webhook_request_limit = self
1502 .catalog()
1503 .system_config()
1504 .webhook_concurrent_request_limit();
1505 self.webhook_concurrency_limit
1506 .set_limit(webhook_request_limit);
1507 }
1508
1509 pub(crate) async fn create_storage_export(
1510 &mut self,
1511 id: GlobalId,
1512 sink: &Sink,
1513 ) -> Result<(), AdapterError> {
1514 self.controller.storage.check_exists(sink.from)?;
1516
1517 let id_bundle = crate::CollectionIdBundle {
1524 storage_ids: btreeset! {sink.from},
1525 compute_ids: btreemap! {},
1526 };
1527
1528 let read_holds = self.acquire_read_holds(&id_bundle);
1536 let as_of = read_holds.least_valid_read();
1537
1538 let storage_sink_from_entry = self.catalog().get_entry_by_global_id(&sink.from);
1539 let storage_sink_desc = mz_storage_types::sinks::StorageSinkDesc {
1540 from: sink.from,
1541 from_desc: storage_sink_from_entry
1542 .relation_desc()
1543 .expect("sinks can only be built on items with descs")
1544 .into_owned(),
1545 connection: sink
1546 .connection
1547 .clone()
1548 .into_inline_connection(self.catalog().state()),
1549 envelope: sink.envelope,
1550 as_of,
1551 with_snapshot: sink.with_snapshot,
1552 version: sink.version,
1553 from_storage_metadata: (),
1554 to_storage_metadata: (),
1555 commit_interval: sink.commit_interval,
1556 };
1557
1558 let collection_desc = CollectionDescription {
1559 desc: KAFKA_PROGRESS_DESC.clone(),
1561 data_source: DataSource::Sink {
1562 desc: ExportDescription {
1563 sink: storage_sink_desc,
1564 instance_id: sink.cluster_id,
1565 },
1566 },
1567 since: None,
1568 timeline: None,
1569 primary: None,
1570 };
1571 let collections = vec![(id, collection_desc)];
1572
1573 let storage_metadata = self.catalog.state().storage_metadata();
1575 let res = self
1576 .controller
1577 .storage
1578 .create_collections(storage_metadata, None, collections)
1579 .await;
1580
1581 drop(read_holds);
1584
1585 Ok(res?)
1586 }
1587
1588 pub(crate) fn validate_resource_limit<F>(
1589 &self,
1590 current_amount: usize,
1591 new_instances: i64,
1592 resource_limit: F,
1593 resource_type: &str,
1594 limit_name: &str,
1595 ) -> Result<(), AdapterError>
1596 where
1597 F: Fn(&SystemVars) -> u32,
1598 {
1599 self.catalog().state().validate_resource_limit(
1600 current_amount,
1601 new_instances,
1602 resource_limit,
1603 resource_type,
1604 limit_name,
1605 )
1606 }
1607
1608 pub(crate) fn validate_resource_limit_numeric<F>(
1609 &self,
1610 current_amount: Numeric,
1611 new_amount: Numeric,
1612 resource_limit: F,
1613 resource_type: &str,
1614 limit_name: &str,
1615 ) -> Result<(), AdapterError>
1616 where
1617 F: Fn(&SystemVars) -> Numeric,
1618 {
1619 self.catalog().state().validate_resource_limit_numeric(
1620 current_amount,
1621 new_amount,
1622 resource_limit,
1623 resource_type,
1624 limit_name,
1625 )
1626 }
1627}