1use std::collections::{BTreeMap, BTreeSet};
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::{CatalogItem, DataSourceDesc, Sink};
25use mz_cluster_client::ReplicaId;
26use mz_controller::clusters::ReplicaLocation;
27use mz_controller_types::ClusterId;
28use mz_ore::instrument;
29use mz_ore::now::to_datetime;
30use mz_ore::retry::Retry;
31use mz_ore::task;
32use mz_repr::adt::numeric::Numeric;
33use mz_repr::{CatalogItemId, GlobalId, Timestamp};
34use mz_sql::catalog::{CatalogClusterReplica, CatalogSchema};
35use mz_sql::names::ResolvedDatabaseSpecifier;
36use mz_sql::plan::ConnectionDetails;
37use mz_sql::session::metadata::SessionMetadata;
38use mz_sql::session::vars::{
39 self, DEFAULT_TIMESTAMP_INTERVAL, MAX_AWS_PRIVATELINK_CONNECTIONS, MAX_CLUSTERS,
40 MAX_CREDIT_CONSUMPTION_RATE, MAX_DATABASES, MAX_KAFKA_CONNECTIONS, MAX_MATERIALIZED_VIEWS,
41 MAX_MYSQL_CONNECTIONS, MAX_NETWORK_POLICIES, MAX_OBJECTS_PER_SCHEMA, MAX_POSTGRES_CONNECTIONS,
42 MAX_REPLICAS_PER_CLUSTER, MAX_ROLES, MAX_SCHEMAS_PER_DATABASE, MAX_SECRETS, MAX_SINKS,
43 MAX_SOURCES, MAX_SQL_SERVER_CONNECTIONS, MAX_TABLES, SystemVars, Var,
44};
45use mz_storage_client::controller::{CollectionDescription, DataSource, ExportDescription};
46use mz_storage_types::connections::inline::IntoInlineConnection;
47use mz_storage_types::read_policy::ReadPolicy;
48use mz_storage_types::sources::kafka::KAFKA_PROGRESS_DESC;
49use serde_json::json;
50use tracing::{Instrument, Level, event, info_span, warn};
51
52use crate::active_compute_sink::{ActiveComputeSink, ActiveComputeSinkRetireReason};
53use crate::catalog::{DropObjectInfo, Op, ReplicaCreateDropReason, TransactionResult};
54use crate::coord::Coordinator;
55use crate::coord::appends::{BuiltinTableAppendCompletion, BuiltinTableAppendNotify};
56use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdate;
57use crate::session::{Session, Transaction, TransactionOps};
58use crate::telemetry::{EventDetails, SegmentClientExt};
59use crate::util::ResultExt;
60use crate::{AdapterError, ExecuteContext, catalog, flags};
61
62impl Coordinator {
63 #[instrument(name = "coord::catalog_transact")]
65 pub(crate) async fn catalog_transact(
66 &mut self,
67 session: Option<&Session>,
68 ops: Vec<catalog::Op>,
69 ) -> Result<(), AdapterError> {
70 let start = Instant::now();
71 let result = self
72 .catalog_transact_with_context(session.map(|session| session.conn_id()), None, ops)
73 .await;
74 self.metrics
75 .catalog_transact_seconds
76 .with_label_values(&["catalog_transact"])
77 .observe(start.elapsed().as_secs_f64());
78 result
79 }
80
81 #[instrument(name = "coord::catalog_transact_with_side_effects")]
90 pub(crate) async fn catalog_transact_with_side_effects<F>(
91 &mut self,
92 mut ctx: Option<&mut ExecuteContext>,
93 ops: Vec<catalog::Op>,
94 side_effect: F,
95 ) -> Result<(), AdapterError>
96 where
97 F: for<'a> FnOnce(
98 &'a mut Coordinator,
99 Option<&'a mut ExecuteContext>,
100 ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
101 + 'static,
102 {
103 let start = Instant::now();
104
105 let (table_updates, catalog_updates) = self
106 .catalog_transact_inner(ctx.as_ref().map(|ctx| ctx.session().conn_id()), ops)
107 .await?;
108
109 let apply_implications_res = self
112 .apply_catalog_implications(ctx.as_deref_mut(), catalog_updates)
113 .await;
114
115 apply_implications_res.expect("cannot fail to apply catalog update implications");
119
120 mz_ore::soft_assert_eq_no_log!(
123 self.check_consistency(),
124 Ok(()),
125 "coordinator inconsistency detected"
126 );
127
128 let side_effects_fut = side_effect(self, ctx);
129
130 let ((), ()) = futures::future::join(
132 side_effects_fut.instrument(info_span!(
133 "coord::catalog_transact_with_side_effects::side_effects_fut"
134 )),
135 table_updates.instrument(info_span!(
136 "coord::catalog_transact_with_side_effects::table_updates"
137 )),
138 )
139 .await;
140
141 self.metrics
142 .catalog_transact_seconds
143 .with_label_values(&["catalog_transact_with_side_effects"])
144 .observe(start.elapsed().as_secs_f64());
145
146 Ok(())
147 }
148
149 #[instrument(name = "coord::catalog_transact_with_context")]
157 pub(crate) async fn catalog_transact_with_context(
158 &mut self,
159 conn_id: Option<&ConnectionId>,
160 ctx: Option<&mut ExecuteContext>,
161 ops: Vec<catalog::Op>,
162 ) -> Result<(), AdapterError> {
163 let start = Instant::now();
164
165 let conn_id = conn_id.or_else(|| ctx.as_ref().map(|ctx| ctx.session().conn_id()));
166
167 let (table_updates, catalog_updates) = self.catalog_transact_inner(conn_id, ops).await?;
168
169 let apply_catalog_implications_fut = self.apply_catalog_implications(ctx, catalog_updates);
170
171 let (combined_apply_res, ()) = futures::future::join(
173 apply_catalog_implications_fut.instrument(info_span!(
174 "coord::catalog_transact_with_context::side_effects_fut"
175 )),
176 table_updates.instrument(info_span!(
177 "coord::catalog_transact_with_context::table_updates"
178 )),
179 )
180 .await;
181
182 combined_apply_res.expect("cannot fail to apply catalog implications");
186
187 mz_ore::soft_assert_eq_no_log!(
190 self.check_consistency(),
191 Ok(()),
192 "coordinator inconsistency detected"
193 );
194
195 self.metrics
196 .catalog_transact_seconds
197 .with_label_values(&["catalog_transact_with_context"])
198 .observe(start.elapsed().as_secs_f64());
199
200 Ok(())
201 }
202
203 #[instrument(name = "coord::catalog_transact_with_ddl_transaction")]
206 pub(crate) async fn catalog_transact_with_ddl_transaction<F>(
207 &mut self,
208 ctx: &mut ExecuteContext,
209 ops: Vec<catalog::Op>,
210 side_effect: F,
211 ) -> Result<(), AdapterError>
212 where
213 F: for<'a> FnOnce(
214 &'a mut Coordinator,
215 Option<&'a mut ExecuteContext>,
216 ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
217 + Send
218 + Sync
219 + 'static,
220 {
221 let start = Instant::now();
222
223 let Some(Transaction {
224 ops:
225 TransactionOps::DDL {
226 ops: txn_ops,
227 revision: txn_revision,
228 state: txn_state,
229 snapshot: txn_snapshot,
230 side_effects: _,
231 },
232 ..
233 }) = ctx.session().transaction().inner()
234 else {
235 let result = self
236 .catalog_transact_with_side_effects(Some(ctx), ops, side_effect)
237 .await;
238 self.metrics
239 .catalog_transact_seconds
240 .with_label_values(&["catalog_transact_with_ddl_transaction"])
241 .observe(start.elapsed().as_secs_f64());
242 return result;
243 };
244
245 if self.catalog().transient_revision() != *txn_revision {
247 self.metrics
248 .catalog_transact_seconds
249 .with_label_values(&["catalog_transact_with_ddl_transaction"])
250 .observe(start.elapsed().as_secs_f64());
251 return Err(AdapterError::DDLTransactionRace);
252 }
253
254 let txn_ops_clone = txn_ops.clone();
256 let txn_state_clone = txn_state.clone();
257 let prev_snapshot = txn_snapshot.clone();
258
259 let mut combined_ops = txn_ops_clone;
261 combined_ops.extend(ops.iter().cloned());
262 let conn_id = ctx.session().conn_id().clone();
263 self.validate_resource_limits(&combined_ops, &conn_id)?;
264
265 let oracle_write_ts = self.get_local_write_ts().await.timestamp;
267
268 let conn = self.active_conns.get(ctx.session().conn_id());
270
271 let (new_state, new_snapshot) = self
277 .catalog()
278 .transact_incremental_dry_run(
279 &txn_state_clone,
280 ops.clone(),
281 conn,
282 prev_snapshot,
283 oracle_write_ts,
284 )
285 .await?;
286
287 let result = ctx
289 .session_mut()
290 .transaction_mut()
291 .add_ops(TransactionOps::DDL {
292 ops: combined_ops,
293 state: new_state,
294 side_effects: vec![Box::new(side_effect)],
295 revision: self.catalog().transient_revision(),
296 snapshot: Some(new_snapshot),
297 });
298
299 self.metrics
300 .catalog_transact_seconds
301 .with_label_values(&["catalog_transact_with_ddl_transaction"])
302 .observe(start.elapsed().as_secs_f64());
303
304 result
305 }
306
307 #[instrument(name = "coord::catalog_transact_inner")]
311 pub(crate) async fn catalog_transact_inner(
312 &mut self,
313 conn_id: Option<&ConnectionId>,
314 ops: Vec<catalog::Op>,
315 ) -> Result<(BuiltinTableAppendNotify, Vec<ParsedStateUpdate>), AdapterError> {
316 if self.controller.read_only() {
317 return Err(AdapterError::ReadOnly);
318 }
319
320 event!(Level::TRACE, ops = format!("{:?}", ops));
321
322 let mut webhook_sources_to_restart = BTreeSet::new();
323 let mut clusters_to_drop = vec![];
324 let mut cluster_replicas_to_drop = vec![];
325 let mut clusters_to_create = vec![];
326 let mut cluster_replicas_to_create = vec![];
327 let mut update_metrics_config = false;
328 let mut update_tracing_config = false;
329 let mut update_controller_config = false;
330 let mut update_compute_config = false;
331 let mut update_storage_config = false;
332 let mut update_timestamp_oracle_config = false;
333 let mut update_metrics_retention = false;
334 let mut update_secrets_caching_config = false;
335 let mut update_cluster_scheduling_config = false;
336 let mut update_http_config = false;
337 let mut update_advance_timelines_interval = false;
338 let mut update_optimizer_e2e_latency_warning_threshold = false;
339
340 for op in &ops {
341 match op {
342 catalog::Op::DropObjects(drop_object_infos) => {
343 for drop_object_info in drop_object_infos {
344 match &drop_object_info {
345 catalog::DropObjectInfo::Item(_) => {
346 }
349 catalog::DropObjectInfo::Cluster(id) => {
350 clusters_to_drop.push(*id);
351 }
352 catalog::DropObjectInfo::ClusterReplica((
353 cluster_id,
354 replica_id,
355 _reason,
356 )) => {
357 cluster_replicas_to_drop.push((*cluster_id, *replica_id));
359 }
360 _ => (),
361 }
362 }
363 }
364 catalog::Op::ResetSystemConfiguration { name }
365 | catalog::Op::UpdateSystemConfiguration { name, .. } => {
366 update_metrics_config |= self
367 .catalog
368 .state()
369 .system_config()
370 .is_metrics_config_var(name);
371 update_tracing_config |= vars::is_tracing_var(name);
372 update_controller_config |= self
373 .catalog
374 .state()
375 .system_config()
376 .is_controller_config_var(name);
377 update_compute_config |= self
378 .catalog
379 .state()
380 .system_config()
381 .is_compute_config_var(name);
382 update_storage_config |= self
383 .catalog
384 .state()
385 .system_config()
386 .is_storage_config_var(name);
387 update_timestamp_oracle_config |= vars::is_timestamp_oracle_config_var(name);
388 update_metrics_retention |= name == vars::METRICS_RETENTION.name();
389 update_secrets_caching_config |= vars::is_secrets_caching_var(name);
390 update_cluster_scheduling_config |= vars::is_cluster_scheduling_var(name);
391 update_http_config |= vars::is_http_config_var(name);
392 update_advance_timelines_interval |= name == DEFAULT_TIMESTAMP_INTERVAL.name();
393 update_optimizer_e2e_latency_warning_threshold |=
394 name == vars::OPTIMIZER_E2E_LATENCY_WARNING_THRESHOLD.name();
395 }
396 catalog::Op::ResetAllSystemConfiguration => {
397 update_tracing_config = true;
401 update_controller_config = true;
402 update_compute_config = true;
403 update_storage_config = true;
404 update_timestamp_oracle_config = true;
405 update_metrics_retention = true;
406 update_secrets_caching_config = true;
407 update_cluster_scheduling_config = true;
408 update_metrics_config = true;
409 update_http_config = true;
410 update_advance_timelines_interval = true;
411 update_optimizer_e2e_latency_warning_threshold = true;
412 }
413 catalog::Op::RenameItem { id, .. } => {
414 let item = self.catalog().get_entry(id);
415 let is_webhook_source = item
416 .source()
417 .map(|s| matches!(s.data_source, DataSourceDesc::Webhook { .. }))
418 .unwrap_or(false);
419 if is_webhook_source {
420 webhook_sources_to_restart.insert(*id);
421 }
422 }
423 catalog::Op::RenameSchema {
424 database_spec,
425 schema_spec,
426 ..
427 } => {
428 let schema = self.catalog().get_schema(
429 database_spec,
430 schema_spec,
431 conn_id.unwrap_or(&SYSTEM_CONN_ID),
432 );
433 let webhook_sources = schema.item_ids().filter(|id| {
434 let item = self.catalog().get_entry(id);
435 item.source()
436 .map(|s| matches!(s.data_source, DataSourceDesc::Webhook { .. }))
437 .unwrap_or(false)
438 });
439 webhook_sources_to_restart.extend(webhook_sources);
440 }
441 catalog::Op::CreateCluster { id, .. } => {
442 clusters_to_create.push(*id);
443 }
444 catalog::Op::CreateClusterReplica {
445 cluster_id,
446 name,
447 config,
448 ..
449 } => {
450 cluster_replicas_to_create.push((
451 *cluster_id,
452 name.clone(),
453 config.location.num_processes(),
454 ));
455 }
456 _ => (),
457 }
458 }
459
460 self.validate_resource_limits(&ops, conn_id.unwrap_or(&SYSTEM_CONN_ID))?;
461
462 let oracle_write_ts = self.get_local_write_ts().await.timestamp;
471
472 let Coordinator {
473 catalog,
474 active_conns,
475 controller,
476 cluster_replica_statuses,
477 ..
478 } = self;
479 let catalog = Arc::make_mut(catalog);
480 let conn = conn_id.map(|id| active_conns.get(id).expect("connection must exist"));
481
482 let TransactionResult {
483 builtin_table_updates,
484 catalog_updates,
485 audit_events,
486 } = catalog
487 .transact(
488 Some(&mut controller.storage_collections),
489 oracle_write_ts,
490 conn,
491 ops,
492 )
493 .await?;
494
495 for (cluster_id, replica_id) in &cluster_replicas_to_drop {
496 cluster_replica_statuses.remove_cluster_replica_statuses(cluster_id, replica_id);
497 }
498 for cluster_id in &clusters_to_drop {
499 cluster_replica_statuses.remove_cluster_statuses(cluster_id);
500 }
501 for cluster_id in clusters_to_create {
502 cluster_replica_statuses.initialize_cluster_statuses(cluster_id);
503 }
504 let now = to_datetime((catalog.config().now)());
505 for (cluster_id, replica_name, num_processes) in cluster_replicas_to_create {
506 let replica_id = catalog
507 .resolve_replica_in_cluster(&cluster_id, &replica_name)
508 .expect("just created")
509 .replica_id();
510 cluster_replica_statuses.initialize_cluster_replica_statuses(
511 cluster_id,
512 replica_id,
513 num_processes,
514 now,
515 );
516 }
517
518 let (builtin_update_notify, _) = self
521 .builtin_table_update()
522 .execute(builtin_table_updates)
523 .await;
524
525 let _: () = async {
528 if !webhook_sources_to_restart.is_empty() {
529 self.restart_webhook_sources(webhook_sources_to_restart);
530 }
531
532 if update_metrics_config {
533 mz_metrics::update_dyncfg(&self.catalog().system_config().dyncfg_updates());
534 }
535 if update_controller_config {
536 self.update_controller_config();
537 }
538 if update_compute_config {
539 self.update_compute_config();
540 }
541 if update_storage_config {
542 self.update_storage_config();
543 }
544 if update_timestamp_oracle_config {
545 self.update_timestamp_oracle_config();
546 }
547 if update_metrics_retention {
548 self.update_metrics_retention();
549 }
550 if update_tracing_config {
551 self.update_tracing_config();
552 }
553 if update_secrets_caching_config {
554 self.update_secrets_caching_config();
555 }
556 if update_cluster_scheduling_config {
557 self.update_cluster_scheduling_config();
558 }
559 if update_http_config {
560 self.update_http_config();
561 }
562 if update_advance_timelines_interval {
563 let new_interval = self.catalog().system_config().default_timestamp_interval();
564 if new_interval != self.advance_timelines_interval.period() {
565 self.advance_timelines_interval = tokio::time::interval(new_interval);
566 }
567 }
568 if update_optimizer_e2e_latency_warning_threshold {
569 let threshold = self
570 .catalog()
571 .system_config()
572 .optimizer_e2e_latency_warning_threshold();
573 self.optimizer_metrics
574 .set_e2e_optimization_time_log_threshold(threshold);
575 }
576 }
577 .instrument(info_span!("coord::catalog_transact_with::finalize"))
578 .await;
579
580 let conn = conn_id.and_then(|id| self.active_conns.get(id));
581 if let Some(segment_client) = &self.segment_client {
582 for VersionedEvent::V1(event) in audit_events {
583 let event_type = format!(
584 "{} {}",
585 event.object_type.as_title_case(),
586 event.event_type.as_title_case()
587 );
588 segment_client.environment_track(
589 &self.catalog().config().environment_id,
590 event_type,
591 json!({ "details": event.details.as_json() }),
592 EventDetails {
593 user_id: conn
594 .and_then(|c| c.user().external_metadata.as_ref())
595 .map(|m| m.user_id),
596 application_name: conn.map(|c| c.application_name()),
597 ..Default::default()
598 },
599 );
600 }
601 }
602
603 Ok((builtin_update_notify, catalog_updates))
604 }
605
606 pub(crate) fn drop_replica(&mut self, cluster_id: ClusterId, replica_id: ReplicaId) {
607 self.drop_introspection_subscribes(replica_id);
608
609 self.controller
610 .drop_replica(cluster_id, replica_id)
611 .expect("dropping replica must not fail");
612 }
613
614 pub(crate) fn drop_sources(&mut self, sources: Vec<(CatalogItemId, GlobalId)>) {
616 for (item_id, _gid) in &sources {
617 self.active_webhooks.remove(item_id);
618 }
619 let storage_metadata = self.catalog.state().storage_metadata();
620 let source_gids = sources.into_iter().map(|(_id, gid)| gid).collect();
621 self.controller
622 .storage
623 .drop_sources(storage_metadata, source_gids)
624 .unwrap_or_terminate("cannot fail to drop sources");
625 }
626
627 pub(crate) fn drop_tables(&mut self, tables: Vec<(CatalogItemId, GlobalId)>, ts: Timestamp) {
629 for (item_id, _gid) in &tables {
630 self.active_webhooks.remove(item_id);
631 }
632
633 let storage_metadata = self.catalog.state().storage_metadata();
634 let table_gids = tables.into_iter().map(|(_id, gid)| gid).collect();
635 self.controller
636 .storage
637 .drop_tables(storage_metadata, table_gids, ts)
638 .unwrap_or_terminate("cannot fail to drop tables");
639 }
640
641 fn restart_webhook_sources(&mut self, sources: impl IntoIterator<Item = CatalogItemId>) {
642 for id in sources {
643 self.active_webhooks.remove(&id);
644 }
645 }
646
647 #[must_use]
653 pub async fn drop_compute_sink(
654 &mut self,
655 sink_id: GlobalId,
656 ) -> Option<(ActiveComputeSink, BuiltinTableAppendNotify)> {
657 self.drop_compute_sinks([sink_id]).await.remove(&sink_id)
658 }
659
660 #[must_use]
671 pub async fn drop_compute_sinks(
672 &mut self,
673 sink_ids: impl IntoIterator<Item = GlobalId>,
674 ) -> BTreeMap<GlobalId, (ActiveComputeSink, BuiltinTableAppendNotify)> {
675 let mut by_id = BTreeMap::new();
676 let mut by_cluster: BTreeMap<_, Vec<_>> = BTreeMap::new();
677 for sink_id in sink_ids {
678 let (sink, write_notify) = match self.remove_active_compute_sink(sink_id).await {
679 None => {
680 tracing::debug!(%sink_id, "drop_compute_sinks: sink already removed");
685 continue;
686 }
687 Some(entry) => entry,
688 };
689
690 by_cluster
691 .entry(sink.cluster_id())
692 .or_default()
693 .push(sink_id);
694 by_id.insert(sink_id, (sink, write_notify));
695 }
696 for (cluster_id, ids) in by_cluster {
697 let compute = &mut self.controller.compute;
698 if compute.instance_exists(cluster_id) {
700 compute
701 .drop_collections(cluster_id, ids)
702 .unwrap_or_terminate("cannot fail to drop collections");
703 }
704 }
705 by_id
706 }
707
708 pub async fn retire_compute_sinks(
714 &mut self,
715 mut reasons: BTreeMap<GlobalId, ActiveComputeSinkRetireReason>,
716 ) -> BuiltinTableAppendCompletion {
717 let sink_ids = reasons.keys().cloned();
718 let to_retire: Vec<_> = self
719 .drop_compute_sinks(sink_ids)
720 .await
721 .into_iter()
722 .map(|(id, (sink, write_notify))| {
723 let reason = reasons
724 .remove(&id)
725 .expect("all returned IDs are in `reasons`");
726 (sink, write_notify, reason)
727 })
728 .collect();
729
730 let (done_tx, done_rx) = tokio::sync::oneshot::channel();
736 task::spawn(|| "retire_compute_sinks", async move {
737 for (sink, write_notify, reason) in to_retire {
738 write_notify.await;
739 sink.retire(reason);
740 }
741 let _ = done_tx.send(());
742 });
743 BuiltinTableAppendCompletion::new(Box::pin(async move {
744 let _ = done_rx.await;
745 }))
746 }
747
748 pub async fn drop_reconfiguration_replicas(
751 &mut self,
752 cluster_ids: BTreeSet<ClusterId>,
753 ) -> Result<(), AdapterError> {
754 let pending_cluster_ops: Vec<Op> = cluster_ids
755 .iter()
756 .map(|c| {
757 self.catalog()
758 .get_cluster(c.clone())
759 .replicas()
760 .filter_map(|r| match r.config.location {
761 ReplicaLocation::Managed(ref l) if l.pending => {
762 Some(DropObjectInfo::ClusterReplica((
763 c.clone(),
764 r.replica_id,
765 ReplicaCreateDropReason::Manual,
766 )))
767 }
768 _ => None,
769 })
770 .collect::<Vec<DropObjectInfo>>()
771 })
772 .filter_map(|pending_replica_drop_ops_by_cluster| {
773 match pending_replica_drop_ops_by_cluster.len() {
774 0 => None,
775 _ => Some(Op::DropObjects(pending_replica_drop_ops_by_cluster)),
776 }
777 })
778 .collect();
779 if !pending_cluster_ops.is_empty() {
780 self.catalog_transact(None, pending_cluster_ops).await?;
781 }
782 Ok(())
783 }
784
785 #[mz_ore::instrument(level = "debug")]
787 pub(crate) async fn cancel_compute_sinks_for_conn(
788 &mut self,
789 conn_id: &ConnectionId,
790 ) -> BuiltinTableAppendCompletion {
791 self.retire_compute_sinks_for_conn(conn_id, ActiveComputeSinkRetireReason::Canceled)
792 .await
793 }
794
795 #[mz_ore::instrument(level = "debug")]
797 pub(crate) async fn cancel_cluster_reconfigurations_for_conn(
798 &mut self,
799 conn_id: &ConnectionId,
800 ) {
801 self.retire_cluster_reconfigurations_for_conn(conn_id).await
802 }
803
804 #[mz_ore::instrument(level = "debug")]
807 pub(crate) async fn retire_compute_sinks_for_conn(
808 &mut self,
809 conn_id: &ConnectionId,
810 reason: ActiveComputeSinkRetireReason,
811 ) -> BuiltinTableAppendCompletion {
812 let drop_sinks = self
813 .active_conns
814 .get_mut(conn_id)
815 .expect("must exist for active session")
816 .drop_sinks
817 .iter()
818 .map(|sink_id| (*sink_id, reason.clone()))
819 .collect();
820 self.retire_compute_sinks(drop_sinks).await
821 }
822
823 #[mz_ore::instrument(level = "debug")]
825 pub(crate) async fn retire_cluster_reconfigurations_for_conn(
826 &mut self,
827 conn_id: &ConnectionId,
828 ) {
829 let reconfiguring_clusters = self
830 .active_conns
831 .get(conn_id)
832 .expect("must exist for active session")
833 .pending_cluster_alters
834 .clone();
835 self.drop_reconfiguration_replicas(reconfiguring_clusters)
837 .await
838 .unwrap_or_terminate("cannot fail to drop reconfiguration replicas");
839
840 self.active_conns
841 .get_mut(conn_id)
842 .expect("must exist for active session")
843 .pending_cluster_alters
844 .clear();
845 }
846
847 pub(crate) fn drop_storage_sinks(&mut self, sink_gids: Vec<GlobalId>) {
848 let storage_metadata = self.catalog.state().storage_metadata();
849 self.controller
850 .storage
851 .drop_sinks(storage_metadata, sink_gids)
852 .unwrap_or_terminate("cannot fail to drop sinks");
853 }
854
855 pub(crate) fn drop_compute_collections(&mut self, collections: Vec<(ClusterId, GlobalId)>) {
856 let mut by_cluster: BTreeMap<_, Vec<_>> = BTreeMap::new();
857 for (cluster_id, gid) in collections {
858 by_cluster.entry(cluster_id).or_default().push(gid);
859 }
860 for (cluster_id, gids) in by_cluster {
861 let compute = &mut self.controller.compute;
862 if compute.instance_exists(cluster_id) {
864 compute
865 .drop_collections(cluster_id, gids)
866 .unwrap_or_terminate("cannot fail to drop collections");
867 }
868 }
869 }
870
871 pub(crate) fn drop_vpc_endpoints_in_background(&self, vpc_endpoints: Vec<CatalogItemId>) {
872 let Some(cloud_resource_controller) = self.cloud_resource_controller.as_ref() else {
876 warn!("dropping VPC endpoints without cloud_resource_controller; skipping cleanup");
877 return;
878 };
879 let cloud_resource_controller = Arc::clone(cloud_resource_controller);
880 task::spawn(
888 || "drop_vpc_endpoints",
889 async move {
890 for vpc_endpoint in vpc_endpoints {
891 let _ = Retry::default()
892 .max_duration(Duration::from_secs(60))
893 .retry_async(|_state| async {
894 fail_point!("drop_vpc_endpoint", |r| {
895 Err(anyhow::anyhow!("Fail point error {:?}", r))
896 });
897 match cloud_resource_controller
898 .delete_vpc_endpoint(vpc_endpoint)
899 .await
900 {
901 Ok(_) => Ok(()),
902 Err(e) => {
903 warn!("Dropping VPC Endpoints has encountered an error: {}", e);
904 Err(e)
905 }
906 }
907 })
908 .await;
909 }
910 }
911 .instrument(info_span!(
912 "coord::catalog_transact_inner::drop_vpc_endpoints"
913 )),
914 );
915 }
916
917 pub(crate) async fn drop_temp_items(&mut self, conn_id: &ConnectionId) {
920 let temp_items = self.catalog().state().get_temp_items(conn_id).collect();
921 let all_items = self.catalog().object_dependents(&temp_items, conn_id);
922
923 if all_items.is_empty() {
924 return;
925 }
926 let op = Op::DropObjects(
927 all_items
928 .into_iter()
929 .map(DropObjectInfo::manual_drop_from_object_id)
930 .collect(),
931 );
932
933 self.catalog_transact_with_context(Some(conn_id), None, vec![op])
934 .await
935 .expect("unable to drop temporary items for conn_id");
936 }
937
938 fn update_cluster_scheduling_config(&self) {
939 let config = flags::orchestrator_scheduling_config(self.catalog.system_config());
940 self.controller
941 .update_orchestrator_scheduling_config(config);
942 }
943
944 fn update_secrets_caching_config(&self) {
945 let config = flags::caching_config(self.catalog.system_config());
946 self.caching_secrets_reader.set_policy(config);
947 }
948
949 fn update_tracing_config(&self) {
950 let tracing = flags::tracing_config(self.catalog().system_config());
951 tracing.apply(&self.tracing_handle);
952 }
953
954 fn update_compute_config(&mut self) {
955 let config_params = flags::compute_config(self.catalog().system_config());
956 self.controller.compute.update_configuration(config_params);
957 }
958
959 fn update_storage_config(&mut self) {
960 let config_params = flags::storage_config(self.catalog().system_config());
961 self.controller.storage.update_parameters(config_params);
962 }
963
964 fn update_timestamp_oracle_config(&self) {
965 let config_params = flags::timestamp_oracle_config(self.catalog().system_config());
966 if let Some(config) = self.timestamp_oracle_config.as_ref() {
967 config.apply_parameters(config_params)
968 }
969 }
970
971 fn update_metrics_retention(&self) {
972 let duration = self.catalog().system_config().metrics_retention();
973 let policy = ReadPolicy::lag_writes_by(
974 Timestamp::new(u64::try_from(duration.as_millis()).unwrap_or_else(|_e| {
975 tracing::error!("Absurd metrics retention duration: {duration:?}.");
976 u64::MAX
977 })),
978 SINCE_GRANULARITY,
979 );
980 let storage_policies = self
981 .catalog()
982 .entries()
983 .filter(|entry| {
984 entry.item().is_retained_metrics_object()
985 && entry.item().is_compute_object_on_cluster().is_none()
986 })
987 .map(|entry| (entry.id(), policy.clone()))
988 .collect::<Vec<_>>();
989 let compute_policies = self
990 .catalog()
991 .entries()
992 .filter_map(|entry| {
993 if let (true, Some(cluster_id)) = (
994 entry.item().is_retained_metrics_object(),
995 entry.item().is_compute_object_on_cluster(),
996 ) {
997 Some((cluster_id, entry.id(), policy.clone()))
998 } else {
999 None
1000 }
1001 })
1002 .collect::<Vec<_>>();
1003 self.update_storage_read_policies(storage_policies);
1004 self.update_compute_read_policies(compute_policies);
1005 }
1006
1007 fn update_controller_config(&mut self) {
1008 let sys_config = self.catalog().system_config();
1009 self.controller
1010 .update_configuration(sys_config.dyncfg_updates());
1011 }
1012
1013 fn update_http_config(&mut self) {
1014 let webhook_request_limit = self
1015 .catalog()
1016 .system_config()
1017 .webhook_concurrent_request_limit();
1018 self.webhook_concurrency_limit
1019 .set_limit(webhook_request_limit);
1020 }
1021
1022 pub(crate) async fn create_storage_export(
1023 &mut self,
1024 id: GlobalId,
1025 sink: &Sink,
1026 ) -> Result<(), AdapterError> {
1027 self.controller.storage.check_exists(sink.from)?;
1029
1030 let id_bundle = crate::CollectionIdBundle {
1037 storage_ids: btreeset! {sink.from},
1038 compute_ids: btreemap! {},
1039 };
1040
1041 let read_holds = self.acquire_read_holds(&id_bundle);
1049 let as_of = read_holds.least_valid_read();
1050
1051 let storage_sink_from_entry = self.catalog().get_entry_by_global_id(&sink.from);
1052 let storage_sink_desc = mz_storage_types::sinks::StorageSinkDesc {
1053 from: sink.from,
1054 from_desc: storage_sink_from_entry
1055 .relation_desc()
1056 .expect("sinks can only be built on items with descs")
1057 .into_owned(),
1058 connection: sink
1059 .connection
1060 .clone()
1061 .into_inline_connection(self.catalog().state()),
1062 envelope: sink.envelope,
1063 as_of,
1064 with_snapshot: sink.with_snapshot,
1065 version: sink.version,
1066 from_storage_metadata: (),
1067 to_storage_metadata: (),
1068 commit_interval: sink.commit_interval,
1069 };
1070
1071 let collection_desc = CollectionDescription {
1072 desc: KAFKA_PROGRESS_DESC.clone(),
1074 data_source: DataSource::Sink {
1075 desc: ExportDescription {
1076 sink: storage_sink_desc,
1077 instance_id: sink.cluster_id,
1078 },
1079 },
1080 since: None,
1081 timeline: None,
1082 primary: None,
1083 };
1084 let collections = vec![(id, collection_desc)];
1085
1086 let storage_metadata = self.catalog.state().storage_metadata();
1088 let res = self
1089 .controller
1090 .storage
1091 .create_collections(storage_metadata, None, collections)
1092 .await;
1093
1094 drop(read_holds);
1097
1098 Ok(res?)
1099 }
1100
1101 fn validate_resource_limits(
1104 &self,
1105 ops: &Vec<catalog::Op>,
1106 conn_id: &ConnectionId,
1107 ) -> Result<(), AdapterError> {
1108 let mut new_kafka_connections = 0;
1109 let mut new_postgres_connections = 0;
1110 let mut new_mysql_connections = 0;
1111 let mut new_sql_server_connections = 0;
1112 let mut new_aws_privatelink_connections = 0;
1113 let mut new_tables = 0;
1114 let mut new_sources = 0;
1115 let mut new_sinks = 0;
1116 let mut new_materialized_views = 0;
1117 let mut new_clusters = 0;
1118 let mut new_replicas_per_cluster = BTreeMap::new();
1119 let mut new_credit_consumption_rate = Numeric::zero();
1120 let mut new_databases = 0;
1121 let mut new_schemas_per_database = BTreeMap::new();
1122 let mut new_objects_per_schema = BTreeMap::new();
1123 let mut new_secrets = 0;
1124 let mut new_roles = 0;
1125 let mut new_network_policies = 0;
1126 for op in ops {
1127 match op {
1128 Op::CreateDatabase { .. } => {
1129 new_databases += 1;
1130 }
1131 Op::CreateSchema { database_id, .. } => {
1132 if let ResolvedDatabaseSpecifier::Id(database_id) = database_id {
1133 *new_schemas_per_database.entry(database_id).or_insert(0) += 1;
1134 }
1135 }
1136 Op::CreateRole { .. } => {
1137 new_roles += 1;
1138 }
1139 Op::CreateNetworkPolicy { .. } => {
1140 new_network_policies += 1;
1141 }
1142 Op::CreateCluster { .. } => {
1143 new_clusters += 1;
1147 }
1148 Op::CreateClusterReplica {
1149 cluster_id, config, ..
1150 } => {
1151 if cluster_id.is_user() {
1152 *new_replicas_per_cluster.entry(*cluster_id).or_insert(0) += 1;
1153 if let ReplicaLocation::Managed(location) = &config.location {
1154 let replica_allocation = self
1155 .catalog()
1156 .cluster_replica_sizes()
1157 .0
1158 .get(location.size_for_billing())
1159 .expect(
1160 "location size is validated against the cluster replica sizes",
1161 );
1162 new_credit_consumption_rate += replica_allocation.credits_per_hour
1163 }
1164 }
1165 }
1166 Op::CreateItem { name, item, .. } => {
1167 *new_objects_per_schema
1168 .entry((
1169 name.qualifiers.database_spec.clone(),
1170 name.qualifiers.schema_spec.clone(),
1171 ))
1172 .or_insert(0) += 1;
1173 match item {
1174 CatalogItem::Connection(connection) => match connection.details {
1175 ConnectionDetails::Kafka(_) => new_kafka_connections += 1,
1176 ConnectionDetails::Postgres(_) => new_postgres_connections += 1,
1177 ConnectionDetails::MySql(_) => new_mysql_connections += 1,
1178 ConnectionDetails::SqlServer(_) => new_sql_server_connections += 1,
1179 ConnectionDetails::AwsPrivatelink(_) => {
1180 new_aws_privatelink_connections += 1
1181 }
1182 ConnectionDetails::Csr(_)
1183 | ConnectionDetails::GlueSchemaRegistry(_)
1184 | ConnectionDetails::Ssh { .. }
1185 | ConnectionDetails::Aws(_)
1186 | ConnectionDetails::Gcp(_)
1187 | ConnectionDetails::IcebergCatalog(_) => {}
1188 },
1189 CatalogItem::Table(_) => {
1190 new_tables += 1;
1191 }
1192 CatalogItem::Source(source) => {
1193 new_sources += source.user_controllable_persist_shard_count()
1194 }
1195 CatalogItem::Sink(_) => new_sinks += 1,
1196 CatalogItem::MaterializedView(_) => {
1197 new_materialized_views += 1;
1198 }
1199 CatalogItem::Secret(_) => {
1200 new_secrets += 1;
1201 }
1202 CatalogItem::Log(_)
1203 | CatalogItem::View(_)
1204 | CatalogItem::Index(_)
1205 | CatalogItem::Type(_)
1206 | CatalogItem::Func(_) => {}
1207 }
1208 }
1209 Op::DropObjects(drop_object_infos) => {
1210 for drop_object_info in drop_object_infos {
1211 match drop_object_info {
1212 DropObjectInfo::Cluster(_) => {
1213 new_clusters -= 1;
1214 }
1215 DropObjectInfo::ClusterReplica((cluster_id, replica_id, _reason)) => {
1216 if cluster_id.is_user() {
1217 *new_replicas_per_cluster.entry(*cluster_id).or_insert(0) -= 1;
1218 let cluster = self
1219 .catalog()
1220 .get_cluster_replica(*cluster_id, *replica_id);
1221 if let ReplicaLocation::Managed(location) =
1222 &cluster.config.location
1223 {
1224 let replica_allocation = self
1225 .catalog()
1226 .cluster_replica_sizes()
1227 .0
1228 .get(location.size_for_billing())
1229 .expect(
1230 "location size is validated against the cluster replica sizes",
1231 );
1232 new_credit_consumption_rate -=
1233 replica_allocation.credits_per_hour
1234 }
1235 }
1236 }
1237 DropObjectInfo::Database(_) => {
1238 new_databases -= 1;
1239 }
1240 DropObjectInfo::Schema((database_spec, _)) => {
1241 if let ResolvedDatabaseSpecifier::Id(database_id) = database_spec {
1242 *new_schemas_per_database.entry(database_id).or_insert(0) -= 1;
1243 }
1244 }
1245 DropObjectInfo::Role(_) => {
1246 new_roles -= 1;
1247 }
1248 DropObjectInfo::NetworkPolicy(_) => {
1249 new_network_policies -= 1;
1250 }
1251 DropObjectInfo::Item(id) => {
1252 let entry = self.catalog().get_entry(id);
1253 *new_objects_per_schema
1254 .entry((
1255 entry.name().qualifiers.database_spec.clone(),
1256 entry.name().qualifiers.schema_spec.clone(),
1257 ))
1258 .or_insert(0) -= 1;
1259 match entry.item() {
1260 CatalogItem::Connection(connection) => match connection.details
1261 {
1262 ConnectionDetails::AwsPrivatelink(_) => {
1263 new_aws_privatelink_connections -= 1;
1264 }
1265 _ => (),
1266 },
1267 CatalogItem::Table(_) => {
1268 new_tables -= 1;
1269 }
1270 CatalogItem::Source(source) => {
1271 new_sources -=
1272 source.user_controllable_persist_shard_count()
1273 }
1274 CatalogItem::Sink(_) => new_sinks -= 1,
1275 CatalogItem::MaterializedView(_) => {
1276 new_materialized_views -= 1;
1277 }
1278 CatalogItem::Secret(_) => {
1279 new_secrets -= 1;
1280 }
1281 CatalogItem::Log(_)
1282 | CatalogItem::View(_)
1283 | CatalogItem::Index(_)
1284 | CatalogItem::Type(_)
1285 | CatalogItem::Func(_) => {}
1286 }
1287 }
1288 }
1289 }
1290 }
1291 Op::UpdateItem {
1292 name: _,
1293 id,
1294 to_item,
1295 } => match to_item {
1296 CatalogItem::Source(source) => {
1297 let current_source = self
1298 .catalog()
1299 .get_entry(id)
1300 .source()
1301 .expect("source update is for source item");
1302
1303 new_sources += source.user_controllable_persist_shard_count()
1304 - current_source.user_controllable_persist_shard_count();
1305 }
1306 CatalogItem::Connection(_)
1307 | CatalogItem::Table(_)
1308 | CatalogItem::Sink(_)
1309 | CatalogItem::MaterializedView(_)
1310 | CatalogItem::Secret(_)
1311 | CatalogItem::Log(_)
1312 | CatalogItem::View(_)
1313 | CatalogItem::Index(_)
1314 | CatalogItem::Type(_)
1315 | CatalogItem::Func(_) => {}
1316 },
1317 Op::AlterRole { .. }
1318 | Op::AlterRetainHistory { .. }
1319 | Op::AlterSourceTimestampInterval { .. }
1320 | Op::AlterNetworkPolicy { .. }
1321 | Op::AlterAddColumn { .. }
1322 | Op::AlterMaterializedViewApplyReplacement { .. }
1323 | Op::UpdatePrivilege { .. }
1324 | Op::UpdateDefaultPrivilege { .. }
1325 | Op::GrantRole { .. }
1326 | Op::RenameCluster { .. }
1327 | Op::RenameClusterReplica { .. }
1328 | Op::RenameItem { .. }
1329 | Op::RenameSchema { .. }
1330 | Op::UpdateOwner { .. }
1331 | Op::RevokeRole { .. }
1332 | Op::UpdateClusterConfig { .. }
1333 | Op::UpdateClusterReplicaConfig { .. }
1334 | Op::UpdateSourceReferences { .. }
1335 | Op::UpdateSystemConfiguration { .. }
1336 | Op::ResetSystemConfiguration { .. }
1337 | Op::ResetAllSystemConfiguration { .. }
1338 | Op::UpdateScopedSystemParameters { .. }
1339 | Op::Comment { .. }
1340 | Op::CheckClusterState { .. }
1341 | Op::InjectAuditEvents { .. } => {}
1342 }
1343 }
1344
1345 let mut current_aws_privatelink_connections = 0;
1346 let mut current_postgres_connections = 0;
1347 let mut current_mysql_connections = 0;
1348 let mut current_sql_server_connections = 0;
1349 let mut current_kafka_connections = 0;
1350 for c in self.catalog().user_connections() {
1351 let connection = c
1352 .connection()
1353 .expect("`user_connections()` only returns connection objects");
1354
1355 match connection.details {
1356 ConnectionDetails::AwsPrivatelink(_) => current_aws_privatelink_connections += 1,
1357 ConnectionDetails::Postgres(_) => current_postgres_connections += 1,
1358 ConnectionDetails::MySql(_) => current_mysql_connections += 1,
1359 ConnectionDetails::SqlServer(_) => current_sql_server_connections += 1,
1360 ConnectionDetails::Kafka(_) => current_kafka_connections += 1,
1361 ConnectionDetails::Csr(_)
1362 | ConnectionDetails::GlueSchemaRegistry(_)
1363 | ConnectionDetails::Ssh { .. }
1364 | ConnectionDetails::Aws(_)
1365 | ConnectionDetails::Gcp(_)
1366 | ConnectionDetails::IcebergCatalog(_) => {}
1367 }
1368 }
1369 self.validate_resource_limit(
1370 current_kafka_connections,
1371 new_kafka_connections,
1372 SystemVars::max_kafka_connections,
1373 "Kafka Connection",
1374 MAX_KAFKA_CONNECTIONS.name(),
1375 )?;
1376 self.validate_resource_limit(
1377 current_postgres_connections,
1378 new_postgres_connections,
1379 SystemVars::max_postgres_connections,
1380 "PostgreSQL Connection",
1381 MAX_POSTGRES_CONNECTIONS.name(),
1382 )?;
1383 self.validate_resource_limit(
1384 current_mysql_connections,
1385 new_mysql_connections,
1386 SystemVars::max_mysql_connections,
1387 "MySQL Connection",
1388 MAX_MYSQL_CONNECTIONS.name(),
1389 )?;
1390 self.validate_resource_limit(
1391 current_sql_server_connections,
1392 new_sql_server_connections,
1393 SystemVars::max_sql_server_connections,
1394 "SQL Server Connection",
1395 MAX_SQL_SERVER_CONNECTIONS.name(),
1396 )?;
1397 self.validate_resource_limit(
1398 current_aws_privatelink_connections,
1399 new_aws_privatelink_connections,
1400 SystemVars::max_aws_privatelink_connections,
1401 "AWS PrivateLink Connection",
1402 MAX_AWS_PRIVATELINK_CONNECTIONS.name(),
1403 )?;
1404 self.validate_resource_limit(
1405 self.catalog().user_tables().count(),
1406 new_tables,
1407 SystemVars::max_tables,
1408 "table",
1409 MAX_TABLES.name(),
1410 )?;
1411
1412 let current_sources: usize = self
1413 .catalog()
1414 .user_sources()
1415 .filter_map(|source| source.source())
1416 .map(|source| source.user_controllable_persist_shard_count())
1417 .sum::<i64>()
1418 .try_into()
1419 .expect("non-negative sum of sources");
1420
1421 self.validate_resource_limit(
1422 current_sources,
1423 new_sources,
1424 SystemVars::max_sources,
1425 "source",
1426 MAX_SOURCES.name(),
1427 )?;
1428 self.validate_resource_limit(
1429 self.catalog().user_sinks().count(),
1430 new_sinks,
1431 SystemVars::max_sinks,
1432 "sink",
1433 MAX_SINKS.name(),
1434 )?;
1435 self.validate_resource_limit(
1436 self.catalog().user_materialized_views().count(),
1437 new_materialized_views,
1438 SystemVars::max_materialized_views,
1439 "materialized view",
1440 MAX_MATERIALIZED_VIEWS.name(),
1441 )?;
1442 self.validate_resource_limit(
1443 self.catalog().user_clusters().count(),
1449 new_clusters,
1450 SystemVars::max_clusters,
1451 "cluster",
1452 MAX_CLUSTERS.name(),
1453 )?;
1454 for (cluster_id, new_replicas) in new_replicas_per_cluster {
1455 let current_amount = self
1457 .catalog()
1458 .try_get_cluster(cluster_id)
1459 .map(|instance| instance.user_replicas().count())
1460 .unwrap_or(0);
1461 self.validate_resource_limit(
1462 current_amount,
1463 new_replicas,
1464 SystemVars::max_replicas_per_cluster,
1465 "cluster replica",
1466 MAX_REPLICAS_PER_CLUSTER.name(),
1467 )?;
1468 }
1469 self.validate_resource_limit_numeric(
1470 self.current_credit_consumption_rate(None),
1471 new_credit_consumption_rate,
1472 |system_vars| {
1473 self.license_key
1474 .max_credit_consumption_rate()
1475 .map_or_else(|| system_vars.max_credit_consumption_rate(), Numeric::from)
1476 },
1477 "cluster replica",
1478 MAX_CREDIT_CONSUMPTION_RATE.name(),
1479 )?;
1480 self.validate_resource_limit(
1481 self.catalog().databases().count(),
1482 new_databases,
1483 SystemVars::max_databases,
1484 "database",
1485 MAX_DATABASES.name(),
1486 )?;
1487 for (database_id, new_schemas) in new_schemas_per_database {
1488 self.validate_resource_limit(
1489 self.catalog().get_database(database_id).schemas_by_id.len(),
1490 new_schemas,
1491 SystemVars::max_schemas_per_database,
1492 "schema",
1493 MAX_SCHEMAS_PER_DATABASE.name(),
1494 )?;
1495 }
1496 for ((database_spec, schema_spec), new_objects) in new_objects_per_schema {
1497 let current_items = self
1500 .catalog()
1501 .try_get_schema(&database_spec, &schema_spec, conn_id)
1502 .map(|schema| schema.items.len())
1503 .unwrap_or(0);
1504 self.validate_resource_limit(
1505 current_items,
1506 new_objects,
1507 SystemVars::max_objects_per_schema,
1508 "object",
1509 MAX_OBJECTS_PER_SCHEMA.name(),
1510 )?;
1511 }
1512 self.validate_resource_limit(
1513 self.catalog().user_secrets().count(),
1514 new_secrets,
1515 SystemVars::max_secrets,
1516 "secret",
1517 MAX_SECRETS.name(),
1518 )?;
1519 self.validate_resource_limit(
1520 self.catalog().user_roles().count(),
1521 new_roles,
1522 SystemVars::max_roles,
1523 "role",
1524 MAX_ROLES.name(),
1525 )?;
1526 self.validate_resource_limit(
1527 self.catalog().user_network_policies().count(),
1528 new_network_policies,
1529 SystemVars::max_network_policies,
1530 "network_policy",
1531 MAX_NETWORK_POLICIES.name(),
1532 )?;
1533 Ok(())
1534 }
1535
1536 pub(crate) fn validate_resource_limit<F>(
1538 &self,
1539 current_amount: usize,
1540 new_instances: i64,
1541 resource_limit: F,
1542 resource_type: &str,
1543 limit_name: &str,
1544 ) -> Result<(), AdapterError>
1545 where
1546 F: Fn(&SystemVars) -> u32,
1547 {
1548 if new_instances <= 0 {
1549 return Ok(());
1550 }
1551
1552 let limit: i64 = resource_limit(self.catalog().system_config()).into();
1553 let current_amount: Option<i64> = current_amount.try_into().ok();
1554 let desired =
1555 current_amount.and_then(|current_amount| current_amount.checked_add(new_instances));
1556
1557 let exceeds_limit = if let Some(desired) = desired {
1558 desired > limit
1559 } else {
1560 true
1561 };
1562
1563 let desired = desired
1564 .map(|desired| desired.to_string())
1565 .unwrap_or_else(|| format!("more than {}", i64::MAX));
1566 let current = current_amount
1567 .map(|current| current.to_string())
1568 .unwrap_or_else(|| format!("more than {}", i64::MAX));
1569 if exceeds_limit {
1570 Err(AdapterError::ResourceExhaustion {
1571 resource_type: resource_type.to_string(),
1572 limit_name: limit_name.to_string(),
1573 desired,
1574 limit: limit.to_string(),
1575 current,
1576 })
1577 } else {
1578 Ok(())
1579 }
1580 }
1581
1582 pub(crate) fn validate_resource_limit_numeric<F>(
1586 &self,
1587 current_amount: Numeric,
1588 new_amount: Numeric,
1589 resource_limit: F,
1590 resource_type: &str,
1591 limit_name: &str,
1592 ) -> Result<(), AdapterError>
1593 where
1594 F: Fn(&SystemVars) -> Numeric,
1595 {
1596 if new_amount <= Numeric::zero() {
1597 return Ok(());
1598 }
1599
1600 let limit = resource_limit(self.catalog().system_config());
1601 let desired = current_amount + new_amount;
1605 if desired > limit {
1606 Err(AdapterError::ResourceExhaustion {
1607 resource_type: resource_type.to_string(),
1608 limit_name: limit_name.to_string(),
1609 desired: desired.to_string(),
1610 limit: limit.to_string(),
1611 current: current_amount.to_string(),
1612 })
1613 } else {
1614 Ok(())
1615 }
1616 }
1617}