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};
43use mz_catalog::memory::error::{Error, ErrorKind};
44use mz_catalog::memory::objects::{
45 CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate, StateUpdateKind,
46};
47use mz_controller::clusters::ReplicaLogging;
48use mz_controller_types::ClusterId;
49use mz_ore::cast::usize_to_u64;
50use mz_ore::collections::HashSet;
51use mz_ore::now::{SYSTEM_TIME, to_datetime};
52use mz_ore::{instrument, soft_assert_no_log};
53use mz_repr::adt::mz_acl_item::PrivilegeMap;
54use mz_repr::namespaces::is_unstable_schema;
55use mz_repr::{CatalogItemId, Diff, GlobalId, Timestamp};
56use mz_sql::catalog::{CatalogError as SqlCatalogError, CatalogItemType, RoleMembership, RoleVars};
57use mz_sql::func::OP_IMPLS;
58use mz_sql::names::CommentObjectId;
59use mz_sql::rbac;
60use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, SYSTEM_USER};
61use mz_sql::session::vars::{SessionVars, SystemVars, VarError, VarInput};
62use mz_storage_client::controller::{StorageMetadata, StorageTxn};
63use mz_storage_client::storage_collections::StorageCollections;
64use tracing::{Instrument, info, warn};
65use uuid::Uuid;
66
67use crate::AdapterError;
69use crate::catalog::migrate::{self, get_migration_version, set_migration_version};
70use crate::catalog::state::LocalExpressionCache;
71use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState, Config, is_reserved_name};
72
73pub struct InitializeStateResult {
74 pub state: CatalogState,
76 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
78 pub new_builtin_collections: BTreeSet<GlobalId>,
80 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
82 pub last_seen_version: String,
84 pub expr_cache_handle: Option<ExpressionCacheHandle>,
86 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
88 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
90}
91
92pub struct OpenCatalogResult {
93 pub catalog: Catalog,
95 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
97 pub new_builtin_collections: BTreeSet<GlobalId>,
99 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
101 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
103 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
105}
106
107impl Catalog {
108 pub async fn initialize_state<'a>(
112 config: StateConfig,
113 storage: &'a mut Box<dyn mz_catalog::durable::DurableCatalogState>,
114 ) -> Result<InitializeStateResult, AdapterError> {
115 for builtin_role in BUILTIN_ROLES {
116 assert!(
117 is_reserved_name(builtin_role.name),
118 "builtin role {builtin_role:?} must start with one of the following prefixes {}",
119 BUILTIN_PREFIXES.join(", ")
120 );
121 }
122 for builtin_cluster in BUILTIN_CLUSTERS {
123 assert!(
124 is_reserved_name(builtin_cluster.name),
125 "builtin cluster {builtin_cluster:?} must start with one of the following prefixes {}",
126 BUILTIN_PREFIXES.join(", ")
127 );
128 }
129
130 let mut system_configuration = SystemVars::new().set_unsafe(config.unsafe_mode);
131 if config.all_features {
132 system_configuration.enable_all_feature_flags_by_default();
133 }
134
135 let mut state = CatalogState {
136 database_by_name: imbl::OrdMap::new(),
137 database_by_id: imbl::OrdMap::new(),
138 entry_by_id: imbl::OrdMap::new(),
139 entry_by_global_id: imbl::OrdMap::new(),
140 notices_by_dep_id: imbl::OrdMap::new(),
141 ambient_schemas_by_name: imbl::OrdMap::new(),
142 ambient_schemas_by_id: imbl::OrdMap::new(),
143 clusters_by_name: imbl::OrdMap::new(),
144 clusters_by_id: imbl::OrdMap::new(),
145 roles_by_name: imbl::OrdMap::new(),
146 roles_by_id: imbl::OrdMap::new(),
147 network_policies_by_id: imbl::OrdMap::new(),
148 role_auth_by_id: imbl::OrdMap::new(),
149 network_policies_by_name: imbl::OrdMap::new(),
150 system_configuration: Arc::new(system_configuration),
151 scoped_system_parameters: Default::default(),
152 default_privileges: Arc::new(DefaultPrivileges::default()),
153 system_privileges: Arc::new(PrivilegeMap::default()),
154 comments: Arc::new(CommentsMap::default()),
155 source_references: imbl::OrdMap::new(),
156 storage_metadata: Arc::new(StorageMetadata::default()),
157 temporary_namespaces: Default::default(),
158 mock_authentication_nonce: Default::default(),
159 config: mz_sql::catalog::CatalogConfig {
160 start_time: to_datetime((config.now)()),
161 start_instant: Instant::now(),
162 nonce: rand::random(),
163 environment_id: config.environment_id,
164 session_id: Uuid::new_v4(),
165 build_info: config.build_info,
166 now: config.now.clone(),
167 connection_context: config.connection_context,
168 aws_account_id: config
169 .aws_principal_context
170 .as_ref()
171 .map(|c| c.aws_account_id.clone()),
172 helm_chart_version: config.helm_chart_version,
173 },
174 cluster_replica_sizes: config.cluster_replica_sizes,
175 availability_zones: config.availability_zones,
176 egress_addresses: config.egress_addresses,
177 aws_principal_context: config.aws_principal_context,
178 aws_privatelink_availability_zones: config.aws_privatelink_availability_zones,
179 http_host_name: config.http_host_name,
180 license_key: config.license_key,
181 };
182
183 let deploy_generation = storage.get_deployment_generation().await?;
184
185 let mut updates: Vec<_> = storage.sync_to_current_updates().await?;
186 assert!(!updates.is_empty(), "initial catalog snapshot is missing");
187 let mut txn = storage.transaction().await?;
188
189 let new_builtin_collections = {
191 migrate::durable_migrate(
192 &mut txn,
193 state.config.environment_id.organization_id(),
194 config.boot_ts,
195 )?;
196 if let Some(remote_system_parameters) = config.remote_system_parameters {
199 for (name, value) in remote_system_parameters {
200 txn.upsert_system_config(&name, value)?;
201 }
202 txn.set_system_config_synced_once()?;
203 }
204 let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?;
206 let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap {
207 system_cluster: config.builtin_system_cluster_config,
208 catalog_server_cluster: config.builtin_catalog_server_cluster_config,
209 probe_cluster: config.builtin_probe_cluster_config,
210 support_cluster: config.builtin_support_cluster_config,
211 analytics_cluster: config.builtin_analytics_cluster_config,
212 };
213 add_new_remove_old_builtin_clusters_migration(
214 &mut txn,
215 &builtin_bootstrap_cluster_config_map,
216 config.boot_ts,
217 )?;
218 add_new_remove_old_builtin_introspection_source_migration(&mut txn)?;
219 reconcile_builtin_cluster_replicas(
220 &mut txn,
221 &builtin_bootstrap_cluster_config_map,
222 config.boot_ts,
223 )?;
224 add_new_remove_old_builtin_roles_migration(&mut txn)?;
225 remove_invalid_config_param_role_defaults_migration(&mut txn)?;
226 remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?;
227
228 new_builtin_collections
229 };
230
231 let op_updates = txn.get_and_commit_op_updates();
232 updates.extend(op_updates);
233
234 let mut builtin_table_updates = Vec::new();
235
236 {
238 for (name, value) in config.system_parameter_defaults {
241 match state.set_system_configuration_default(&name, VarInput::Flat(&value)) {
242 Ok(_) => (),
243 Err(Error {
244 kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
245 }) => {
246 warn!(%name, "cannot load unknown system parameter from catalog storage to set default parameter");
247 }
248 Err(e) => return Err(e.into()),
249 };
250 }
251 }
252
253 let mut updates = into_consolidatable_updates_startup(updates, config.boot_ts);
256 differential_dataflow::consolidation::consolidate_updates(&mut updates);
257 soft_assert_no_log!(
258 updates.iter().all(|(_, _, diff)| *diff == Diff::ONE),
259 "consolidated updates should be positive during startup: {updates:?}"
260 );
261
262 let mut pre_item_updates = Vec::new();
263 let mut system_item_updates = Vec::new();
264 let mut item_updates = Vec::new();
265 let mut post_item_updates = Vec::new();
266 let mut audit_log_updates = Vec::new();
267 for (kind, ts, diff) in updates {
268 match kind {
269 StateUpdateKind::Role(_)
270 | StateUpdateKind::RoleAuth(_)
271 | StateUpdateKind::Database(_)
272 | StateUpdateKind::Schema(_)
273 | StateUpdateKind::DefaultPrivilege(_)
274 | StateUpdateKind::SystemPrivilege(_)
275 | StateUpdateKind::SystemConfiguration(_)
276 | StateUpdateKind::ClusterSystemConfiguration(_)
277 | StateUpdateKind::ReplicaSystemConfiguration(_)
278 | StateUpdateKind::Cluster(_)
279 | StateUpdateKind::NetworkPolicy(_)
280 | StateUpdateKind::ClusterReplica(_) => pre_item_updates.push(StateUpdate {
281 kind,
282 ts,
283 diff: diff.try_into().expect("valid diff"),
284 }),
285 StateUpdateKind::IntrospectionSourceIndex(_)
286 | StateUpdateKind::SystemObjectMapping(_) => {
287 system_item_updates.push(StateUpdate {
288 kind,
289 ts,
290 diff: diff.try_into().expect("valid diff"),
291 })
292 }
293 StateUpdateKind::Item(_) => item_updates.push(StateUpdate {
294 kind,
295 ts,
296 diff: diff.try_into().expect("valid diff"),
297 }),
298 StateUpdateKind::Comment(_)
299 | StateUpdateKind::StorageCollectionMetadata(_)
300 | StateUpdateKind::SourceReferences(_)
301 | StateUpdateKind::UnfinalizedShard(_) => {
302 post_item_updates.push((kind, ts, diff));
303 }
304 StateUpdateKind::AuditLog(_) => {
305 audit_log_updates.push(StateUpdate {
306 kind,
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 state.system_config().sync_dyncfgs();
330
331 {
335 if let Some(password) = config.external_login_password_mz_system {
336 let role_auth = RoleAuth {
337 role_id: MZ_SYSTEM_ROLE_ID,
338 password_hash: Some(
341 scram256_hash(&password, &NonZeroU32::new(600_000).expect("known valid"))
342 .map_err(|_| {
343 AdapterError::Internal("Failed to hash mz_system password.".to_owned())
344 })?,
345 ),
346 updated_at: SYSTEM_TIME(),
347 };
348 state
349 .role_auth_by_id
350 .insert(MZ_SYSTEM_ROLE_ID, role_auth.clone());
351 let builtin_table_update = state.generate_builtin_table_update(
352 mz_catalog::memory::objects::StateUpdateKind::RoleAuth(role_auth.into()),
353 mz_catalog::memory::objects::StateDiff::Addition,
354 );
355 builtin_table_updates.extend(builtin_table_update);
356 }
357 }
358
359 let expr_cache_start = Instant::now();
360 info!("startup: coordinator init: catalog open: expr cache open beginning");
361 let enable_expr_cache_dyncfg = ENABLE_EXPRESSION_CACHE.get(state.system_config().dyncfgs());
364 let expr_cache_enabled = config
365 .enable_expression_cache_override
366 .unwrap_or(enable_expr_cache_dyncfg);
367 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) = if expr_cache_enabled {
368 info!(
369 ?config.enable_expression_cache_override,
370 ?enable_expr_cache_dyncfg,
371 "using expression cache for startup"
372 );
373 let current_ids = txn
374 .get_items()
375 .flat_map(|item| {
376 let gid = item.global_id.clone();
377 let gids: Vec<_> = item.extra_versions.values().cloned().collect();
378 std::iter::once(gid).chain(gids)
379 })
380 .chain(
381 txn.get_system_object_mappings()
382 .map(|som| som.unique_identifier.global_id),
383 )
384 .collect();
385 let dyncfgs = config.persist_client.dyncfgs().clone();
386 let build_version = if config.build_info.is_dev() {
387 config
390 .build_info
391 .semver_version_build()
392 .expect("build ID is not available on your platform!")
393 } else {
394 config.build_info.semver_version()
395 };
396 let expr_cache_config = ExpressionCacheConfig {
397 build_version,
398 shard_id: txn
399 .get_expression_cache_shard()
400 .expect("expression cache shard should exist for opened catalogs"),
401 persist: config.persist_client,
402 current_ids,
403 remove_prior_versions: !config.read_only,
404 compact_shard: config.read_only,
405 dyncfgs,
406 };
407 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) =
408 ExpressionCacheHandle::spawn_expression_cache(expr_cache_config).await;
409 (
410 Some(expr_cache_handle),
411 cached_local_exprs,
412 cached_global_exprs,
413 )
414 } else {
415 (None, BTreeMap::new(), BTreeMap::new())
416 };
417 let mut local_expr_cache = LocalExpressionCache::new(cached_local_exprs);
418 info!(
419 "startup: coordinator init: catalog open: expr cache open complete in {:?}",
420 expr_cache_start.elapsed()
421 );
422
423 let (builtin_table_update, _catalog_updates) = state
429 .apply_updates(system_item_updates, &mut local_expr_cache)
430 .await;
431 builtin_table_updates.extend(builtin_table_update);
432
433 let last_seen_version =
434 get_migration_version(&txn).map_or_else(|| "new".into(), |v| v.to_string());
435
436 let mz_authentication_mock_nonce =
437 txn.get_authentication_mock_nonce().ok_or_else(|| {
438 Error::new(ErrorKind::SettingError("authentication nonce".to_string()))
439 })?;
440
441 state.mock_authentication_nonce = Some(mz_authentication_mock_nonce);
442
443 let (builtin_table_update, _catalog_updates) = if !config.skip_migrations {
445 let migrate_result = migrate::migrate(
446 &mut state,
447 &mut txn,
448 &mut local_expr_cache,
449 item_updates,
450 config.now,
451 config.boot_ts,
452 )
453 .await
454 .map_err(|e| {
455 Error::new(ErrorKind::FailedCatalogMigration {
456 last_seen_version: last_seen_version.clone(),
457 this_version: config.build_info.version,
458 cause: e.to_string(),
459 })
460 })?;
461 if !migrate_result.post_item_updates.is_empty() {
462 post_item_updates.extend(migrate_result.post_item_updates);
465 if let Some(max_ts) = post_item_updates.iter().map(|(_, ts, _)| ts).max().cloned() {
467 for (_, ts, _) in &mut post_item_updates {
468 *ts = max_ts;
469 }
470 }
471 differential_dataflow::consolidation::consolidate_updates(&mut post_item_updates);
472 }
473
474 (
475 migrate_result.builtin_table_updates,
476 migrate_result.catalog_updates,
477 )
478 } else {
479 state
480 .apply_updates(item_updates, &mut local_expr_cache)
481 .await
482 };
483 builtin_table_updates.extend(builtin_table_update);
484
485 let post_item_updates = post_item_updates
486 .into_iter()
487 .map(|(kind, ts, diff)| StateUpdate {
488 kind,
489 ts,
490 diff: diff.try_into().expect("valid diff"),
491 })
492 .collect();
493 let (builtin_table_update, _catalog_updates) = state
494 .apply_updates(post_item_updates, &mut local_expr_cache)
495 .await;
496 builtin_table_updates.extend(builtin_table_update);
497
498 for audit_log_update in audit_log_updates {
502 builtin_table_updates.extend(
503 state.generate_builtin_table_update(audit_log_update.kind, audit_log_update.diff),
504 );
505 }
506
507 let schema_migration_result = builtin_schema_migration::run(
509 config.build_info,
510 deploy_generation,
511 &mut txn,
512 config.builtin_item_migration_config,
513 )
514 .await?;
515
516 let state_updates = txn.get_and_commit_op_updates();
517
518 let (table_updates, _catalog_updates) = state
524 .apply_updates(state_updates, &mut local_expr_cache)
525 .await;
526 builtin_table_updates.extend(table_updates);
527 let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates);
528
529 set_migration_version(&mut txn, config.build_info.semver_version())?;
531
532 txn.commit(config.boot_ts).await?;
533
534 schema_migration_result.cleanup_action.await;
536
537 Ok(InitializeStateResult {
538 state,
539 migrated_storage_collections_0dt: schema_migration_result.replaced_items,
540 new_builtin_collections: new_builtin_collections.into_iter().collect(),
541 builtin_table_updates,
542 last_seen_version,
543 expr_cache_handle,
544 cached_global_exprs,
545 uncached_local_exprs: local_expr_cache.into_uncached_exprs(),
546 })
547 }
548
549 #[instrument(name = "catalog::open")]
560 pub fn open(config: Config<'_>) -> BoxFuture<'static, Result<OpenCatalogResult, AdapterError>> {
561 async move {
562 let mut storage = config.storage;
563
564 let InitializeStateResult {
565 state,
566 migrated_storage_collections_0dt,
567 new_builtin_collections,
568 mut builtin_table_updates,
569 last_seen_version: _,
570 expr_cache_handle,
571 cached_global_exprs,
572 uncached_local_exprs,
573 } =
574 Self::initialize_state(config.state, &mut storage)
578 .instrument(tracing::info_span!("catalog::initialize_state"))
579 .boxed()
580 .await?;
581
582 let catalog = Catalog {
583 state,
584 expr_cache_handle,
585 transient_revision: 1,
586 shared_transient_revision: Arc::new(AtomicU64::new(1)),
587 storage: Arc::new(tokio::sync::Mutex::new(storage)),
588 };
589
590 for (op, func) in OP_IMPLS.iter() {
593 match func {
594 mz_sql::func::Func::Scalar(impls) => {
595 for imp in impls {
596 builtin_table_updates.push(catalog.state.resolve_builtin_table_update(
597 catalog.state.pack_op_update(op, imp.details(), Diff::ONE),
598 ));
599 }
600 }
601 _ => unreachable!("all operators must be scalar functions"),
602 }
603 }
604
605 for ip in &catalog.state.egress_addresses {
606 builtin_table_updates.push(
607 catalog
608 .state
609 .resolve_builtin_table_update(catalog.state.pack_egress_ip_update(ip)?),
610 );
611 }
612
613 if !catalog.state.license_key.id.is_empty() {
614 builtin_table_updates.push(
615 catalog.state.resolve_builtin_table_update(
616 catalog
617 .state
618 .pack_license_key_update(&catalog.state.license_key)?,
619 ),
620 );
621 }
622
623 catalog.storage().await.mark_bootstrap_complete().await;
624
625 Ok(OpenCatalogResult {
626 catalog,
627 migrated_storage_collections_0dt,
628 new_builtin_collections,
629 builtin_table_updates,
630 cached_global_exprs,
631 uncached_local_exprs,
632 })
633 }
634 .instrument(tracing::info_span!("catalog::open"))
635 .boxed()
636 }
637
638 async fn initialize_storage_state(
645 &mut self,
646 storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
647 ) -> Result<(), mz_catalog::durable::CatalogError> {
648 let collections = self
649 .entries()
650 .filter(|entry| entry.item().is_storage_collection())
651 .flat_map(|entry| entry.global_ids())
652 .collect();
653
654 let mut state = self.state.clone();
657
658 let mut storage = self.storage().await;
659 let shard_id = storage.shard_id();
660 let mut txn = storage.transaction().await?;
661
662 let item_id = self.resolve_builtin_storage_collection(&MZ_CATALOG_RAW);
665 let global_id = self.get_entry(&item_id).latest_global_id();
666 match txn.get_collection_metadata().get(&global_id) {
667 None => {
668 txn.insert_collection_metadata([(global_id, shard_id)].into())
669 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
670 }
671 Some(id) => assert_eq!(*id, shard_id),
672 }
673
674 storage_collections
675 .initialize_state(&mut txn, collections)
676 .await
677 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
678
679 let updates = txn.get_and_commit_op_updates();
680 let (builtin_updates, catalog_updates) = state
681 .apply_updates(updates, &mut LocalExpressionCache::Closed)
682 .await;
683 assert!(
684 builtin_updates.is_empty(),
685 "storage is not allowed to generate catalog changes that would cause changes to builtin tables"
686 );
687 assert!(
688 catalog_updates.is_empty(),
689 "storage is not allowed to generate catalog changes that would change the catalog or controller state"
690 );
691 let commit_ts = txn.upper();
692 txn.commit(commit_ts).await?;
693 drop(storage);
694
695 self.state = state;
697 Ok(())
698 }
699
700 pub async fn initialize_controller(
703 &mut self,
704 config: mz_controller::ControllerConfig,
705 envd_epoch: core::num::NonZeroI64,
706 read_only: bool,
707 ) -> Result<mz_controller::Controller, mz_catalog::durable::CatalogError> {
708 let controller_start = Instant::now();
709 info!("startup: controller init: beginning");
710
711 let controller = {
712 let mut storage = self.storage().await;
713 let mut tx = storage.transaction().await?;
714 mz_controller::prepare_initialization(&mut tx)
715 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
716 let updates = tx.get_and_commit_op_updates();
717 assert!(
718 updates.is_empty(),
719 "initializing controller should not produce updates: {updates:?}"
720 );
721 let commit_ts = tx.upper();
722 tx.commit(commit_ts).await?;
723
724 let read_only_tx = storage.transaction().await?;
725
726 mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await
727 };
728
729 self.initialize_storage_state(&controller.storage_collections)
730 .await?;
731
732 info!(
733 "startup: controller init: complete in {:?}",
734 controller_start.elapsed()
735 );
736
737 Ok(controller)
738 }
739
740 pub async fn expire(self) {
742 if let Some(storage) = Arc::into_inner(self.storage) {
745 let storage = storage.into_inner();
746 storage.expire().await;
747 }
748 }
749}
750
751impl CatalogState {
752 fn set_system_configuration_default(
754 &mut self,
755 name: &str,
756 value: VarInput,
757 ) -> Result<(), Error> {
758 Ok(Arc::make_mut(&mut self.system_configuration).set_default(name, value)?)
759 }
760}
761
762fn add_new_remove_old_builtin_items_migration(
766 txn: &mut mz_catalog::durable::Transaction<'_>,
767) -> Result<Vec<GlobalId>, mz_catalog::durable::CatalogError> {
768 let mut new_builtin_mappings = Vec::new();
769 let mut builtin_descs = HashSet::new();
771
772 let mut builtins = Vec::new();
775 for builtin in BUILTINS::iter() {
776 let desc = SystemObjectDescription {
777 schema_name: builtin.schema().to_string(),
778 object_type: builtin.catalog_item_type(),
779 object_name: builtin.name().to_string(),
780 };
781 if !builtin_descs.insert(desc.clone()) {
783 panic!(
784 "duplicate builtin description: {:?}, {:?}",
785 SystemObjectDescription {
786 schema_name: builtin.schema().to_string(),
787 object_type: builtin.catalog_item_type(),
788 object_name: builtin.name().to_string(),
789 },
790 builtin
791 );
792 }
793 builtins.push((desc, builtin));
794 }
795
796 let mut system_object_mappings: BTreeMap<_, _> = txn
797 .get_system_object_mappings()
798 .map(|system_object_mapping| {
799 (
800 system_object_mapping.description.clone(),
801 system_object_mapping,
802 )
803 })
804 .collect();
805
806 let (existing_builtins, new_builtins): (Vec<_>, Vec<_>) =
807 builtins.into_iter().partition_map(|(desc, builtin)| {
808 let fingerprint = match builtin.runtime_alterable() {
809 false => builtin.fingerprint(),
810 true => RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into(),
811 };
812 match system_object_mappings.remove(&desc) {
813 Some(system_object_mapping) => {
814 Either::Left((builtin, system_object_mapping, fingerprint))
815 }
816 None => Either::Right((builtin, fingerprint)),
817 }
818 });
819 let new_builtin_ids = txn.allocate_system_item_ids(usize_to_u64(new_builtins.len()))?;
820 let new_builtins: Vec<_> = new_builtins
821 .into_iter()
822 .zip_eq(new_builtin_ids.clone())
823 .collect();
824
825 for ((builtin, fingerprint), (catalog_id, global_id)) in new_builtins.iter().cloned() {
827 new_builtin_mappings.push(SystemObjectMapping {
828 description: SystemObjectDescription {
829 schema_name: builtin.schema().to_string(),
830 object_type: builtin.catalog_item_type(),
831 object_name: builtin.name().to_string(),
832 },
833 unique_identifier: SystemObjectUniqueIdentifier {
834 catalog_id,
835 global_id,
836 fingerprint,
837 },
838 });
839
840 let handled_runtime_alterable = match builtin {
846 Builtin::Connection(c) if c.runtime_alterable => {
847 let mut acl_items = vec![rbac::owner_privilege(
848 mz_sql::catalog::ObjectType::Connection,
849 c.owner_id.clone(),
850 )];
851 acl_items.extend_from_slice(c.access);
852 let versions = BTreeMap::new();
854
855 txn.insert_item(
856 catalog_id,
857 c.oid,
858 global_id,
859 mz_catalog::durable::initialize::resolve_system_schema(c.schema).id,
860 c.name,
861 c.sql.into(),
862 *c.owner_id,
863 acl_items,
864 versions,
865 None,
866 )?;
867 true
868 }
869 _ => false,
870 };
871 assert_eq!(
872 builtin.runtime_alterable(),
873 handled_runtime_alterable,
874 "runtime alterable object was not handled by migration",
875 );
876 }
877 txn.set_system_object_mappings(new_builtin_mappings)?;
878
879 let builtins_with_catalog_ids = existing_builtins
881 .iter()
882 .map(|(b, m, _)| (*b, m.unique_identifier.catalog_id))
883 .chain(
884 new_builtins
885 .into_iter()
886 .map(|((b, _), (catalog_id, _))| (b, catalog_id)),
887 );
888
889 for (builtin, id) in builtins_with_catalog_ids {
890 let (comment_id, desc, comments) = match builtin {
891 Builtin::Source(s) => (CommentObjectId::Source(id), &s.desc, &s.column_comments),
892 Builtin::View(v) => (CommentObjectId::View(id), &v.desc, &v.column_comments),
893 Builtin::Table(t) => (CommentObjectId::Table(id), &t.desc, &t.column_comments),
894 Builtin::MaterializedView(mv) => (
895 CommentObjectId::MaterializedView(id),
896 &mv.desc,
897 &mv.column_comments,
898 ),
899 Builtin::Log(_)
900 | Builtin::Type(_)
901 | Builtin::Func(_)
902 | Builtin::Index(_)
903 | Builtin::Connection(_) => continue,
904 };
905 txn.drop_comments(&BTreeSet::from_iter([
910 CommentObjectId::Table(id),
911 CommentObjectId::View(id),
912 CommentObjectId::MaterializedView(id),
913 CommentObjectId::Source(id),
914 ]))?;
915
916 let mut comments = comments.clone();
917 for (col_idx, name) in desc.iter_names().enumerate() {
918 if let Some(comment) = comments.remove(name.as_str()) {
919 txn.update_comment(comment_id, Some(col_idx + 1), Some(comment.to_owned()))?;
921 }
922 }
923 assert!(
924 comments.is_empty(),
925 "builtin object contains dangling comments that don't correspond to columns {comments:?}"
926 );
927 }
928
929 let mut deleted_system_objects = BTreeSet::new();
932 let mut deleted_runtime_alterable_system_ids = BTreeSet::new();
933 let mut deleted_comments = BTreeSet::new();
934 for (desc, mapping) in system_object_mappings {
935 deleted_system_objects.insert(mapping.description);
936 if mapping.unique_identifier.fingerprint == RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL {
937 deleted_runtime_alterable_system_ids.insert(mapping.unique_identifier.catalog_id);
938 }
939
940 let id = mapping.unique_identifier.catalog_id;
941 let comment_id = match desc.object_type {
942 CatalogItemType::Table => CommentObjectId::Table(id),
943 CatalogItemType::Source => CommentObjectId::Source(id),
944 CatalogItemType::View => CommentObjectId::View(id),
945 CatalogItemType::MaterializedView => CommentObjectId::MaterializedView(id),
946 CatalogItemType::Sink
947 | CatalogItemType::MetricSink
948 | CatalogItemType::Index
949 | CatalogItemType::Type
950 | CatalogItemType::Func
951 | CatalogItemType::Secret
952 | CatalogItemType::Connection => continue,
953 };
954 deleted_comments.insert(comment_id);
955 }
956 let delete_exceptions: HashSet<SystemObjectDescription> = [].into();
962 assert!(
966 deleted_system_objects
967 .iter()
968 .filter(|object| object.object_type != CatalogItemType::Index)
970 .all(
971 |deleted_object| is_unstable_schema(&deleted_object.schema_name)
972 || delete_exceptions.contains(deleted_object)
973 ),
974 "only objects in unstable schemas can be deleted, deleted objects: {:?}",
975 deleted_system_objects
976 );
977 txn.drop_comments(&deleted_comments)?;
978 txn.remove_items(&deleted_runtime_alterable_system_ids)?;
979 txn.remove_system_object_mappings(deleted_system_objects)?;
980
981 let new_builtin_collections = new_builtin_ids
983 .into_iter()
984 .map(|(_catalog_id, global_id)| global_id)
985 .collect();
986
987 Ok(new_builtin_collections)
988}
989
990fn add_new_remove_old_builtin_clusters_migration(
991 txn: &mut mz_catalog::durable::Transaction<'_>,
992 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
993 boot_ts: Timestamp,
994) -> Result<(), mz_catalog::durable::CatalogError> {
995 let mut durable_clusters: BTreeMap<_, _> = txn
996 .get_clusters()
997 .filter(|cluster| cluster.id.is_system())
998 .map(|cluster| (cluster.name.to_string(), cluster))
999 .collect();
1000
1001 for builtin_cluster in BUILTIN_CLUSTERS {
1003 if durable_clusters.remove(builtin_cluster.name).is_none() {
1004 let cluster_config = builtin_cluster_config_map.get_config(builtin_cluster.name)?;
1005
1006 let cluster_id = txn.insert_system_cluster(
1007 builtin_cluster.name,
1008 vec![],
1009 builtin_cluster.privileges.to_vec(),
1010 builtin_cluster.owner_id.to_owned(),
1011 mz_catalog::durable::ClusterConfig {
1012 variant: mz_catalog::durable::ClusterVariant::Managed(ClusterVariantManaged {
1013 size: cluster_config.size,
1014 availability_zones: vec![],
1015 replication_factor: cluster_config.replication_factor,
1016 logging: default_logging_config(),
1017 arrangement_compression: false,
1018 optimizer_feature_overrides: Default::default(),
1019 schedule: Default::default(),
1020 auto_scaling_strategy: None,
1021 reconfiguration: None,
1022 burst: None,
1023 }),
1024 workload_class: None,
1025 },
1026 &HashSet::new(),
1027 )?;
1028
1029 let audit_id = txn.allocate_audit_log_id()?;
1030 txn.insert_audit_log_event(VersionedEvent::new(
1031 audit_id,
1032 EventType::Create,
1033 ObjectType::Cluster,
1034 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1035 id: cluster_id.to_string(),
1036 name: builtin_cluster.name.to_string(),
1037 }),
1038 None,
1039 boot_ts.into(),
1040 ));
1041 }
1042 }
1043
1044 let old_clusters = durable_clusters
1046 .values()
1047 .map(|cluster| cluster.id)
1048 .collect();
1049 txn.remove_clusters(&old_clusters)?;
1050
1051 for (_name, cluster) in &durable_clusters {
1052 let audit_id = txn.allocate_audit_log_id()?;
1053 txn.insert_audit_log_event(VersionedEvent::new(
1054 audit_id,
1055 EventType::Drop,
1056 ObjectType::Cluster,
1057 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1058 id: cluster.id.to_string(),
1059 name: cluster.name.clone(),
1060 }),
1061 None,
1062 boot_ts.into(),
1063 ));
1064 }
1065
1066 Ok(())
1067}
1068
1069fn add_new_remove_old_builtin_introspection_source_migration(
1070 txn: &mut mz_catalog::durable::Transaction<'_>,
1071) -> Result<(), AdapterError> {
1072 let mut new_indexes = Vec::new();
1073 let mut removed_indexes = BTreeSet::new();
1074 for cluster in txn.get_clusters() {
1075 let mut introspection_source_index_ids = txn.get_introspection_source_indexes(cluster.id);
1076
1077 let mut new_logs = Vec::new();
1078
1079 for log in BUILTINS::logs() {
1080 if introspection_source_index_ids.remove(log.name).is_none() {
1081 new_logs.push(log);
1082 }
1083 }
1084
1085 for log in new_logs {
1086 let (item_id, gid) =
1087 Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant);
1088 new_indexes.push((cluster.id, log.name.to_string(), item_id, gid));
1089 }
1090
1091 removed_indexes.extend(
1094 introspection_source_index_ids
1095 .into_keys()
1096 .map(|name| (cluster.id, name.to_string())),
1097 );
1098 }
1099 txn.insert_introspection_source_indexes(new_indexes, &HashSet::new())?;
1100 txn.remove_introspection_source_indexes(removed_indexes)?;
1101 Ok(())
1102}
1103
1104fn add_new_remove_old_builtin_roles_migration(
1105 txn: &mut mz_catalog::durable::Transaction<'_>,
1106) -> Result<(), mz_catalog::durable::CatalogError> {
1107 let mut durable_roles: BTreeMap<_, _> = txn
1108 .get_roles()
1109 .filter(|role| role.id.is_system() || role.id.is_predefined())
1110 .map(|role| (role.name.to_string(), role))
1111 .collect();
1112
1113 for builtin_role in BUILTIN_ROLES {
1115 if durable_roles.remove(builtin_role.name).is_none() {
1116 txn.insert_builtin_role(
1117 builtin_role.id,
1118 builtin_role.name.to_string(),
1119 builtin_role.attributes.clone(),
1120 RoleMembership::new(),
1121 RoleVars::default(),
1122 builtin_role.oid,
1123 )?;
1124 }
1125 }
1126
1127 let old_roles = durable_roles.values().map(|role| role.id).collect();
1129 txn.remove_roles(&old_roles)?;
1130
1131 Ok(())
1132}
1133
1134fn reconcile_builtin_cluster_replicas(
1166 txn: &mut Transaction<'_>,
1167 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1168 boot_ts: Timestamp,
1169) -> Result<(), AdapterError> {
1170 let builtin_cluster_names: BTreeSet<&str> = BUILTIN_CLUSTERS
1171 .iter()
1172 .map(|cluster| cluster.name)
1173 .collect();
1174
1175 let clusters: Vec<_> = txn
1179 .get_clusters()
1180 .filter(|cluster| {
1181 cluster.id.is_system() && builtin_cluster_names.contains(cluster.name.as_str())
1182 })
1183 .collect();
1184
1185 let builtin_cluster_ids: BTreeSet<ClusterId> =
1186 clusters.iter().map(|cluster| cluster.id).collect();
1187
1188 let mut replicas_by_cluster: BTreeMap<ClusterId, BTreeMap<String, ClusterReplica>> =
1197 BTreeMap::new();
1198 for replica in txn.get_cluster_replicas().filter(|replica| {
1199 builtin_cluster_ids.contains(&replica.cluster_id)
1200 && !matches!(
1201 replica.config.location,
1202 ReplicaLocation::Managed { internal: true, .. }
1203 )
1204 }) {
1205 replicas_by_cluster
1206 .entry(replica.cluster_id)
1207 .or_default()
1208 .insert(replica.name.clone(), replica);
1209 }
1210
1211 let mut to_drop: Vec<(String, ClusterReplica)> = Vec::new();
1212
1213 for cluster in clusters {
1214 let ClusterVariant::Managed(managed) = &cluster.config.variant else {
1218 continue;
1219 };
1220
1221 let bootstrap_config = builtin_cluster_config_map.get_config(&cluster.name)?;
1226 if bootstrap_config.replication_factor != managed.replication_factor {
1227 warn!(
1228 cluster = %cluster.name,
1229 configured_replication_factor = managed.replication_factor,
1230 bootstrap_replication_factor = bootstrap_config.replication_factor,
1231 "bootstrap replication factor is not applied to an already-existing \
1232 builtin cluster. Use ALTER CLUSTER ... SET (REPLICATION FACTOR ...) \
1233 to change it",
1234 );
1235 }
1236
1237 let mut surplus = replicas_by_cluster.remove(&cluster.id).unwrap_or_default();
1242 for index in 0..managed.replication_factor {
1243 let replica_name = managed_cluster_replica_name(index);
1244 if surplus.remove(&replica_name).is_some() {
1245 continue;
1246 }
1247
1248 let replica_id = txn.allocate_system_replica_id()?;
1252 txn.insert_cluster_replica_with_id(
1253 cluster.id,
1254 replica_id,
1255 &replica_name,
1256 managed_replica_config(managed),
1257 cluster.owner_id,
1263 )?;
1264 info!(
1265 cluster = %cluster.name, replica = %replica_name, %replica_id,
1266 "creating builtin cluster replica to match the cluster's replication factor"
1267 );
1268
1269 let audit_id = txn.allocate_audit_log_id()?;
1270 txn.insert_audit_log_event(VersionedEvent::new(
1271 audit_id,
1272 EventType::Create,
1273 ObjectType::ClusterReplica,
1274 EventDetails::CreateClusterReplicaV4(mz_audit_log::CreateClusterReplicaV4 {
1275 cluster_id: cluster.id.to_string(),
1276 cluster_name: cluster.name.clone(),
1277 replica_id: Some(replica_id.to_string()),
1278 replica_name,
1279 logical_size: managed.size.clone(),
1280 billed_as: None,
1281 internal: false,
1282 reason: CreateOrDropClusterReplicaReasonV1::System,
1283 scheduling_policies: None,
1284 }),
1285 None,
1286 boot_ts.into(),
1287 ));
1288 }
1289
1290 to_drop.extend(
1296 surplus
1297 .into_values()
1298 .map(|replica| (cluster.name.clone(), replica)),
1299 );
1300 }
1301
1302 let drop_ids = to_drop
1305 .iter()
1306 .map(|(_cluster_name, replica)| replica.replica_id)
1307 .collect();
1308 txn.remove_cluster_replicas(&drop_ids)?;
1309
1310 for (cluster_name, replica) in to_drop {
1311 info!(
1312 cluster = %cluster_name, replica = %replica.name, replica_id = %replica.replica_id,
1313 "dropping builtin cluster replica not called for by the cluster's replication factor"
1314 );
1315
1316 let audit_id = txn.allocate_audit_log_id()?;
1317 txn.insert_audit_log_event(VersionedEvent::new(
1318 audit_id,
1319 EventType::Drop,
1320 ObjectType::ClusterReplica,
1321 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1322 cluster_id: replica.cluster_id.to_string(),
1323 cluster_name,
1324 replica_id: Some(replica.replica_id.to_string()),
1325 replica_name: replica.name,
1326 reason: CreateOrDropClusterReplicaReasonV1::System,
1327 scheduling_policies: None,
1328 }),
1329 None,
1330 boot_ts.into(),
1331 ));
1332 }
1333
1334 Ok(())
1335}
1336
1337fn managed_replica_config(managed: &ClusterVariantManaged) -> ReplicaConfig {
1351 let ClusterVariantManaged {
1354 size,
1355 availability_zones,
1356 logging,
1357 arrangement_compression,
1358 replication_factor: _,
1359 optimizer_feature_overrides: _,
1360 schedule: _,
1361 auto_scaling_strategy: _,
1362 reconfiguration: _,
1363 burst: _,
1364 } = managed;
1365 ReplicaConfig {
1366 location: ReplicaLocation::Managed {
1367 size: size.clone(),
1368 availability_zones: availability_zones.clone(),
1369 billed_as: None,
1370 internal: false,
1371 pending: false,
1372 },
1373 logging: logging.clone(),
1374 arrangement_compression: *arrangement_compression,
1375 }
1376}
1377
1378fn remove_invalid_config_param_role_defaults_migration(
1385 txn: &mut Transaction<'_>,
1386) -> Result<(), AdapterError> {
1387 static BUILD_INFO: mz_build_info::BuildInfo = mz_build_info::build_info!();
1388
1389 let roles_to_migrate: BTreeMap<_, _> = txn
1390 .get_roles()
1391 .filter_map(|mut role| {
1392 let session_vars = SessionVars::new_unchecked(&BUILD_INFO, SYSTEM_USER.clone(), None);
1397
1398 let mut invalid_roles_vars = BTreeMap::new();
1400 for (name, value) in &role.vars.map {
1401 let Ok(session_var) = session_vars.inspect(name) else {
1403 invalid_roles_vars.insert(name.clone(), value.clone());
1404 continue;
1405 };
1406 if session_var.check(value.borrow()).is_err() {
1407 invalid_roles_vars.insert(name.clone(), value.clone());
1408 }
1409 }
1410
1411 if invalid_roles_vars.is_empty() {
1413 return None;
1414 }
1415
1416 tracing::warn!(?role, ?invalid_roles_vars, "removing invalid role vars");
1417
1418 for (name, _value) in invalid_roles_vars {
1420 role.vars.map.remove(&name);
1421 }
1422 Some(role)
1423 })
1424 .map(|role| (role.id, role))
1425 .collect();
1426
1427 txn.update_roles_without_auth(roles_to_migrate)?;
1428
1429 Ok(())
1430}
1431
1432fn remove_pending_cluster_replicas_migration(
1435 tx: &mut Transaction,
1436 boot_ts: mz_repr::Timestamp,
1437) -> Result<(), anyhow::Error> {
1438 let cluster_names: BTreeMap<_, _> = tx.get_clusters().map(|c| (c.id, c.name)).collect();
1440
1441 let occurred_at = boot_ts.into();
1442
1443 for replica in tx.get_cluster_replicas().collect::<Vec<_>>() {
1444 if let mz_catalog::durable::ReplicaLocation::Managed { pending: true, .. } =
1445 replica.config.location
1446 {
1447 let cluster_name = cluster_names
1448 .get(&replica.cluster_id)
1449 .cloned()
1450 .unwrap_or_else(|| "<unknown>".to_string());
1451
1452 info!(
1453 "removing pending cluster replica '{}' from cluster '{}'",
1454 replica.name, cluster_name,
1455 );
1456
1457 tx.remove_cluster_replica(replica.replica_id)?;
1458
1459 let audit_id = tx.allocate_audit_log_id()?;
1463 tx.insert_audit_log_event(VersionedEvent::new(
1464 audit_id,
1465 EventType::Drop,
1466 ObjectType::ClusterReplica,
1467 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1468 cluster_id: replica.cluster_id.to_string(),
1469 cluster_name,
1470 replica_id: Some(replica.replica_id.to_string()),
1471 replica_name: replica.name,
1472 reason: CreateOrDropClusterReplicaReasonV1::System,
1473 scheduling_policies: None,
1474 }),
1475 None,
1476 occurred_at,
1477 ));
1478 }
1479 }
1480 Ok(())
1481}
1482
1483fn default_logging_config() -> ReplicaLogging {
1484 ReplicaLogging {
1485 log_logging: false,
1486 interval: Some(Duration::from_secs(1)),
1487 }
1488}
1489
1490#[derive(Debug)]
1491pub struct BuiltinBootstrapClusterConfigMap {
1492 pub system_cluster: BootstrapBuiltinClusterConfig,
1494 pub catalog_server_cluster: BootstrapBuiltinClusterConfig,
1496 pub probe_cluster: BootstrapBuiltinClusterConfig,
1498 pub support_cluster: BootstrapBuiltinClusterConfig,
1500 pub analytics_cluster: BootstrapBuiltinClusterConfig,
1502}
1503
1504impl BuiltinBootstrapClusterConfigMap {
1505 fn get_config(
1507 &self,
1508 cluster_name: &str,
1509 ) -> Result<BootstrapBuiltinClusterConfig, mz_catalog::durable::CatalogError> {
1510 let cluster_config = if cluster_name == mz_catalog::builtin::MZ_SYSTEM_CLUSTER.name {
1511 &self.system_cluster
1512 } else if cluster_name == mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER.name {
1513 &self.catalog_server_cluster
1514 } else if cluster_name == mz_catalog::builtin::MZ_PROBE_CLUSTER.name {
1515 &self.probe_cluster
1516 } else if cluster_name == mz_catalog::builtin::MZ_SUPPORT_CLUSTER.name {
1517 &self.support_cluster
1518 } else if cluster_name == mz_catalog::builtin::MZ_ANALYTICS_CLUSTER.name {
1519 &self.analytics_cluster
1520 } else {
1521 return Err(mz_catalog::durable::CatalogError::Catalog(
1522 SqlCatalogError::UnexpectedBuiltinCluster(cluster_name.to_owned()),
1523 ));
1524 };
1525 Ok(cluster_config.clone())
1526 }
1527}
1528
1529pub(crate) fn into_consolidatable_updates_startup(
1537 updates: Vec<StateUpdate>,
1538 ts: Timestamp,
1539) -> Vec<(StateUpdateKind, Timestamp, Diff)> {
1540 updates
1541 .into_iter()
1542 .map(|StateUpdate { kind, ts: _, diff }| (kind, ts, Diff::from(diff)))
1543 .collect()
1544}
1545
1546#[cfg(test)]
1547mod tests {
1548 use mz_catalog::durable::ClusterVariantManaged;
1549
1550 use super::*;
1551
1552 #[mz_ore::test]
1556 fn test_managed_replica_config_derives_every_shared_field() {
1557 let managed = ClusterVariantManaged {
1558 size: "somesize".into(),
1559 availability_zones: vec!["az1".into(), "az2".into()],
1560 logging: ReplicaLogging {
1561 log_logging: true,
1562 interval: Some(Duration::from_millis(10)),
1563 },
1564 arrangement_compression: true,
1565 replication_factor: 3,
1566 optimizer_feature_overrides: Default::default(),
1567 schedule: Default::default(),
1568 auto_scaling_strategy: None,
1569 reconfiguration: None,
1570 burst: None,
1571 };
1572
1573 let config = managed_replica_config(&managed);
1574
1575 let ReplicaConfig {
1578 location,
1579 logging,
1580 arrangement_compression,
1581 } = config;
1582
1583 assert_eq!(logging, managed.logging);
1584 assert_eq!(arrangement_compression, managed.arrangement_compression);
1585 match location {
1586 ReplicaLocation::Managed {
1587 size,
1588 availability_zones,
1589 billed_as,
1590 internal,
1591 pending,
1592 } => {
1593 assert_eq!(size, managed.size);
1594 assert_eq!(availability_zones, managed.availability_zones);
1595 assert_eq!(billed_as, None);
1599 assert!(!internal);
1600 assert!(!pending);
1601 }
1602 ReplicaLocation::Unmanaged { .. } => panic!("expected a managed location"),
1603 }
1604 }
1605}