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::builtin::{
29 BUILTIN_CLUSTERS, BUILTIN_PREFIXES, BUILTIN_ROLES, BUILTINS, Builtin, Fingerprint,
30 MZ_CATALOG_RAW, RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL,
31};
32use mz_catalog::config::StateConfig;
33use mz_catalog::durable::objects::{
34 SystemObjectDescription, SystemObjectMapping, SystemObjectUniqueIdentifier,
35};
36use mz_catalog::durable::{
37 ClusterReplica, ClusterVariant, ClusterVariantManaged, ReplicaConfig, ReplicaLocation,
38 Transaction, managed_cluster_replica_name,
39};
40use mz_catalog::expr_cache::{
41 ExpressionCacheConfig, ExpressionCacheHandle, GlobalExpressions, LocalExpressions,
42 latest_item_version,
43};
44use mz_catalog::memory::error::{Error, ErrorKind};
45use mz_catalog::memory::objects::{
46 CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate, StateUpdateKind,
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, RelationVersion, 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 semver::Version;
66use tracing::{Instrument, info, warn};
67use uuid::Uuid;
68
69use crate::AdapterError;
71use crate::catalog::migrate::{self, get_migration_version, set_migration_version};
72use crate::catalog::state::LocalExpressionCache;
73use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState, Config, is_reserved_name};
74
75pub struct InitializeStateResult {
76 pub state: CatalogState,
78 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
87 pub new_builtin_collections: BTreeSet<GlobalId>,
89 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
91 pub last_seen_version: Option<Version>,
98 pub expr_cache_handle: Option<ExpressionCacheHandle>,
100 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
102 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
104}
105
106pub struct OpenCatalogResult {
107 pub catalog: Catalog,
109 pub last_seen_version: Option<Version>,
111 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
114 pub new_builtin_collections: BTreeSet<GlobalId>,
116 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
118 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
120 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
122}
123
124impl Catalog {
125 pub async fn initialize_state<'a>(
129 config: StateConfig,
130 storage: &'a mut Box<dyn mz_catalog::durable::DurableCatalogState>,
131 ) -> Result<InitializeStateResult, AdapterError> {
132 for builtin_role in BUILTIN_ROLES {
133 assert!(
134 is_reserved_name(builtin_role.name),
135 "builtin role {builtin_role:?} must start with one of the following prefixes {}",
136 BUILTIN_PREFIXES.join(", ")
137 );
138 }
139 for builtin_cluster in BUILTIN_CLUSTERS {
140 assert!(
141 is_reserved_name(builtin_cluster.name),
142 "builtin cluster {builtin_cluster:?} must start with one of the following prefixes {}",
143 BUILTIN_PREFIXES.join(", ")
144 );
145 }
146
147 let mut system_configuration = SystemVars::new().set_unsafe(config.unsafe_mode);
148 if config.all_features {
149 system_configuration.enable_all_feature_flags_by_default();
150 }
151
152 let mut state = CatalogState {
153 database_by_name: imbl::OrdMap::new(),
154 database_by_id: imbl::OrdMap::new(),
155 entry_by_id: imbl::OrdMap::new(),
156 entry_by_global_id: imbl::OrdMap::new(),
157 notices_by_dep_id: imbl::OrdMap::new(),
158 ambient_schemas_by_name: imbl::OrdMap::new(),
159 ambient_schemas_by_id: imbl::OrdMap::new(),
160 clusters_by_name: imbl::OrdMap::new(),
161 clusters_by_id: imbl::OrdMap::new(),
162 roles_by_name: imbl::OrdMap::new(),
163 roles_by_id: imbl::OrdMap::new(),
164 network_policies_by_id: imbl::OrdMap::new(),
165 role_auth_by_id: imbl::OrdMap::new(),
166 network_policies_by_name: imbl::OrdMap::new(),
167 system_configuration: Arc::new(system_configuration),
168 scoped_system_parameters: Default::default(),
169 default_privileges: Arc::new(DefaultPrivileges::default()),
170 system_privileges: Arc::new(PrivilegeMap::default()),
171 comments: Arc::new(CommentsMap::default()),
172 source_references: imbl::OrdMap::new(),
173 storage_metadata: Arc::new(StorageMetadata::default()),
174 temporary_namespaces: Default::default(),
175 mock_authentication_nonce: Default::default(),
176 config: mz_sql::catalog::CatalogConfig {
177 start_time: to_datetime((config.now)()),
178 start_instant: Instant::now(),
179 nonce: rand::random(),
180 environment_id: config.environment_id,
181 session_id: Uuid::new_v4(),
182 build_info: config.build_info,
183 now: config.now.clone(),
184 connection_context: config.connection_context,
185 aws_account_id: config
186 .aws_principal_context
187 .as_ref()
188 .map(|c| c.aws_account_id.clone()),
189 helm_chart_version: config.helm_chart_version,
190 },
191 cluster_replica_sizes: config.cluster_replica_sizes,
192 availability_zones: config.availability_zones,
193 egress_addresses: config.egress_addresses,
194 aws_principal_context: config.aws_principal_context,
195 aws_privatelink_availability_zones: config.aws_privatelink_availability_zones,
196 http_host_name: config.http_host_name,
197 license_key: config.license_key,
198 };
199
200 let deploy_generation = storage.get_deployment_generation().await?;
201
202 let mut updates: Vec<_> = storage.sync_to_current_updates().await?;
203 assert!(!updates.is_empty(), "initial catalog snapshot is missing");
204 let mut txn = storage.transaction().await?;
205
206 let new_builtin_collections = {
208 migrate::durable_migrate(
209 &mut txn,
210 state.config.environment_id.organization_id(),
211 config.boot_ts,
212 )?;
213 if let Some(remote_system_parameters) = config.remote_system_parameters {
216 for (name, value) in remote_system_parameters {
217 txn.upsert_system_config(&name, value)?;
218 }
219 txn.set_system_config_synced_once()?;
220 }
221 let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?;
223 let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap {
224 system_cluster: config.builtin_system_cluster_config,
225 catalog_server_cluster: config.builtin_catalog_server_cluster_config,
226 probe_cluster: config.builtin_probe_cluster_config,
227 support_cluster: config.builtin_support_cluster_config,
228 analytics_cluster: config.builtin_analytics_cluster_config,
229 };
230 add_new_remove_old_builtin_clusters_migration(
231 &mut txn,
232 &builtin_bootstrap_cluster_config_map,
233 config.boot_ts,
234 )?;
235 add_new_remove_old_builtin_introspection_source_migration(&mut txn)?;
236 reconcile_builtin_cluster_replicas(
237 &mut txn,
238 &builtin_bootstrap_cluster_config_map,
239 config.boot_ts,
240 )?;
241 add_new_remove_old_builtin_roles_migration(&mut txn)?;
242 remove_invalid_config_param_role_defaults_migration(&mut txn)?;
243 remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?;
244
245 new_builtin_collections
246 };
247
248 let op_updates = txn.get_and_commit_op_updates();
249 updates.extend(op_updates);
250
251 let mut builtin_table_updates = Vec::new();
252
253 {
255 for (name, value) in config.system_parameter_defaults {
258 match state.set_system_configuration_default(&name, VarInput::Flat(&value)) {
259 Ok(_) => (),
260 Err(Error {
261 kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
262 }) => {
263 warn!(%name, "cannot load unknown system parameter from catalog storage to set default parameter");
264 }
265 Err(e) => return Err(e.into()),
266 };
267 }
268 }
269
270 let mut updates = into_consolidatable_updates_startup(updates, config.boot_ts);
273 differential_dataflow::consolidation::consolidate_updates(&mut updates);
274 soft_assert_no_log!(
275 updates.iter().all(|(_, _, diff)| *diff == Diff::ONE),
276 "consolidated updates should be positive during startup: {updates:?}"
277 );
278
279 let mut pre_item_updates = Vec::new();
280 let mut system_item_updates = Vec::new();
281 let mut item_updates = Vec::new();
282 let mut post_item_updates = Vec::new();
283 let mut audit_log_updates = Vec::new();
284 for (kind, ts, diff) in updates {
285 match kind {
286 StateUpdateKind::Role(_)
287 | StateUpdateKind::RoleAuth(_)
288 | StateUpdateKind::Database(_)
289 | StateUpdateKind::Schema(_)
290 | StateUpdateKind::DefaultPrivilege(_)
291 | StateUpdateKind::SystemPrivilege(_)
292 | StateUpdateKind::SystemConfiguration(_)
293 | StateUpdateKind::ClusterSystemConfiguration(_)
294 | StateUpdateKind::ReplicaSystemConfiguration(_)
295 | StateUpdateKind::Cluster(_)
296 | StateUpdateKind::NetworkPolicy(_)
297 | StateUpdateKind::ClusterReplica(_) => pre_item_updates.push(StateUpdate {
298 kind,
299 ts,
300 diff: diff.try_into().expect("valid diff"),
301 }),
302 StateUpdateKind::IntrospectionSourceIndex(_)
303 | StateUpdateKind::SystemObjectMapping(_) => {
304 system_item_updates.push(StateUpdate {
305 kind,
306 ts,
307 diff: diff.try_into().expect("valid diff"),
308 })
309 }
310 StateUpdateKind::Item(_) => item_updates.push(StateUpdate {
311 kind,
312 ts,
313 diff: diff.try_into().expect("valid diff"),
314 }),
315 StateUpdateKind::Comment(_)
316 | StateUpdateKind::StorageCollectionMetadata(_)
317 | StateUpdateKind::SourceReferences(_)
318 | StateUpdateKind::UnfinalizedShard(_) => {
319 post_item_updates.push((kind, ts, diff));
320 }
321 StateUpdateKind::AuditLog(_) => {
322 audit_log_updates.push(StateUpdate {
323 kind,
324 ts,
325 diff: diff.try_into().expect("valid diff"),
326 });
327 }
328 }
329 }
330
331 let (builtin_table_update, _catalog_updates) = state
332 .apply_updates(pre_item_updates, &mut LocalExpressionCache::Closed)
333 .await;
334 builtin_table_updates.extend(builtin_table_update);
335
336 state.system_config().sync_dyncfgs();
347
348 {
352 if let Some(password) = config.external_login_password_mz_system {
353 let role_auth = RoleAuth {
354 role_id: MZ_SYSTEM_ROLE_ID,
355 password_hash: Some(
358 scram256_hash(&password, &NonZeroU32::new(600_000).expect("known valid"))
359 .map_err(|_| {
360 AdapterError::Internal("Failed to hash mz_system password.".to_owned())
361 })?,
362 ),
363 updated_at: SYSTEM_TIME(),
364 };
365 state
366 .role_auth_by_id
367 .insert(MZ_SYSTEM_ROLE_ID, role_auth.clone());
368 let builtin_table_update = state.generate_builtin_table_update(
369 mz_catalog::memory::objects::StateUpdateKind::RoleAuth(role_auth.into()),
370 mz_catalog::memory::objects::StateDiff::Addition,
371 );
372 builtin_table_updates.extend(builtin_table_update);
373 }
374 }
375
376 let expr_cache_start = Instant::now();
377 info!("startup: coordinator init: catalog open: expr cache open beginning");
378 let enable_expr_cache_dyncfg = ENABLE_EXPRESSION_CACHE.get(state.system_config().dyncfgs());
381 let expr_cache_enabled = config
382 .enable_expression_cache_override
383 .unwrap_or(enable_expr_cache_dyncfg);
384 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) = if expr_cache_enabled {
385 info!(
386 ?config.enable_expression_cache_override,
387 ?enable_expr_cache_dyncfg,
388 "using expression cache for startup"
389 );
390 let current_items = txn
391 .get_items()
392 .flat_map(|item| {
393 let item_version = latest_item_version(&item.extra_versions);
394 std::iter::once(item.global_id)
395 .chain(item.extra_versions.into_values())
396 .map(move |gid| (gid, item_version))
397 })
398 .chain(
399 txn.get_system_object_mappings()
400 .map(|som| (som.unique_identifier.global_id, RelationVersion::root())),
401 )
402 .collect();
403 let dyncfgs = config.persist_client.dyncfgs().clone();
404 let build_version = if config.build_info.is_dev() {
405 config
408 .build_info
409 .semver_version_build()
410 .expect("build ID is not available on your platform!")
411 } else {
412 config.build_info.semver_version()
413 };
414 let expr_cache_config = ExpressionCacheConfig {
415 build_version,
416 shard_id: txn
417 .get_expression_cache_shard()
418 .expect("expression cache shard should exist for opened catalogs"),
419 persist: config.persist_client,
420 current_items,
421 remove_prior_versions: !config.read_only,
422 compact_shard: config.read_only,
423 dyncfgs,
424 };
425 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) =
426 ExpressionCacheHandle::spawn_expression_cache(expr_cache_config).await;
427 (
428 Some(expr_cache_handle),
429 cached_local_exprs,
430 cached_global_exprs,
431 )
432 } else {
433 (None, BTreeMap::new(), BTreeMap::new())
434 };
435 let mut local_expr_cache = LocalExpressionCache::new(cached_local_exprs);
436 info!(
437 "startup: coordinator init: catalog open: expr cache open complete in {:?}",
438 expr_cache_start.elapsed()
439 );
440
441 let (builtin_table_update, _catalog_updates) = state
447 .apply_updates(system_item_updates, &mut local_expr_cache)
448 .await;
449 builtin_table_updates.extend(builtin_table_update);
450
451 let last_seen_version = get_migration_version(&txn);
452
453 let mz_authentication_mock_nonce =
454 txn.get_authentication_mock_nonce().ok_or_else(|| {
455 Error::new(ErrorKind::SettingError("authentication nonce".to_string()))
456 })?;
457
458 state.mock_authentication_nonce = Some(mz_authentication_mock_nonce);
459
460 let (builtin_table_update, _catalog_updates) = if !config.skip_migrations {
462 let migrate_result = migrate::migrate(
463 &mut state,
464 &mut txn,
465 &mut local_expr_cache,
466 item_updates,
467 config.now,
468 config.boot_ts,
469 )
470 .await
471 .map_err(|e| {
472 Error::new(ErrorKind::FailedCatalogMigration {
473 last_seen_version: last_seen_version
474 .as_ref()
475 .map_or_else(|| "new".to_string(), |v| v.to_string()),
476 this_version: config.build_info.version,
477 cause: e.to_string(),
478 })
479 })?;
480 if !migrate_result.post_item_updates.is_empty() {
481 post_item_updates.extend(migrate_result.post_item_updates);
484 if let Some(max_ts) = post_item_updates.iter().map(|(_, ts, _)| ts).max().cloned() {
486 for (_, ts, _) in &mut post_item_updates {
487 *ts = max_ts;
488 }
489 }
490 differential_dataflow::consolidation::consolidate_updates(&mut post_item_updates);
491 }
492
493 (
494 migrate_result.builtin_table_updates,
495 migrate_result.catalog_updates,
496 )
497 } else {
498 state
499 .apply_updates(item_updates, &mut local_expr_cache)
500 .await
501 };
502 builtin_table_updates.extend(builtin_table_update);
503
504 let post_item_updates = post_item_updates
505 .into_iter()
506 .map(|(kind, ts, diff)| StateUpdate {
507 kind,
508 ts,
509 diff: diff.try_into().expect("valid diff"),
510 })
511 .collect();
512 let (builtin_table_update, _catalog_updates) = state
513 .apply_updates(post_item_updates, &mut local_expr_cache)
514 .await;
515 builtin_table_updates.extend(builtin_table_update);
516
517 for audit_log_update in audit_log_updates {
521 builtin_table_updates.extend(
522 state.generate_builtin_table_update(audit_log_update.kind, audit_log_update.diff),
523 );
524 }
525
526 let schema_migration_result = builtin_schema_migration::run(
528 config.build_info,
529 deploy_generation,
530 &mut txn,
531 config.builtin_item_migration_config,
532 )
533 .await?;
534
535 let state_updates = txn.get_and_commit_op_updates();
536
537 let (table_updates, _catalog_updates) = state
543 .apply_updates(state_updates, &mut local_expr_cache)
544 .await;
545 builtin_table_updates.extend(table_updates);
546 let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates);
547
548 set_migration_version(&mut txn, config.build_info.semver_version())?;
550
551 txn.commit(config.boot_ts).await?;
552
553 schema_migration_result.cleanup_action.await;
555
556 Ok(InitializeStateResult {
557 state,
558 migrated_storage_collections_0dt: schema_migration_result.replaced_items,
559 new_builtin_collections: new_builtin_collections.into_iter().collect(),
560 builtin_table_updates,
561 last_seen_version,
562 expr_cache_handle,
563 cached_global_exprs,
564 uncached_local_exprs: local_expr_cache.into_uncached_exprs(),
565 })
566 }
567
568 #[instrument(name = "catalog::open")]
579 pub fn open(config: Config<'_>) -> BoxFuture<'static, Result<OpenCatalogResult, AdapterError>> {
580 async move {
581 let mut storage = config.storage;
582
583 let InitializeStateResult {
584 state,
585 migrated_storage_collections_0dt,
586 new_builtin_collections,
587 mut builtin_table_updates,
588 last_seen_version,
589 expr_cache_handle,
590 cached_global_exprs,
591 uncached_local_exprs,
592 } =
593 Self::initialize_state(config.state, &mut storage)
597 .instrument(tracing::info_span!("catalog::initialize_state"))
598 .boxed()
599 .await?;
600
601 let catalog = Catalog {
602 state,
603 expr_cache_handle,
604 transient_revision: 1,
605 shared_transient_revision: Arc::new(AtomicU64::new(1)),
606 storage: Arc::new(tokio::sync::Mutex::new(storage)),
607 };
608
609 for (op, func) in OP_IMPLS.iter() {
612 match func {
613 mz_sql::func::Func::Scalar(impls) => {
614 for imp in impls {
615 builtin_table_updates.push(catalog.state.resolve_builtin_table_update(
616 catalog.state.pack_op_update(op, imp.details(), Diff::ONE),
617 ));
618 }
619 }
620 _ => unreachable!("all operators must be scalar functions"),
621 }
622 }
623
624 for ip in &catalog.state.egress_addresses {
625 builtin_table_updates.push(
626 catalog
627 .state
628 .resolve_builtin_table_update(catalog.state.pack_egress_ip_update(ip)?),
629 );
630 }
631
632 if !catalog.state.license_key.id.is_empty() {
633 builtin_table_updates.push(
634 catalog.state.resolve_builtin_table_update(
635 catalog
636 .state
637 .pack_license_key_update(&catalog.state.license_key)?,
638 ),
639 );
640 }
641
642 catalog.storage().await.mark_bootstrap_complete().await;
643
644 Ok(OpenCatalogResult {
645 catalog,
646 last_seen_version,
647 migrated_storage_collections_0dt,
648 new_builtin_collections,
649 builtin_table_updates,
650 cached_global_exprs,
651 uncached_local_exprs,
652 })
653 }
654 .instrument(tracing::info_span!("catalog::open"))
655 .boxed()
656 }
657
658 async fn initialize_storage_state(
665 &mut self,
666 storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
667 ) -> Result<(), mz_catalog::durable::CatalogError> {
668 let collections = self
669 .entries()
670 .filter(|entry| entry.item().is_storage_collection())
671 .flat_map(|entry| entry.global_ids())
672 .collect();
673
674 let mut state = self.state.clone();
677
678 let mut storage = self.storage().await;
679 let shard_id = storage.shard_id();
680 let mut txn = storage.transaction().await?;
681
682 let item_id = self.resolve_builtin_storage_collection(&MZ_CATALOG_RAW);
685 let global_id = self.get_entry(&item_id).latest_global_id();
686 match txn.get_collection_metadata().get(&global_id) {
687 None => {
688 txn.insert_collection_metadata([(global_id, shard_id)].into())
689 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
690 }
691 Some(id) => assert_eq!(*id, shard_id),
692 }
693
694 storage_collections
695 .initialize_state(&mut txn, collections)
696 .await
697 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
698
699 let updates = txn.get_and_commit_op_updates();
700 let (builtin_updates, catalog_updates) = state
701 .apply_updates(updates, &mut LocalExpressionCache::Closed)
702 .await;
703 assert!(
704 builtin_updates.is_empty(),
705 "storage is not allowed to generate catalog changes that would cause changes to builtin tables"
706 );
707 assert!(
708 catalog_updates.is_empty(),
709 "storage is not allowed to generate catalog changes that would change the catalog or controller state"
710 );
711 let commit_ts = txn.upper();
712 txn.commit(commit_ts).await?;
713 drop(storage);
714
715 self.state = state;
717 Ok(())
718 }
719
720 pub async fn initialize_controller(
723 &mut self,
724 config: mz_controller::ControllerConfig,
725 envd_epoch: core::num::NonZeroI64,
726 read_only: bool,
727 ) -> Result<mz_controller::Controller, mz_catalog::durable::CatalogError> {
728 let controller_start = Instant::now();
729 info!("startup: controller init: beginning");
730
731 let controller = {
732 let mut storage = self.storage().await;
733 let mut tx = storage.transaction().await?;
734 mz_controller::prepare_initialization(&mut tx)
735 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
736 let updates = tx.get_and_commit_op_updates();
737 assert!(
738 updates.is_empty(),
739 "initializing controller should not produce updates: {updates:?}"
740 );
741 let commit_ts = tx.upper();
742 tx.commit(commit_ts).await?;
743
744 let read_only_tx = storage.transaction().await?;
745
746 mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await
747 };
748
749 self.initialize_storage_state(&controller.storage_collections)
750 .await?;
751
752 info!(
753 "startup: controller init: complete in {:?}",
754 controller_start.elapsed()
755 );
756
757 Ok(controller)
758 }
759
760 pub async fn expire(self) {
762 if let Some(storage) = Arc::into_inner(self.storage) {
765 let storage = storage.into_inner();
766 storage.expire().await;
767 }
768 }
769}
770
771impl CatalogState {
772 fn set_system_configuration_default(
774 &mut self,
775 name: &str,
776 value: VarInput,
777 ) -> Result<(), Error> {
778 Ok(Arc::make_mut(&mut self.system_configuration).set_default(name, value)?)
779 }
780}
781
782fn add_new_remove_old_builtin_items_migration(
786 txn: &mut mz_catalog::durable::Transaction<'_>,
787) -> Result<Vec<GlobalId>, mz_catalog::durable::CatalogError> {
788 let mut new_builtin_mappings = Vec::new();
789 let mut builtin_descs = HashSet::new();
791
792 let mut builtins = Vec::new();
795 for builtin in BUILTINS::iter() {
796 let desc = SystemObjectDescription {
797 schema_name: builtin.schema().to_string(),
798 object_type: builtin.catalog_item_type(),
799 object_name: builtin.name().to_string(),
800 };
801 if !builtin_descs.insert(desc.clone()) {
803 panic!(
804 "duplicate builtin description: {:?}, {:?}",
805 SystemObjectDescription {
806 schema_name: builtin.schema().to_string(),
807 object_type: builtin.catalog_item_type(),
808 object_name: builtin.name().to_string(),
809 },
810 builtin
811 );
812 }
813 builtins.push((desc, builtin));
814 }
815
816 let mut system_object_mappings: BTreeMap<_, _> = txn
817 .get_system_object_mappings()
818 .map(|system_object_mapping| {
819 (
820 system_object_mapping.description.clone(),
821 system_object_mapping,
822 )
823 })
824 .collect();
825
826 let (existing_builtins, new_builtins): (Vec<_>, Vec<_>) =
827 builtins.into_iter().partition_map(|(desc, builtin)| {
828 let fingerprint = match builtin.runtime_alterable() {
829 false => builtin.fingerprint(),
830 true => RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into(),
831 };
832 match system_object_mappings.remove(&desc) {
833 Some(system_object_mapping) => {
834 Either::Left((builtin, system_object_mapping, fingerprint))
835 }
836 None => Either::Right((builtin, fingerprint)),
837 }
838 });
839 let new_builtin_ids = txn.allocate_system_item_ids(usize_to_u64(new_builtins.len()))?;
840 let new_builtins: Vec<_> = new_builtins
841 .into_iter()
842 .zip_eq(new_builtin_ids.clone())
843 .collect();
844
845 for ((builtin, fingerprint), (catalog_id, global_id)) in new_builtins.iter().cloned() {
847 new_builtin_mappings.push(SystemObjectMapping {
848 description: SystemObjectDescription {
849 schema_name: builtin.schema().to_string(),
850 object_type: builtin.catalog_item_type(),
851 object_name: builtin.name().to_string(),
852 },
853 unique_identifier: SystemObjectUniqueIdentifier {
854 catalog_id,
855 global_id,
856 fingerprint,
857 },
858 });
859
860 let handled_runtime_alterable = match builtin {
866 Builtin::Connection(c) if c.runtime_alterable => {
867 let mut acl_items = vec![rbac::owner_privilege(
868 mz_sql::catalog::ObjectType::Connection,
869 c.owner_id.clone(),
870 )];
871 acl_items.extend_from_slice(c.access);
872 let versions = BTreeMap::new();
874
875 txn.insert_item(
876 catalog_id,
877 c.oid,
878 global_id,
879 mz_catalog::durable::initialize::resolve_system_schema(c.schema).id,
880 c.name,
881 c.sql.into(),
882 *c.owner_id,
883 acl_items,
884 versions,
885 None,
886 )?;
887 true
888 }
889 _ => false,
890 };
891 assert_eq!(
892 builtin.runtime_alterable(),
893 handled_runtime_alterable,
894 "runtime alterable object was not handled by migration",
895 );
896 }
897 txn.set_system_object_mappings(new_builtin_mappings)?;
898
899 let builtins_with_catalog_ids = existing_builtins
901 .iter()
902 .map(|(b, m, _)| (*b, m.unique_identifier.catalog_id))
903 .chain(
904 new_builtins
905 .into_iter()
906 .map(|((b, _), (catalog_id, _))| (b, catalog_id)),
907 );
908
909 for (builtin, id) in builtins_with_catalog_ids {
910 let (comment_id, desc, comments) = match builtin {
911 Builtin::Source(s) => (CommentObjectId::Source(id), &s.desc, &s.column_comments),
912 Builtin::View(v) => (CommentObjectId::View(id), &v.desc, &v.column_comments),
913 Builtin::Table(t) => (CommentObjectId::Table(id), &t.desc, &t.column_comments),
914 Builtin::MaterializedView(mv) => (
915 CommentObjectId::MaterializedView(id),
916 &mv.desc,
917 &mv.column_comments,
918 ),
919 Builtin::Log(_)
920 | Builtin::Type(_)
921 | Builtin::Func(_)
922 | Builtin::Index(_)
923 | Builtin::Connection(_) => continue,
924 };
925 txn.drop_comments(&BTreeSet::from_iter([
930 CommentObjectId::Table(id),
931 CommentObjectId::View(id),
932 CommentObjectId::MaterializedView(id),
933 CommentObjectId::Source(id),
934 ]))?;
935
936 let mut comments = comments.clone();
937 for (col_idx, name) in desc.iter_names().enumerate() {
938 if let Some(comment) = comments.remove(name.as_str()) {
939 txn.update_comment(comment_id, Some(col_idx + 1), Some(comment.to_owned()))?;
941 }
942 }
943 assert!(
944 comments.is_empty(),
945 "builtin object contains dangling comments that don't correspond to columns {comments:?}"
946 );
947 }
948
949 let mut deleted_system_objects = BTreeSet::new();
952 let mut deleted_runtime_alterable_system_ids = BTreeSet::new();
953 let mut deleted_comments = BTreeSet::new();
954 for (desc, mapping) in system_object_mappings {
955 deleted_system_objects.insert(mapping.description);
956 if mapping.unique_identifier.fingerprint == RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL {
957 deleted_runtime_alterable_system_ids.insert(mapping.unique_identifier.catalog_id);
958 }
959
960 let id = mapping.unique_identifier.catalog_id;
961 let comment_id = match desc.object_type {
962 CatalogItemType::Table => CommentObjectId::Table(id),
963 CatalogItemType::Source => CommentObjectId::Source(id),
964 CatalogItemType::View => CommentObjectId::View(id),
965 CatalogItemType::MaterializedView => CommentObjectId::MaterializedView(id),
966 CatalogItemType::Sink
967 | CatalogItemType::MetricSink
968 | CatalogItemType::Index
969 | CatalogItemType::Type
970 | CatalogItemType::Func
971 | CatalogItemType::Secret
972 | CatalogItemType::Connection => continue,
973 };
974 deleted_comments.insert(comment_id);
975 }
976 let delete_exceptions: HashSet<SystemObjectDescription> = [].into();
982 assert!(
986 deleted_system_objects
987 .iter()
988 .filter(|object| object.object_type != CatalogItemType::Index)
990 .all(
991 |deleted_object| is_unstable_schema(&deleted_object.schema_name)
992 || delete_exceptions.contains(deleted_object)
993 ),
994 "only objects in unstable schemas can be deleted, deleted objects: {:?}",
995 deleted_system_objects
996 );
997 txn.drop_comments(&deleted_comments)?;
998 txn.remove_items(&deleted_runtime_alterable_system_ids)?;
999 txn.remove_system_object_mappings(deleted_system_objects)?;
1000
1001 let new_builtin_collections = new_builtin_ids
1003 .into_iter()
1004 .map(|(_catalog_id, global_id)| global_id)
1005 .collect();
1006
1007 Ok(new_builtin_collections)
1008}
1009
1010fn add_new_remove_old_builtin_clusters_migration(
1011 txn: &mut mz_catalog::durable::Transaction<'_>,
1012 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1013 boot_ts: Timestamp,
1014) -> Result<(), mz_catalog::durable::CatalogError> {
1015 let mut durable_clusters: BTreeMap<_, _> = txn
1016 .get_clusters()
1017 .filter(|cluster| cluster.id.is_system())
1018 .map(|cluster| (cluster.name.to_string(), cluster))
1019 .collect();
1020
1021 for builtin_cluster in BUILTIN_CLUSTERS {
1023 if durable_clusters.remove(builtin_cluster.name).is_none() {
1024 let cluster_config = builtin_cluster_config_map.get_config(builtin_cluster.name)?;
1025
1026 let cluster_id = txn.insert_system_cluster(
1027 builtin_cluster.name,
1028 vec![],
1029 builtin_cluster.privileges.to_vec(),
1030 builtin_cluster.owner_id.to_owned(),
1031 mz_catalog::durable::ClusterConfig {
1032 variant: mz_catalog::durable::ClusterVariant::Managed(ClusterVariantManaged {
1033 size: cluster_config.size,
1034 availability_zones: vec![],
1035 replication_factor: cluster_config.replication_factor,
1036 logging: default_logging_config(),
1037 arrangement_compression: false,
1038 optimizer_feature_overrides: Default::default(),
1039 schedule: Default::default(),
1040 auto_scaling_strategy: None,
1041 reconfiguration: None,
1042 burst: None,
1043 }),
1044 workload_class: None,
1045 },
1046 &HashSet::new(),
1047 )?;
1048
1049 let audit_id = txn.allocate_audit_log_id()?;
1050 txn.insert_audit_log_event(VersionedEvent::new(
1051 audit_id,
1052 EventType::Create,
1053 ObjectType::Cluster,
1054 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1055 id: cluster_id.to_string(),
1056 name: builtin_cluster.name.to_string(),
1057 }),
1058 None,
1059 boot_ts.into(),
1060 ));
1061 }
1062 }
1063
1064 let old_clusters = durable_clusters
1066 .values()
1067 .map(|cluster| cluster.id)
1068 .collect();
1069 txn.remove_clusters(&old_clusters)?;
1070
1071 for (_name, cluster) in &durable_clusters {
1072 let audit_id = txn.allocate_audit_log_id()?;
1073 txn.insert_audit_log_event(VersionedEvent::new(
1074 audit_id,
1075 EventType::Drop,
1076 ObjectType::Cluster,
1077 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1078 id: cluster.id.to_string(),
1079 name: cluster.name.clone(),
1080 }),
1081 None,
1082 boot_ts.into(),
1083 ));
1084 }
1085
1086 Ok(())
1087}
1088
1089fn add_new_remove_old_builtin_introspection_source_migration(
1090 txn: &mut mz_catalog::durable::Transaction<'_>,
1091) -> Result<(), AdapterError> {
1092 let mut new_indexes = Vec::new();
1093 let mut removed_indexes = BTreeSet::new();
1094 for cluster in txn.get_clusters() {
1095 let mut introspection_source_index_ids = txn.get_introspection_source_indexes(cluster.id);
1096
1097 let mut new_logs = Vec::new();
1098
1099 for log in BUILTINS::logs() {
1100 if introspection_source_index_ids.remove(log.name).is_none() {
1101 new_logs.push(log);
1102 }
1103 }
1104
1105 for log in new_logs {
1106 let (item_id, gid) =
1107 Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant);
1108 new_indexes.push((cluster.id, log.name.to_string(), item_id, gid));
1109 }
1110
1111 removed_indexes.extend(
1114 introspection_source_index_ids
1115 .into_keys()
1116 .map(|name| (cluster.id, name.to_string())),
1117 );
1118 }
1119 txn.insert_introspection_source_indexes(new_indexes, &HashSet::new())?;
1120 txn.remove_introspection_source_indexes(removed_indexes)?;
1121 Ok(())
1122}
1123
1124fn add_new_remove_old_builtin_roles_migration(
1125 txn: &mut mz_catalog::durable::Transaction<'_>,
1126) -> Result<(), mz_catalog::durable::CatalogError> {
1127 let mut durable_roles: BTreeMap<_, _> = txn
1128 .get_roles()
1129 .filter(|role| role.id.is_system() || role.id.is_predefined())
1130 .map(|role| (role.name.to_string(), role))
1131 .collect();
1132
1133 for builtin_role in BUILTIN_ROLES {
1135 if durable_roles.remove(builtin_role.name).is_none() {
1136 txn.insert_builtin_role(
1137 builtin_role.id,
1138 builtin_role.name.to_string(),
1139 builtin_role.attributes.clone(),
1140 RoleMembership::new(),
1141 RoleVars::default(),
1142 builtin_role.oid,
1143 )?;
1144 }
1145 }
1146
1147 let old_roles = durable_roles.values().map(|role| role.id).collect();
1149 txn.remove_roles(&old_roles)?;
1150
1151 Ok(())
1152}
1153
1154fn reconcile_builtin_cluster_replicas(
1186 txn: &mut Transaction<'_>,
1187 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1188 boot_ts: Timestamp,
1189) -> Result<(), AdapterError> {
1190 let builtin_cluster_names: BTreeSet<&str> = BUILTIN_CLUSTERS
1191 .iter()
1192 .map(|cluster| cluster.name)
1193 .collect();
1194
1195 let clusters: Vec<_> = txn
1199 .get_clusters()
1200 .filter(|cluster| {
1201 cluster.id.is_system() && builtin_cluster_names.contains(cluster.name.as_str())
1202 })
1203 .collect();
1204
1205 let builtin_cluster_ids: BTreeSet<ClusterId> =
1206 clusters.iter().map(|cluster| cluster.id).collect();
1207
1208 let mut replicas_by_cluster: BTreeMap<ClusterId, BTreeMap<String, ClusterReplica>> =
1217 BTreeMap::new();
1218 for replica in txn.get_cluster_replicas().filter(|replica| {
1219 builtin_cluster_ids.contains(&replica.cluster_id)
1220 && !matches!(
1221 replica.config.location,
1222 ReplicaLocation::Managed { internal: true, .. }
1223 )
1224 }) {
1225 replicas_by_cluster
1226 .entry(replica.cluster_id)
1227 .or_default()
1228 .insert(replica.name.clone(), replica);
1229 }
1230
1231 let mut to_drop: Vec<(String, ClusterReplica)> = Vec::new();
1232
1233 for cluster in clusters {
1234 let ClusterVariant::Managed(managed) = &cluster.config.variant else {
1238 continue;
1239 };
1240
1241 let bootstrap_config = builtin_cluster_config_map.get_config(&cluster.name)?;
1246 if bootstrap_config.replication_factor != managed.replication_factor {
1247 warn!(
1248 cluster = %cluster.name,
1249 configured_replication_factor = managed.replication_factor,
1250 bootstrap_replication_factor = bootstrap_config.replication_factor,
1251 "bootstrap replication factor is not applied to an already-existing \
1252 builtin cluster. Use ALTER CLUSTER ... SET (REPLICATION FACTOR ...) \
1253 to change it",
1254 );
1255 }
1256
1257 let mut surplus = replicas_by_cluster.remove(&cluster.id).unwrap_or_default();
1262 for index in 0..managed.replication_factor {
1263 let replica_name = managed_cluster_replica_name(index);
1264 if surplus.remove(&replica_name).is_some() {
1265 continue;
1266 }
1267
1268 let replica_id = txn.allocate_system_replica_id()?;
1272 txn.insert_cluster_replica_with_id(
1273 cluster.id,
1274 replica_id,
1275 &replica_name,
1276 managed_replica_config(managed),
1277 cluster.owner_id,
1283 )?;
1284 info!(
1285 cluster = %cluster.name, replica = %replica_name, %replica_id,
1286 "creating builtin cluster replica to match the cluster's replication factor"
1287 );
1288
1289 let audit_id = txn.allocate_audit_log_id()?;
1290 txn.insert_audit_log_event(VersionedEvent::new(
1291 audit_id,
1292 EventType::Create,
1293 ObjectType::ClusterReplica,
1294 EventDetails::CreateClusterReplicaV4(mz_audit_log::CreateClusterReplicaV4 {
1295 cluster_id: cluster.id.to_string(),
1296 cluster_name: cluster.name.clone(),
1297 replica_id: Some(replica_id.to_string()),
1298 replica_name,
1299 logical_size: managed.size.clone(),
1300 billed_as: None,
1301 internal: false,
1302 reason: CreateOrDropClusterReplicaReasonV1::System,
1303 scheduling_policies: None,
1304 }),
1305 None,
1306 boot_ts.into(),
1307 ));
1308 }
1309
1310 to_drop.extend(
1316 surplus
1317 .into_values()
1318 .map(|replica| (cluster.name.clone(), replica)),
1319 );
1320 }
1321
1322 let drop_ids = to_drop
1325 .iter()
1326 .map(|(_cluster_name, replica)| replica.replica_id)
1327 .collect();
1328 txn.remove_cluster_replicas(&drop_ids)?;
1329
1330 for (cluster_name, replica) in to_drop {
1331 info!(
1332 cluster = %cluster_name, replica = %replica.name, replica_id = %replica.replica_id,
1333 "dropping builtin cluster replica not called for by the cluster's replication factor"
1334 );
1335
1336 let audit_id = txn.allocate_audit_log_id()?;
1337 txn.insert_audit_log_event(VersionedEvent::new(
1338 audit_id,
1339 EventType::Drop,
1340 ObjectType::ClusterReplica,
1341 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1342 cluster_id: replica.cluster_id.to_string(),
1343 cluster_name,
1344 replica_id: Some(replica.replica_id.to_string()),
1345 replica_name: replica.name,
1346 reason: CreateOrDropClusterReplicaReasonV1::System,
1347 scheduling_policies: None,
1348 }),
1349 None,
1350 boot_ts.into(),
1351 ));
1352 }
1353
1354 Ok(())
1355}
1356
1357fn managed_replica_config(managed: &ClusterVariantManaged) -> ReplicaConfig {
1371 let ClusterVariantManaged {
1374 size,
1375 availability_zones,
1376 logging,
1377 arrangement_compression,
1378 replication_factor: _,
1379 optimizer_feature_overrides: _,
1380 schedule: _,
1381 auto_scaling_strategy: _,
1382 reconfiguration: _,
1383 burst: _,
1384 } = managed;
1385 ReplicaConfig {
1386 location: ReplicaLocation::Managed {
1387 size: size.clone(),
1388 availability_zones: availability_zones.clone(),
1389 billed_as: None,
1390 internal: false,
1391 pending: false,
1392 },
1393 logging: logging.clone(),
1394 arrangement_compression: *arrangement_compression,
1395 }
1396}
1397
1398fn remove_invalid_config_param_role_defaults_migration(
1405 txn: &mut Transaction<'_>,
1406) -> Result<(), AdapterError> {
1407 static BUILD_INFO: mz_build_info::BuildInfo = mz_build_info::build_info!();
1408
1409 let roles_to_migrate: BTreeMap<_, _> = txn
1410 .get_roles()
1411 .filter_map(|mut role| {
1412 let session_vars = SessionVars::new_unchecked(&BUILD_INFO, SYSTEM_USER.clone(), None);
1417
1418 let mut invalid_roles_vars = BTreeMap::new();
1420 for (name, value) in &role.vars.map {
1421 let Ok(session_var) = session_vars.inspect(name) else {
1423 invalid_roles_vars.insert(name.clone(), value.clone());
1424 continue;
1425 };
1426 if session_var.check(value.borrow()).is_err() {
1427 invalid_roles_vars.insert(name.clone(), value.clone());
1428 }
1429 }
1430
1431 if invalid_roles_vars.is_empty() {
1433 return None;
1434 }
1435
1436 tracing::warn!(?role, ?invalid_roles_vars, "removing invalid role vars");
1437
1438 for (name, _value) in invalid_roles_vars {
1440 role.vars.map.remove(&name);
1441 }
1442 Some(role)
1443 })
1444 .map(|role| (role.id, role))
1445 .collect();
1446
1447 txn.update_roles_without_auth(roles_to_migrate)?;
1448
1449 Ok(())
1450}
1451
1452fn remove_pending_cluster_replicas_migration(
1461 tx: &mut Transaction,
1462 boot_ts: mz_repr::Timestamp,
1463) -> Result<(), anyhow::Error> {
1464 let cluster_names: BTreeMap<_, _> = tx.get_clusters().map(|c| (c.id, c.name)).collect();
1466
1467 let occurred_at = boot_ts.into();
1468
1469 for replica in tx.get_cluster_replicas().collect::<Vec<_>>() {
1470 if let mz_catalog::durable::ReplicaLocation::Managed { pending: true, .. } =
1471 replica.config.location
1472 {
1473 let cluster_name = cluster_names
1474 .get(&replica.cluster_id)
1475 .cloned()
1476 .unwrap_or_else(|| "<unknown>".to_string());
1477
1478 info!(
1479 "removing pending cluster replica '{}' from cluster '{}'",
1480 replica.name, cluster_name,
1481 );
1482
1483 tx.remove_cluster_replica(replica.replica_id)?;
1484
1485 let audit_id = tx.allocate_audit_log_id()?;
1489 tx.insert_audit_log_event(VersionedEvent::new(
1490 audit_id,
1491 EventType::Drop,
1492 ObjectType::ClusterReplica,
1493 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1494 cluster_id: replica.cluster_id.to_string(),
1495 cluster_name,
1496 replica_id: Some(replica.replica_id.to_string()),
1497 replica_name: replica.name,
1498 reason: CreateOrDropClusterReplicaReasonV1::System,
1499 scheduling_policies: None,
1500 }),
1501 None,
1502 occurred_at,
1503 ));
1504 }
1505 }
1506 Ok(())
1507}
1508
1509fn default_logging_config() -> ReplicaLogging {
1510 ReplicaLogging {
1511 log_logging: false,
1512 interval: Some(Duration::from_secs(1)),
1513 }
1514}
1515
1516#[derive(Debug)]
1517pub struct BuiltinBootstrapClusterConfigMap {
1518 pub system_cluster: BootstrapBuiltinClusterConfig,
1520 pub catalog_server_cluster: BootstrapBuiltinClusterConfig,
1522 pub probe_cluster: BootstrapBuiltinClusterConfig,
1524 pub support_cluster: BootstrapBuiltinClusterConfig,
1526 pub analytics_cluster: BootstrapBuiltinClusterConfig,
1528}
1529
1530impl BuiltinBootstrapClusterConfigMap {
1531 fn get_config(
1533 &self,
1534 cluster_name: &str,
1535 ) -> Result<BootstrapBuiltinClusterConfig, mz_catalog::durable::CatalogError> {
1536 let cluster_config = if cluster_name == mz_catalog::builtin::MZ_SYSTEM_CLUSTER.name {
1537 &self.system_cluster
1538 } else if cluster_name == mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER.name {
1539 &self.catalog_server_cluster
1540 } else if cluster_name == mz_catalog::builtin::MZ_PROBE_CLUSTER.name {
1541 &self.probe_cluster
1542 } else if cluster_name == mz_catalog::builtin::MZ_SUPPORT_CLUSTER.name {
1543 &self.support_cluster
1544 } else if cluster_name == mz_catalog::builtin::MZ_ANALYTICS_CLUSTER.name {
1545 &self.analytics_cluster
1546 } else {
1547 return Err(mz_catalog::durable::CatalogError::Catalog(
1548 SqlCatalogError::UnexpectedBuiltinCluster(cluster_name.to_owned()),
1549 ));
1550 };
1551 Ok(cluster_config.clone())
1552 }
1553}
1554
1555pub(crate) fn into_consolidatable_updates_startup(
1563 updates: Vec<StateUpdate>,
1564 ts: Timestamp,
1565) -> Vec<(StateUpdateKind, Timestamp, Diff)> {
1566 updates
1567 .into_iter()
1568 .map(|StateUpdate { kind, ts: _, diff }| (kind, ts, Diff::from(diff)))
1569 .collect()
1570}
1571
1572#[cfg(test)]
1573mod tests {
1574 use mz_catalog::durable::ClusterVariantManaged;
1575
1576 use super::*;
1577
1578 #[mz_ore::test]
1582 fn test_managed_replica_config_derives_every_shared_field() {
1583 let managed = ClusterVariantManaged {
1584 size: "somesize".into(),
1585 availability_zones: vec!["az1".into(), "az2".into()],
1586 logging: ReplicaLogging {
1587 log_logging: true,
1588 interval: Some(Duration::from_millis(10)),
1589 },
1590 arrangement_compression: true,
1591 replication_factor: 3,
1592 optimizer_feature_overrides: Default::default(),
1593 schedule: Default::default(),
1594 auto_scaling_strategy: None,
1595 reconfiguration: None,
1596 burst: None,
1597 };
1598
1599 let config = managed_replica_config(&managed);
1600
1601 let ReplicaConfig {
1604 location,
1605 logging,
1606 arrangement_compression,
1607 } = config;
1608
1609 assert_eq!(logging, managed.logging);
1610 assert_eq!(arrangement_compression, managed.arrangement_compression);
1611 match location {
1612 ReplicaLocation::Managed {
1613 size,
1614 availability_zones,
1615 billed_as,
1616 internal,
1617 pending,
1618 } => {
1619 assert_eq!(size, managed.size);
1620 assert_eq!(availability_zones, managed.availability_zones);
1621 assert_eq!(billed_as, None);
1625 assert!(!internal);
1626 assert!(!pending);
1627 }
1628 ReplicaLocation::Unmanaged { .. } => panic!("expected a managed location"),
1629 }
1630 }
1631}