1mod builtin_schema_migration;
13
14use std::collections::{BTreeMap, BTreeSet};
15use std::num::NonZeroU32;
16use std::sync::Arc;
17use std::sync::atomic::AtomicU64;
18use std::time::{Duration, Instant};
19
20use futures::future::{BoxFuture, FutureExt};
21use itertools::{Either, Itertools};
22use mz_adapter_types::bootstrap_builtin_cluster_config::BootstrapBuiltinClusterConfig;
23use mz_adapter_types::dyncfgs::ENABLE_EXPRESSION_CACHE;
24use mz_audit_log::{
25 CreateOrDropClusterReplicaReasonV1, EventDetails, EventType, ObjectType, VersionedEvent,
26};
27use mz_auth::hash::scram256_hash;
28use mz_catalog::SYSTEM_CONN_ID;
29use mz_catalog::builtin::{
30 BUILTIN_CLUSTERS, BUILTIN_PREFIXES, BUILTIN_ROLES, BUILTINS, Builtin, Fingerprint,
31 MZ_CATALOG_RAW, RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL,
32};
33use mz_catalog::config::StateConfig;
34use mz_catalog::durable::objects::{
35 SystemObjectDescription, SystemObjectMapping, SystemObjectUniqueIdentifier,
36};
37use mz_catalog::durable::{
38 ClusterReplica, ClusterVariant, ClusterVariantManaged, ReplicaConfig, ReplicaLocation,
39 Transaction, managed_cluster_replica_name,
40};
41use mz_catalog::expr_cache::{
42 ExpressionCacheConfig, ExpressionCacheHandle, GlobalExpressions, LocalExpressions,
43};
44use mz_catalog::memory::error::{Error, ErrorKind};
45use mz_catalog::memory::objects::{
46 BootstrapStateUpdateKind, CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate,
47};
48use mz_controller::clusters::ReplicaLogging;
49use mz_controller_types::ClusterId;
50use mz_ore::cast::usize_to_u64;
51use mz_ore::collections::HashSet;
52use mz_ore::now::{SYSTEM_TIME, to_datetime};
53use mz_ore::{instrument, soft_assert_no_log};
54use mz_repr::adt::mz_acl_item::PrivilegeMap;
55use mz_repr::namespaces::is_unstable_schema;
56use mz_repr::{CatalogItemId, Diff, GlobalId, Timestamp};
57use mz_sql::catalog::{CatalogError as SqlCatalogError, CatalogItemType, RoleMembership, RoleVars};
58use mz_sql::func::OP_IMPLS;
59use mz_sql::names::CommentObjectId;
60use mz_sql::rbac;
61use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, SYSTEM_USER};
62use mz_sql::session::vars::{SessionVars, SystemVars, VarError, VarInput};
63use mz_storage_client::controller::{StorageMetadata, StorageTxn};
64use mz_storage_client::storage_collections::StorageCollections;
65use tracing::{Instrument, info, warn};
66use uuid::Uuid;
67
68use crate::AdapterError;
70use crate::catalog::migrate::{self, get_migration_version, set_migration_version};
71use crate::catalog::state::LocalExpressionCache;
72use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState, Config, is_reserved_name};
73
74pub struct InitializeStateResult {
75 pub state: CatalogState,
77 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
79 pub new_builtin_collections: BTreeSet<GlobalId>,
81 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
83 pub last_seen_version: String,
85 pub expr_cache_handle: Option<ExpressionCacheHandle>,
87 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
89 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
91}
92
93pub struct OpenCatalogResult {
94 pub catalog: Catalog,
96 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
98 pub new_builtin_collections: BTreeSet<GlobalId>,
100 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
102 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
104 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
106}
107
108impl Catalog {
109 pub async fn initialize_state<'a>(
113 config: StateConfig,
114 storage: &'a mut Box<dyn mz_catalog::durable::DurableCatalogState>,
115 ) -> Result<InitializeStateResult, AdapterError> {
116 for builtin_role in BUILTIN_ROLES {
117 assert!(
118 is_reserved_name(builtin_role.name),
119 "builtin role {builtin_role:?} must start with one of the following prefixes {}",
120 BUILTIN_PREFIXES.join(", ")
121 );
122 }
123 for builtin_cluster in BUILTIN_CLUSTERS {
124 assert!(
125 is_reserved_name(builtin_cluster.name),
126 "builtin cluster {builtin_cluster:?} must start with one of the following prefixes {}",
127 BUILTIN_PREFIXES.join(", ")
128 );
129 }
130
131 let mut system_configuration = SystemVars::new().set_unsafe(config.unsafe_mode);
132 if config.all_features {
133 system_configuration.enable_all_feature_flags_by_default();
134 }
135
136 let mut state = CatalogState {
137 database_by_name: imbl::OrdMap::new(),
138 database_by_id: imbl::OrdMap::new(),
139 entry_by_id: imbl::OrdMap::new(),
140 entry_by_global_id: imbl::OrdMap::new(),
141 notices_by_dep_id: imbl::OrdMap::new(),
142 ambient_schemas_by_name: imbl::OrdMap::new(),
143 ambient_schemas_by_id: imbl::OrdMap::new(),
144 clusters_by_name: imbl::OrdMap::new(),
145 clusters_by_id: imbl::OrdMap::new(),
146 roles_by_name: imbl::OrdMap::new(),
147 roles_by_id: imbl::OrdMap::new(),
148 network_policies_by_id: imbl::OrdMap::new(),
149 role_auth_by_id: imbl::OrdMap::new(),
150 network_policies_by_name: imbl::OrdMap::new(),
151 system_configuration: Arc::new(system_configuration),
152 scoped_system_parameters: Default::default(),
153 default_privileges: Arc::new(DefaultPrivileges::default()),
154 system_privileges: Arc::new(PrivilegeMap::default()),
155 comments: Arc::new(CommentsMap::default()),
156 source_references: imbl::OrdMap::new(),
157 storage_metadata: Arc::new(StorageMetadata::default()),
158 temporary_schemas: imbl::OrdMap::new(),
159 mock_authentication_nonce: Default::default(),
160 config: mz_sql::catalog::CatalogConfig {
161 start_time: to_datetime((config.now)()),
162 start_instant: Instant::now(),
163 nonce: rand::random(),
164 environment_id: config.environment_id,
165 session_id: Uuid::new_v4(),
166 build_info: config.build_info,
167 now: config.now.clone(),
168 connection_context: config.connection_context,
169 helm_chart_version: config.helm_chart_version,
170 },
171 cluster_replica_sizes: config.cluster_replica_sizes,
172 availability_zones: config.availability_zones,
173 egress_addresses: config.egress_addresses,
174 aws_principal_context: config.aws_principal_context,
175 aws_privatelink_availability_zones: config.aws_privatelink_availability_zones,
176 http_host_name: config.http_host_name,
177 license_key: config.license_key,
178 };
179
180 let deploy_generation = storage.get_deployment_generation().await?;
181
182 let mut updates: Vec<_> = storage.sync_to_current_updates().await?;
183 assert!(!updates.is_empty(), "initial catalog snapshot is missing");
184 let mut txn = storage.transaction().await?;
185
186 let new_builtin_collections = {
188 migrate::durable_migrate(
189 &mut txn,
190 state.config.environment_id.organization_id(),
191 config.boot_ts,
192 )?;
193 if let Some(remote_system_parameters) = config.remote_system_parameters {
196 for (name, value) in remote_system_parameters {
197 txn.upsert_system_config(&name, value)?;
198 }
199 txn.set_system_config_synced_once()?;
200 }
201 let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?;
203 let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap {
204 system_cluster: config.builtin_system_cluster_config,
205 catalog_server_cluster: config.builtin_catalog_server_cluster_config,
206 probe_cluster: config.builtin_probe_cluster_config,
207 support_cluster: config.builtin_support_cluster_config,
208 analytics_cluster: config.builtin_analytics_cluster_config,
209 };
210 add_new_remove_old_builtin_clusters_migration(
211 &mut txn,
212 &builtin_bootstrap_cluster_config_map,
213 config.boot_ts,
214 )?;
215 add_new_remove_old_builtin_introspection_source_migration(&mut txn)?;
216 reconcile_builtin_cluster_replicas(
217 &mut txn,
218 &builtin_bootstrap_cluster_config_map,
219 config.boot_ts,
220 )?;
221 add_new_remove_old_builtin_roles_migration(&mut txn)?;
222 remove_invalid_config_param_role_defaults_migration(&mut txn)?;
223 remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?;
224
225 new_builtin_collections
226 };
227
228 let op_updates = txn.get_and_commit_op_updates();
229 updates.extend(op_updates);
230
231 let mut builtin_table_updates = Vec::new();
232
233 {
235 for (name, value) in config.system_parameter_defaults {
238 match state.set_system_configuration_default(&name, VarInput::Flat(&value)) {
239 Ok(_) => (),
240 Err(Error {
241 kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
242 }) => {
243 warn!(%name, "cannot load unknown system parameter from catalog storage to set default parameter");
244 }
245 Err(e) => return Err(e.into()),
246 };
247 }
248 state.create_temporary_schema(&SYSTEM_CONN_ID, MZ_SYSTEM_ROLE_ID)?;
249 }
250
251 let mut updates = into_consolidatable_updates_startup(updates, config.boot_ts);
254 differential_dataflow::consolidation::consolidate_updates(&mut updates);
255 soft_assert_no_log!(
256 updates.iter().all(|(_, _, diff)| *diff == Diff::ONE),
257 "consolidated updates should be positive during startup: {updates:?}"
258 );
259
260 let mut pre_item_updates = Vec::new();
261 let mut system_item_updates = Vec::new();
262 let mut item_updates = Vec::new();
263 let mut post_item_updates = Vec::new();
264 let mut audit_log_updates = Vec::new();
265 for (kind, ts, diff) in updates {
266 match kind {
267 BootstrapStateUpdateKind::Role(_)
268 | BootstrapStateUpdateKind::RoleAuth(_)
269 | BootstrapStateUpdateKind::Database(_)
270 | BootstrapStateUpdateKind::Schema(_)
271 | BootstrapStateUpdateKind::DefaultPrivilege(_)
272 | BootstrapStateUpdateKind::SystemPrivilege(_)
273 | BootstrapStateUpdateKind::SystemConfiguration(_)
274 | BootstrapStateUpdateKind::ClusterSystemConfiguration(_)
275 | BootstrapStateUpdateKind::ReplicaSystemConfiguration(_)
276 | BootstrapStateUpdateKind::Cluster(_)
277 | BootstrapStateUpdateKind::NetworkPolicy(_)
278 | BootstrapStateUpdateKind::ClusterReplica(_) => {
279 pre_item_updates.push(StateUpdate {
280 kind: kind.into(),
281 ts,
282 diff: diff.try_into().expect("valid diff"),
283 })
284 }
285 BootstrapStateUpdateKind::IntrospectionSourceIndex(_)
286 | BootstrapStateUpdateKind::SystemObjectMapping(_) => {
287 system_item_updates.push(StateUpdate {
288 kind: kind.into(),
289 ts,
290 diff: diff.try_into().expect("valid diff"),
291 })
292 }
293 BootstrapStateUpdateKind::Item(_) => item_updates.push(StateUpdate {
294 kind: kind.into(),
295 ts,
296 diff: diff.try_into().expect("valid diff"),
297 }),
298 BootstrapStateUpdateKind::Comment(_)
299 | BootstrapStateUpdateKind::StorageCollectionMetadata(_)
300 | BootstrapStateUpdateKind::SourceReferences(_)
301 | BootstrapStateUpdateKind::UnfinalizedShard(_) => {
302 post_item_updates.push((kind, ts, diff));
303 }
304 BootstrapStateUpdateKind::AuditLog(_) => {
305 audit_log_updates.push(StateUpdate {
306 kind: kind.into(),
307 ts,
308 diff: diff.try_into().expect("valid diff"),
309 });
310 }
311 }
312 }
313
314 let (builtin_table_update, _catalog_updates) = state
315 .apply_updates(pre_item_updates, &mut LocalExpressionCache::Closed)
316 .await;
317 builtin_table_updates.extend(builtin_table_update);
318
319 {
323 if let Some(password) = config.external_login_password_mz_system {
324 let role_auth = RoleAuth {
325 role_id: MZ_SYSTEM_ROLE_ID,
326 password_hash: Some(
329 scram256_hash(&password, &NonZeroU32::new(600_000).expect("known valid"))
330 .map_err(|_| {
331 AdapterError::Internal("Failed to hash mz_system password.".to_owned())
332 })?,
333 ),
334 updated_at: SYSTEM_TIME(),
335 };
336 state
337 .role_auth_by_id
338 .insert(MZ_SYSTEM_ROLE_ID, role_auth.clone());
339 let builtin_table_update = state.generate_builtin_table_update(
340 mz_catalog::memory::objects::StateUpdateKind::RoleAuth(role_auth.into()),
341 mz_catalog::memory::objects::StateDiff::Addition,
342 );
343 builtin_table_updates.extend(builtin_table_update);
344 }
345 }
346
347 let expr_cache_start = Instant::now();
348 info!("startup: coordinator init: catalog open: expr cache open beginning");
349 let enable_expr_cache_dyncfg = ENABLE_EXPRESSION_CACHE.get(state.system_config().dyncfgs());
352 let expr_cache_enabled = config
353 .enable_expression_cache_override
354 .unwrap_or(enable_expr_cache_dyncfg);
355 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) = if expr_cache_enabled {
356 info!(
357 ?config.enable_expression_cache_override,
358 ?enable_expr_cache_dyncfg,
359 "using expression cache for startup"
360 );
361 let current_ids = txn
362 .get_items()
363 .flat_map(|item| {
364 let gid = item.global_id.clone();
365 let gids: Vec<_> = item.extra_versions.values().cloned().collect();
366 std::iter::once(gid).chain(gids)
367 })
368 .chain(
369 txn.get_system_object_mappings()
370 .map(|som| som.unique_identifier.global_id),
371 )
372 .collect();
373 let dyncfgs = config.persist_client.dyncfgs().clone();
374 let build_version = if config.build_info.is_dev() {
375 config
378 .build_info
379 .semver_version_build()
380 .expect("build ID is not available on your platform!")
381 } else {
382 config.build_info.semver_version()
383 };
384 let expr_cache_config = ExpressionCacheConfig {
385 build_version,
386 shard_id: txn
387 .get_expression_cache_shard()
388 .expect("expression cache shard should exist for opened catalogs"),
389 persist: config.persist_client,
390 current_ids,
391 remove_prior_versions: !config.read_only,
392 compact_shard: config.read_only,
393 dyncfgs,
394 };
395 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) =
396 ExpressionCacheHandle::spawn_expression_cache(expr_cache_config).await;
397 (
398 Some(expr_cache_handle),
399 cached_local_exprs,
400 cached_global_exprs,
401 )
402 } else {
403 (None, BTreeMap::new(), BTreeMap::new())
404 };
405 let mut local_expr_cache = LocalExpressionCache::new(cached_local_exprs);
406 info!(
407 "startup: coordinator init: catalog open: expr cache open complete in {:?}",
408 expr_cache_start.elapsed()
409 );
410
411 let (builtin_table_update, _catalog_updates) = state
417 .apply_updates(system_item_updates, &mut local_expr_cache)
418 .await;
419 builtin_table_updates.extend(builtin_table_update);
420
421 let last_seen_version =
422 get_migration_version(&txn).map_or_else(|| "new".into(), |v| v.to_string());
423
424 let mz_authentication_mock_nonce =
425 txn.get_authentication_mock_nonce().ok_or_else(|| {
426 Error::new(ErrorKind::SettingError("authentication nonce".to_string()))
427 })?;
428
429 state.mock_authentication_nonce = Some(mz_authentication_mock_nonce);
430
431 let (builtin_table_update, _catalog_updates) = if !config.skip_migrations {
433 let migrate_result = migrate::migrate(
434 &mut state,
435 &mut txn,
436 &mut local_expr_cache,
437 item_updates,
438 config.now,
439 config.boot_ts,
440 )
441 .await
442 .map_err(|e| {
443 Error::new(ErrorKind::FailedCatalogMigration {
444 last_seen_version: last_seen_version.clone(),
445 this_version: config.build_info.version,
446 cause: e.to_string(),
447 })
448 })?;
449 if !migrate_result.post_item_updates.is_empty() {
450 post_item_updates.extend(migrate_result.post_item_updates);
453 if let Some(max_ts) = post_item_updates.iter().map(|(_, ts, _)| ts).max().cloned() {
455 for (_, ts, _) in &mut post_item_updates {
456 *ts = max_ts;
457 }
458 }
459 differential_dataflow::consolidation::consolidate_updates(&mut post_item_updates);
460 }
461
462 (
463 migrate_result.builtin_table_updates,
464 migrate_result.catalog_updates,
465 )
466 } else {
467 state
468 .apply_updates(item_updates, &mut local_expr_cache)
469 .await
470 };
471 builtin_table_updates.extend(builtin_table_update);
472
473 let post_item_updates = post_item_updates
474 .into_iter()
475 .map(|(kind, ts, diff)| StateUpdate {
476 kind: kind.into(),
477 ts,
478 diff: diff.try_into().expect("valid diff"),
479 })
480 .collect();
481 let (builtin_table_update, _catalog_updates) = state
482 .apply_updates(post_item_updates, &mut local_expr_cache)
483 .await;
484 builtin_table_updates.extend(builtin_table_update);
485
486 for audit_log_update in audit_log_updates {
490 builtin_table_updates.extend(
491 state.generate_builtin_table_update(audit_log_update.kind, audit_log_update.diff),
492 );
493 }
494
495 let schema_migration_result = builtin_schema_migration::run(
497 config.build_info,
498 deploy_generation,
499 &mut txn,
500 config.builtin_item_migration_config,
501 )
502 .await?;
503
504 let state_updates = txn.get_and_commit_op_updates();
505
506 let (table_updates, _catalog_updates) = state
512 .apply_updates(state_updates, &mut local_expr_cache)
513 .await;
514 builtin_table_updates.extend(table_updates);
515 let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates);
516
517 set_migration_version(&mut txn, config.build_info.semver_version())?;
519
520 txn.commit(config.boot_ts).await?;
521
522 schema_migration_result.cleanup_action.await;
524
525 Ok(InitializeStateResult {
526 state,
527 migrated_storage_collections_0dt: schema_migration_result.replaced_items,
528 new_builtin_collections: new_builtin_collections.into_iter().collect(),
529 builtin_table_updates,
530 last_seen_version,
531 expr_cache_handle,
532 cached_global_exprs,
533 uncached_local_exprs: local_expr_cache.into_uncached_exprs(),
534 })
535 }
536
537 #[instrument(name = "catalog::open")]
548 pub fn open(config: Config<'_>) -> BoxFuture<'static, Result<OpenCatalogResult, AdapterError>> {
549 async move {
550 let mut storage = config.storage;
551
552 let InitializeStateResult {
553 state,
554 migrated_storage_collections_0dt,
555 new_builtin_collections,
556 mut builtin_table_updates,
557 last_seen_version: _,
558 expr_cache_handle,
559 cached_global_exprs,
560 uncached_local_exprs,
561 } =
562 Self::initialize_state(config.state, &mut storage)
566 .instrument(tracing::info_span!("catalog::initialize_state"))
567 .boxed()
568 .await?;
569
570 let catalog = Catalog {
571 state,
572 expr_cache_handle,
573 transient_revision: 1,
574 shared_transient_revision: Arc::new(AtomicU64::new(1)),
575 storage: Arc::new(tokio::sync::Mutex::new(storage)),
576 };
577
578 for (op, func) in OP_IMPLS.iter() {
581 match func {
582 mz_sql::func::Func::Scalar(impls) => {
583 for imp in impls {
584 builtin_table_updates.push(catalog.state.resolve_builtin_table_update(
585 catalog.state.pack_op_update(op, imp.details(), Diff::ONE),
586 ));
587 }
588 }
589 _ => unreachable!("all operators must be scalar functions"),
590 }
591 }
592
593 for ip in &catalog.state.egress_addresses {
594 builtin_table_updates.push(
595 catalog
596 .state
597 .resolve_builtin_table_update(catalog.state.pack_egress_ip_update(ip)?),
598 );
599 }
600
601 if !catalog.state.license_key.id.is_empty() {
602 builtin_table_updates.push(
603 catalog.state.resolve_builtin_table_update(
604 catalog
605 .state
606 .pack_license_key_update(&catalog.state.license_key)?,
607 ),
608 );
609 }
610
611 catalog.storage().await.mark_bootstrap_complete().await;
612
613 Ok(OpenCatalogResult {
614 catalog,
615 migrated_storage_collections_0dt,
616 new_builtin_collections,
617 builtin_table_updates,
618 cached_global_exprs,
619 uncached_local_exprs,
620 })
621 }
622 .instrument(tracing::info_span!("catalog::open"))
623 .boxed()
624 }
625
626 async fn initialize_storage_state(
633 &mut self,
634 storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
635 ) -> Result<(), mz_catalog::durable::CatalogError> {
636 let collections = self
637 .entries()
638 .filter(|entry| entry.item().is_storage_collection())
639 .flat_map(|entry| entry.global_ids())
640 .collect();
641
642 let mut state = self.state.clone();
645
646 let mut storage = self.storage().await;
647 let shard_id = storage.shard_id();
648 let mut txn = storage.transaction().await?;
649
650 let item_id = self.resolve_builtin_storage_collection(&MZ_CATALOG_RAW);
653 let global_id = self.get_entry(&item_id).latest_global_id();
654 match txn.get_collection_metadata().get(&global_id) {
655 None => {
656 txn.insert_collection_metadata([(global_id, shard_id)].into())
657 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
658 }
659 Some(id) => assert_eq!(*id, shard_id),
660 }
661
662 storage_collections
663 .initialize_state(&mut txn, collections)
664 .await
665 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
666
667 let updates = txn.get_and_commit_op_updates();
668 let (builtin_updates, catalog_updates) = state
669 .apply_updates(updates, &mut LocalExpressionCache::Closed)
670 .await;
671 assert!(
672 builtin_updates.is_empty(),
673 "storage is not allowed to generate catalog changes that would cause changes to builtin tables"
674 );
675 assert!(
676 catalog_updates.is_empty(),
677 "storage is not allowed to generate catalog changes that would change the catalog or controller state"
678 );
679 let commit_ts = txn.upper();
680 txn.commit(commit_ts).await?;
681 drop(storage);
682
683 self.state = state;
685 Ok(())
686 }
687
688 pub async fn initialize_controller(
691 &mut self,
692 config: mz_controller::ControllerConfig,
693 envd_epoch: core::num::NonZeroI64,
694 read_only: bool,
695 ) -> Result<mz_controller::Controller, mz_catalog::durable::CatalogError> {
696 let controller_start = Instant::now();
697 info!("startup: controller init: beginning");
698
699 let controller = {
700 let mut storage = self.storage().await;
701 let mut tx = storage.transaction().await?;
702 mz_controller::prepare_initialization(&mut tx)
703 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
704 let updates = tx.get_and_commit_op_updates();
705 assert!(
706 updates.is_empty(),
707 "initializing controller should not produce updates: {updates:?}"
708 );
709 let commit_ts = tx.upper();
710 tx.commit(commit_ts).await?;
711
712 let read_only_tx = storage.transaction().await?;
713
714 mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await
715 };
716
717 self.initialize_storage_state(&controller.storage_collections)
718 .await?;
719
720 info!(
721 "startup: controller init: complete in {:?}",
722 controller_start.elapsed()
723 );
724
725 Ok(controller)
726 }
727
728 pub async fn expire(self) {
730 if let Some(storage) = Arc::into_inner(self.storage) {
733 let storage = storage.into_inner();
734 storage.expire().await;
735 }
736 }
737}
738
739impl CatalogState {
740 fn set_system_configuration_default(
742 &mut self,
743 name: &str,
744 value: VarInput,
745 ) -> Result<(), Error> {
746 Ok(Arc::make_mut(&mut self.system_configuration).set_default(name, value)?)
747 }
748}
749
750fn add_new_remove_old_builtin_items_migration(
754 txn: &mut mz_catalog::durable::Transaction<'_>,
755) -> Result<Vec<GlobalId>, mz_catalog::durable::CatalogError> {
756 let mut new_builtin_mappings = Vec::new();
757 let mut builtin_descs = HashSet::new();
759
760 let mut builtins = Vec::new();
763 for builtin in BUILTINS::iter() {
764 let desc = SystemObjectDescription {
765 schema_name: builtin.schema().to_string(),
766 object_type: builtin.catalog_item_type(),
767 object_name: builtin.name().to_string(),
768 };
769 if !builtin_descs.insert(desc.clone()) {
771 panic!(
772 "duplicate builtin description: {:?}, {:?}",
773 SystemObjectDescription {
774 schema_name: builtin.schema().to_string(),
775 object_type: builtin.catalog_item_type(),
776 object_name: builtin.name().to_string(),
777 },
778 builtin
779 );
780 }
781 builtins.push((desc, builtin));
782 }
783
784 let mut system_object_mappings: BTreeMap<_, _> = txn
785 .get_system_object_mappings()
786 .map(|system_object_mapping| {
787 (
788 system_object_mapping.description.clone(),
789 system_object_mapping,
790 )
791 })
792 .collect();
793
794 let (existing_builtins, new_builtins): (Vec<_>, Vec<_>) =
795 builtins.into_iter().partition_map(|(desc, builtin)| {
796 let fingerprint = match builtin.runtime_alterable() {
797 false => builtin.fingerprint(),
798 true => RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into(),
799 };
800 match system_object_mappings.remove(&desc) {
801 Some(system_object_mapping) => {
802 Either::Left((builtin, system_object_mapping, fingerprint))
803 }
804 None => Either::Right((builtin, fingerprint)),
805 }
806 });
807 let new_builtin_ids = txn.allocate_system_item_ids(usize_to_u64(new_builtins.len()))?;
808 let new_builtins: Vec<_> = new_builtins
809 .into_iter()
810 .zip_eq(new_builtin_ids.clone())
811 .collect();
812
813 for ((builtin, fingerprint), (catalog_id, global_id)) in new_builtins.iter().cloned() {
815 new_builtin_mappings.push(SystemObjectMapping {
816 description: SystemObjectDescription {
817 schema_name: builtin.schema().to_string(),
818 object_type: builtin.catalog_item_type(),
819 object_name: builtin.name().to_string(),
820 },
821 unique_identifier: SystemObjectUniqueIdentifier {
822 catalog_id,
823 global_id,
824 fingerprint,
825 },
826 });
827
828 let handled_runtime_alterable = match builtin {
834 Builtin::Connection(c) if c.runtime_alterable => {
835 let mut acl_items = vec![rbac::owner_privilege(
836 mz_sql::catalog::ObjectType::Connection,
837 c.owner_id.clone(),
838 )];
839 acl_items.extend_from_slice(c.access);
840 let versions = BTreeMap::new();
842
843 txn.insert_item(
844 catalog_id,
845 c.oid,
846 global_id,
847 mz_catalog::durable::initialize::resolve_system_schema(c.schema).id,
848 c.name,
849 c.sql.into(),
850 *c.owner_id,
851 acl_items,
852 versions,
853 )?;
854 true
855 }
856 _ => false,
857 };
858 assert_eq!(
859 builtin.runtime_alterable(),
860 handled_runtime_alterable,
861 "runtime alterable object was not handled by migration",
862 );
863 }
864 txn.set_system_object_mappings(new_builtin_mappings)?;
865
866 let builtins_with_catalog_ids = existing_builtins
868 .iter()
869 .map(|(b, m, _)| (*b, m.unique_identifier.catalog_id))
870 .chain(
871 new_builtins
872 .into_iter()
873 .map(|((b, _), (catalog_id, _))| (b, catalog_id)),
874 );
875
876 for (builtin, id) in builtins_with_catalog_ids {
877 let (comment_id, desc, comments) = match builtin {
878 Builtin::Source(s) => (CommentObjectId::Source(id), &s.desc, &s.column_comments),
879 Builtin::View(v) => (CommentObjectId::View(id), &v.desc, &v.column_comments),
880 Builtin::Table(t) => (CommentObjectId::Table(id), &t.desc, &t.column_comments),
881 Builtin::MaterializedView(mv) => (
882 CommentObjectId::MaterializedView(id),
883 &mv.desc,
884 &mv.column_comments,
885 ),
886 Builtin::Log(_)
887 | Builtin::Type(_)
888 | Builtin::Func(_)
889 | Builtin::Index(_)
890 | Builtin::Connection(_) => continue,
891 };
892 txn.drop_comments(&BTreeSet::from_iter([
897 CommentObjectId::Table(id),
898 CommentObjectId::View(id),
899 CommentObjectId::MaterializedView(id),
900 CommentObjectId::Source(id),
901 ]))?;
902
903 let mut comments = comments.clone();
904 for (col_idx, name) in desc.iter_names().enumerate() {
905 if let Some(comment) = comments.remove(name.as_str()) {
906 txn.update_comment(comment_id, Some(col_idx + 1), Some(comment.to_owned()))?;
908 }
909 }
910 assert!(
911 comments.is_empty(),
912 "builtin object contains dangling comments that don't correspond to columns {comments:?}"
913 );
914 }
915
916 let mut deleted_system_objects = BTreeSet::new();
919 let mut deleted_runtime_alterable_system_ids = BTreeSet::new();
920 let mut deleted_comments = BTreeSet::new();
921 for (desc, mapping) in system_object_mappings {
922 deleted_system_objects.insert(mapping.description);
923 if mapping.unique_identifier.fingerprint == RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL {
924 deleted_runtime_alterable_system_ids.insert(mapping.unique_identifier.catalog_id);
925 }
926
927 let id = mapping.unique_identifier.catalog_id;
928 let comment_id = match desc.object_type {
929 CatalogItemType::Table => CommentObjectId::Table(id),
930 CatalogItemType::Source => CommentObjectId::Source(id),
931 CatalogItemType::View => CommentObjectId::View(id),
932 CatalogItemType::MaterializedView => CommentObjectId::MaterializedView(id),
933 CatalogItemType::Sink
934 | CatalogItemType::Index
935 | CatalogItemType::Type
936 | CatalogItemType::Func
937 | CatalogItemType::Secret
938 | CatalogItemType::Connection => continue,
939 };
940 deleted_comments.insert(comment_id);
941 }
942 let delete_exceptions: HashSet<SystemObjectDescription> = [].into();
948 assert!(
952 deleted_system_objects
953 .iter()
954 .filter(|object| object.object_type != CatalogItemType::Index)
956 .all(
957 |deleted_object| is_unstable_schema(&deleted_object.schema_name)
958 || delete_exceptions.contains(deleted_object)
959 ),
960 "only objects in unstable schemas can be deleted, deleted objects: {:?}",
961 deleted_system_objects
962 );
963 txn.drop_comments(&deleted_comments)?;
964 txn.remove_items(&deleted_runtime_alterable_system_ids)?;
965 txn.remove_system_object_mappings(deleted_system_objects)?;
966
967 let new_builtin_collections = new_builtin_ids
969 .into_iter()
970 .map(|(_catalog_id, global_id)| global_id)
971 .collect();
972
973 Ok(new_builtin_collections)
974}
975
976fn add_new_remove_old_builtin_clusters_migration(
977 txn: &mut mz_catalog::durable::Transaction<'_>,
978 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
979 boot_ts: Timestamp,
980) -> Result<(), mz_catalog::durable::CatalogError> {
981 let mut durable_clusters: BTreeMap<_, _> = txn
982 .get_clusters()
983 .filter(|cluster| cluster.id.is_system())
984 .map(|cluster| (cluster.name.to_string(), cluster))
985 .collect();
986
987 for builtin_cluster in BUILTIN_CLUSTERS {
989 if durable_clusters.remove(builtin_cluster.name).is_none() {
990 let cluster_config = builtin_cluster_config_map.get_config(builtin_cluster.name)?;
991
992 let cluster_id = txn.insert_system_cluster(
993 builtin_cluster.name,
994 vec![],
995 builtin_cluster.privileges.to_vec(),
996 builtin_cluster.owner_id.to_owned(),
997 mz_catalog::durable::ClusterConfig {
998 variant: mz_catalog::durable::ClusterVariant::Managed(ClusterVariantManaged {
999 size: cluster_config.size,
1000 availability_zones: vec![],
1001 replication_factor: cluster_config.replication_factor,
1002 logging: default_logging_config(),
1003 arrangement_compression: false,
1004 optimizer_feature_overrides: Default::default(),
1005 schedule: Default::default(),
1006 auto_scaling_strategy: None,
1007 reconfiguration: None,
1008 burst: None,
1009 }),
1010 workload_class: None,
1011 },
1012 &HashSet::new(),
1013 )?;
1014
1015 let audit_id = txn.allocate_audit_log_id()?;
1016 txn.insert_audit_log_event(VersionedEvent::new(
1017 audit_id,
1018 EventType::Create,
1019 ObjectType::Cluster,
1020 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1021 id: cluster_id.to_string(),
1022 name: builtin_cluster.name.to_string(),
1023 }),
1024 None,
1025 boot_ts.into(),
1026 ));
1027 }
1028 }
1029
1030 let old_clusters = durable_clusters
1032 .values()
1033 .map(|cluster| cluster.id)
1034 .collect();
1035 txn.remove_clusters(&old_clusters)?;
1036
1037 for (_name, cluster) in &durable_clusters {
1038 let audit_id = txn.allocate_audit_log_id()?;
1039 txn.insert_audit_log_event(VersionedEvent::new(
1040 audit_id,
1041 EventType::Drop,
1042 ObjectType::Cluster,
1043 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1044 id: cluster.id.to_string(),
1045 name: cluster.name.clone(),
1046 }),
1047 None,
1048 boot_ts.into(),
1049 ));
1050 }
1051
1052 Ok(())
1053}
1054
1055fn add_new_remove_old_builtin_introspection_source_migration(
1056 txn: &mut mz_catalog::durable::Transaction<'_>,
1057) -> Result<(), AdapterError> {
1058 let mut new_indexes = Vec::new();
1059 let mut removed_indexes = BTreeSet::new();
1060 for cluster in txn.get_clusters() {
1061 let mut introspection_source_index_ids = txn.get_introspection_source_indexes(cluster.id);
1062
1063 let mut new_logs = Vec::new();
1064
1065 for log in BUILTINS::logs() {
1066 if introspection_source_index_ids.remove(log.name).is_none() {
1067 new_logs.push(log);
1068 }
1069 }
1070
1071 for log in new_logs {
1072 let (item_id, gid) =
1073 Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant);
1074 new_indexes.push((cluster.id, log.name.to_string(), item_id, gid));
1075 }
1076
1077 removed_indexes.extend(
1080 introspection_source_index_ids
1081 .into_keys()
1082 .map(|name| (cluster.id, name.to_string())),
1083 );
1084 }
1085 txn.insert_introspection_source_indexes(new_indexes, &HashSet::new())?;
1086 txn.remove_introspection_source_indexes(removed_indexes)?;
1087 Ok(())
1088}
1089
1090fn add_new_remove_old_builtin_roles_migration(
1091 txn: &mut mz_catalog::durable::Transaction<'_>,
1092) -> Result<(), mz_catalog::durable::CatalogError> {
1093 let mut durable_roles: BTreeMap<_, _> = txn
1094 .get_roles()
1095 .filter(|role| role.id.is_system() || role.id.is_predefined())
1096 .map(|role| (role.name.to_string(), role))
1097 .collect();
1098
1099 for builtin_role in BUILTIN_ROLES {
1101 if durable_roles.remove(builtin_role.name).is_none() {
1102 txn.insert_builtin_role(
1103 builtin_role.id,
1104 builtin_role.name.to_string(),
1105 builtin_role.attributes.clone(),
1106 RoleMembership::new(),
1107 RoleVars::default(),
1108 builtin_role.oid,
1109 )?;
1110 }
1111 }
1112
1113 let old_roles = durable_roles.values().map(|role| role.id).collect();
1115 txn.remove_roles(&old_roles)?;
1116
1117 Ok(())
1118}
1119
1120fn reconcile_builtin_cluster_replicas(
1147 txn: &mut Transaction<'_>,
1148 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1149 boot_ts: Timestamp,
1150) -> Result<(), AdapterError> {
1151 let builtin_cluster_names: BTreeSet<&str> = BUILTIN_CLUSTERS
1152 .iter()
1153 .map(|cluster| cluster.name)
1154 .collect();
1155
1156 let clusters: Vec<_> = txn
1160 .get_clusters()
1161 .filter(|cluster| {
1162 cluster.id.is_system() && builtin_cluster_names.contains(cluster.name.as_str())
1163 })
1164 .collect();
1165
1166 let builtin_cluster_ids: BTreeSet<ClusterId> =
1167 clusters.iter().map(|cluster| cluster.id).collect();
1168
1169 let mut replicas_by_cluster: BTreeMap<ClusterId, BTreeMap<String, ClusterReplica>> =
1178 BTreeMap::new();
1179 for replica in txn.get_cluster_replicas().filter(|replica| {
1180 builtin_cluster_ids.contains(&replica.cluster_id)
1181 && !matches!(
1182 replica.config.location,
1183 ReplicaLocation::Managed { internal: true, .. }
1184 )
1185 }) {
1186 replicas_by_cluster
1187 .entry(replica.cluster_id)
1188 .or_default()
1189 .insert(replica.name.clone(), replica);
1190 }
1191
1192 let mut to_drop: Vec<(String, ClusterReplica)> = Vec::new();
1193
1194 for cluster in clusters {
1195 let ClusterVariant::Managed(managed) = &cluster.config.variant else {
1199 continue;
1200 };
1201
1202 let bootstrap_config = builtin_cluster_config_map.get_config(&cluster.name)?;
1207 if bootstrap_config.replication_factor != managed.replication_factor {
1208 warn!(
1209 cluster = %cluster.name,
1210 configured_replication_factor = managed.replication_factor,
1211 bootstrap_replication_factor = bootstrap_config.replication_factor,
1212 "bootstrap replication factor is not applied to an already-existing \
1213 builtin cluster. Use ALTER CLUSTER ... SET (REPLICATION FACTOR ...) \
1214 to change it",
1215 );
1216 }
1217
1218 let mut surplus = replicas_by_cluster.remove(&cluster.id).unwrap_or_default();
1223 for index in 0..managed.replication_factor {
1224 let replica_name = managed_cluster_replica_name(index);
1225 if surplus.remove(&replica_name).is_some() {
1226 continue;
1227 }
1228
1229 let replica_id = txn.allocate_system_replica_id()?;
1233 txn.insert_cluster_replica_with_id(
1234 cluster.id,
1235 replica_id,
1236 &replica_name,
1237 managed_replica_config(managed),
1238 cluster.owner_id,
1244 )?;
1245 info!(
1246 cluster = %cluster.name, replica = %replica_name, %replica_id,
1247 "creating builtin cluster replica to match the cluster's replication factor"
1248 );
1249
1250 let audit_id = txn.allocate_audit_log_id()?;
1251 txn.insert_audit_log_event(VersionedEvent::new(
1252 audit_id,
1253 EventType::Create,
1254 ObjectType::ClusterReplica,
1255 EventDetails::CreateClusterReplicaV4(mz_audit_log::CreateClusterReplicaV4 {
1256 cluster_id: cluster.id.to_string(),
1257 cluster_name: cluster.name.clone(),
1258 replica_id: Some(replica_id.to_string()),
1259 replica_name,
1260 logical_size: managed.size.clone(),
1261 billed_as: None,
1262 internal: false,
1263 reason: CreateOrDropClusterReplicaReasonV1::System,
1264 scheduling_policies: None,
1265 }),
1266 None,
1267 boot_ts.into(),
1268 ));
1269 }
1270
1271 to_drop.extend(
1277 surplus
1278 .into_values()
1279 .map(|replica| (cluster.name.clone(), replica)),
1280 );
1281 }
1282
1283 let drop_ids = to_drop
1286 .iter()
1287 .map(|(_cluster_name, replica)| replica.replica_id)
1288 .collect();
1289 txn.remove_cluster_replicas(&drop_ids)?;
1290
1291 for (cluster_name, replica) in to_drop {
1292 info!(
1293 cluster = %cluster_name, replica = %replica.name, replica_id = %replica.replica_id,
1294 "dropping builtin cluster replica not called for by the cluster's replication factor"
1295 );
1296
1297 let audit_id = txn.allocate_audit_log_id()?;
1298 txn.insert_audit_log_event(VersionedEvent::new(
1299 audit_id,
1300 EventType::Drop,
1301 ObjectType::ClusterReplica,
1302 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1303 cluster_id: replica.cluster_id.to_string(),
1304 cluster_name,
1305 replica_id: Some(replica.replica_id.to_string()),
1306 replica_name: replica.name,
1307 reason: CreateOrDropClusterReplicaReasonV1::System,
1308 scheduling_policies: None,
1309 }),
1310 None,
1311 boot_ts.into(),
1312 ));
1313 }
1314
1315 Ok(())
1316}
1317
1318fn managed_replica_config(managed: &ClusterVariantManaged) -> ReplicaConfig {
1332 let ClusterVariantManaged {
1335 size,
1336 availability_zones,
1337 logging,
1338 arrangement_compression,
1339 replication_factor: _,
1340 optimizer_feature_overrides: _,
1341 schedule: _,
1342 auto_scaling_strategy: _,
1343 reconfiguration: _,
1344 burst: _,
1345 } = managed;
1346 ReplicaConfig {
1347 location: ReplicaLocation::Managed {
1348 size: size.clone(),
1349 availability_zones: availability_zones.clone(),
1350 billed_as: None,
1351 internal: false,
1352 pending: false,
1353 },
1354 logging: logging.clone(),
1355 arrangement_compression: *arrangement_compression,
1356 }
1357}
1358
1359fn remove_invalid_config_param_role_defaults_migration(
1366 txn: &mut Transaction<'_>,
1367) -> Result<(), AdapterError> {
1368 static BUILD_INFO: mz_build_info::BuildInfo = mz_build_info::build_info!();
1369
1370 let roles_to_migrate: BTreeMap<_, _> = txn
1371 .get_roles()
1372 .filter_map(|mut role| {
1373 let session_vars = SessionVars::new_unchecked(&BUILD_INFO, SYSTEM_USER.clone(), None);
1378
1379 let mut invalid_roles_vars = BTreeMap::new();
1381 for (name, value) in &role.vars.map {
1382 let Ok(session_var) = session_vars.inspect(name) else {
1384 invalid_roles_vars.insert(name.clone(), value.clone());
1385 continue;
1386 };
1387 if session_var.check(value.borrow()).is_err() {
1388 invalid_roles_vars.insert(name.clone(), value.clone());
1389 }
1390 }
1391
1392 if invalid_roles_vars.is_empty() {
1394 return None;
1395 }
1396
1397 tracing::warn!(?role, ?invalid_roles_vars, "removing invalid role vars");
1398
1399 for (name, _value) in invalid_roles_vars {
1401 role.vars.map.remove(&name);
1402 }
1403 Some(role)
1404 })
1405 .map(|role| (role.id, role))
1406 .collect();
1407
1408 txn.update_roles_without_auth(roles_to_migrate)?;
1409
1410 Ok(())
1411}
1412
1413fn remove_pending_cluster_replicas_migration(
1416 tx: &mut Transaction,
1417 boot_ts: mz_repr::Timestamp,
1418) -> Result<(), anyhow::Error> {
1419 let cluster_names: BTreeMap<_, _> = tx.get_clusters().map(|c| (c.id, c.name)).collect();
1421
1422 let occurred_at = boot_ts.into();
1423
1424 for replica in tx.get_cluster_replicas().collect::<Vec<_>>() {
1425 if let mz_catalog::durable::ReplicaLocation::Managed { pending: true, .. } =
1426 replica.config.location
1427 {
1428 let cluster_name = cluster_names
1429 .get(&replica.cluster_id)
1430 .cloned()
1431 .unwrap_or_else(|| "<unknown>".to_string());
1432
1433 info!(
1434 "removing pending cluster replica '{}' from cluster '{}'",
1435 replica.name, cluster_name,
1436 );
1437
1438 tx.remove_cluster_replica(replica.replica_id)?;
1439
1440 let audit_id = tx.allocate_audit_log_id()?;
1444 tx.insert_audit_log_event(VersionedEvent::new(
1445 audit_id,
1446 EventType::Drop,
1447 ObjectType::ClusterReplica,
1448 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1449 cluster_id: replica.cluster_id.to_string(),
1450 cluster_name,
1451 replica_id: Some(replica.replica_id.to_string()),
1452 replica_name: replica.name,
1453 reason: CreateOrDropClusterReplicaReasonV1::System,
1454 scheduling_policies: None,
1455 }),
1456 None,
1457 occurred_at,
1458 ));
1459 }
1460 }
1461 Ok(())
1462}
1463
1464fn default_logging_config() -> ReplicaLogging {
1465 ReplicaLogging {
1466 log_logging: false,
1467 interval: Some(Duration::from_secs(1)),
1468 }
1469}
1470
1471#[derive(Debug)]
1472pub struct BuiltinBootstrapClusterConfigMap {
1473 pub system_cluster: BootstrapBuiltinClusterConfig,
1475 pub catalog_server_cluster: BootstrapBuiltinClusterConfig,
1477 pub probe_cluster: BootstrapBuiltinClusterConfig,
1479 pub support_cluster: BootstrapBuiltinClusterConfig,
1481 pub analytics_cluster: BootstrapBuiltinClusterConfig,
1483}
1484
1485impl BuiltinBootstrapClusterConfigMap {
1486 fn get_config(
1488 &self,
1489 cluster_name: &str,
1490 ) -> Result<BootstrapBuiltinClusterConfig, mz_catalog::durable::CatalogError> {
1491 let cluster_config = if cluster_name == mz_catalog::builtin::MZ_SYSTEM_CLUSTER.name {
1492 &self.system_cluster
1493 } else if cluster_name == mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER.name {
1494 &self.catalog_server_cluster
1495 } else if cluster_name == mz_catalog::builtin::MZ_PROBE_CLUSTER.name {
1496 &self.probe_cluster
1497 } else if cluster_name == mz_catalog::builtin::MZ_SUPPORT_CLUSTER.name {
1498 &self.support_cluster
1499 } else if cluster_name == mz_catalog::builtin::MZ_ANALYTICS_CLUSTER.name {
1500 &self.analytics_cluster
1501 } else {
1502 return Err(mz_catalog::durable::CatalogError::Catalog(
1503 SqlCatalogError::UnexpectedBuiltinCluster(cluster_name.to_owned()),
1504 ));
1505 };
1506 Ok(cluster_config.clone())
1507 }
1508}
1509
1510pub(crate) fn into_consolidatable_updates_startup(
1527 updates: Vec<StateUpdate>,
1528 ts: Timestamp,
1529) -> Vec<(BootstrapStateUpdateKind, Timestamp, Diff)> {
1530 updates
1531 .into_iter()
1532 .map(|StateUpdate { kind, ts: _, diff }| {
1533 let kind: BootstrapStateUpdateKind = kind
1534 .try_into()
1535 .unwrap_or_else(|e| panic!("temporary items do not exist during bootstrap: {e:?}"));
1536 (kind, ts, Diff::from(diff))
1537 })
1538 .collect()
1539}
1540
1541#[cfg(test)]
1542mod tests {
1543 use mz_catalog::durable::ClusterVariantManaged;
1544
1545 use super::*;
1546
1547 #[mz_ore::test]
1551 fn test_managed_replica_config_derives_every_shared_field() {
1552 let managed = ClusterVariantManaged {
1553 size: "somesize".into(),
1554 availability_zones: vec!["az1".into(), "az2".into()],
1555 logging: ReplicaLogging {
1556 log_logging: true,
1557 interval: Some(Duration::from_millis(10)),
1558 },
1559 arrangement_compression: true,
1560 replication_factor: 3,
1561 optimizer_feature_overrides: Default::default(),
1562 schedule: Default::default(),
1563 auto_scaling_strategy: None,
1564 reconfiguration: None,
1565 burst: None,
1566 };
1567
1568 let config = managed_replica_config(&managed);
1569
1570 let ReplicaConfig {
1573 location,
1574 logging,
1575 arrangement_compression,
1576 } = config;
1577
1578 assert_eq!(logging, managed.logging);
1579 assert_eq!(arrangement_compression, managed.arrangement_compression);
1580 match location {
1581 ReplicaLocation::Managed {
1582 size,
1583 availability_zones,
1584 billed_as,
1585 internal,
1586 pending,
1587 } => {
1588 assert_eq!(size, managed.size);
1589 assert_eq!(availability_zones, managed.availability_zones);
1590 assert_eq!(billed_as, None);
1594 assert!(!internal);
1595 assert!(!pending);
1596 }
1597 ReplicaLocation::Unmanaged { .. } => panic!("expected a managed location"),
1598 }
1599 }
1600}