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_CLUSTER_REPLICAS, BUILTIN_CLUSTERS, BUILTIN_PREFIXES, BUILTIN_ROLES, BUILTINS, Builtin,
31 Fingerprint, 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::{ClusterReplica, ClusterVariant, ClusterVariantManaged, Transaction};
38use mz_catalog::expr_cache::{
39 ExpressionCacheConfig, ExpressionCacheHandle, GlobalExpressions, LocalExpressions,
40};
41use mz_catalog::memory::error::{Error, ErrorKind};
42use mz_catalog::memory::objects::{
43 BootstrapStateUpdateKind, CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate,
44};
45use mz_controller::clusters::ReplicaLogging;
46use mz_controller_types::ClusterId;
47use mz_ore::cast::usize_to_u64;
48use mz_ore::collections::HashSet;
49use mz_ore::now::{SYSTEM_TIME, to_datetime};
50use mz_ore::{instrument, soft_assert_no_log};
51use mz_repr::adt::mz_acl_item::PrivilegeMap;
52use mz_repr::namespaces::is_unstable_schema;
53use mz_repr::{CatalogItemId, Diff, GlobalId, Timestamp};
54use mz_sql::catalog::{CatalogError as SqlCatalogError, CatalogItemType, RoleMembership, RoleVars};
55use mz_sql::func::OP_IMPLS;
56use mz_sql::names::CommentObjectId;
57use mz_sql::rbac;
58use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, SYSTEM_USER};
59use mz_sql::session::vars::{SessionVars, SystemVars, VarError, VarInput};
60use mz_storage_client::controller::{StorageMetadata, StorageTxn};
61use mz_storage_client::storage_collections::StorageCollections;
62use tracing::{Instrument, info, warn};
63use uuid::Uuid;
64
65use crate::AdapterError;
67use crate::catalog::migrate::{self, get_migration_version, set_migration_version};
68use crate::catalog::state::LocalExpressionCache;
69use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState, Config, is_reserved_name};
70
71pub struct InitializeStateResult {
72 pub state: CatalogState,
74 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
76 pub new_builtin_collections: BTreeSet<GlobalId>,
78 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
80 pub last_seen_version: String,
82 pub expr_cache_handle: Option<ExpressionCacheHandle>,
84 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
86 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
88}
89
90pub struct OpenCatalogResult {
91 pub catalog: Catalog,
93 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
95 pub new_builtin_collections: BTreeSet<GlobalId>,
97 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
99 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
101 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
103}
104
105impl Catalog {
106 pub async fn initialize_state<'a>(
110 config: StateConfig,
111 storage: &'a mut Box<dyn mz_catalog::durable::DurableCatalogState>,
112 ) -> Result<InitializeStateResult, AdapterError> {
113 for builtin_role in BUILTIN_ROLES {
114 assert!(
115 is_reserved_name(builtin_role.name),
116 "builtin role {builtin_role:?} must start with one of the following prefixes {}",
117 BUILTIN_PREFIXES.join(", ")
118 );
119 }
120 for builtin_cluster in BUILTIN_CLUSTERS {
121 assert!(
122 is_reserved_name(builtin_cluster.name),
123 "builtin cluster {builtin_cluster:?} must start with one of the following prefixes {}",
124 BUILTIN_PREFIXES.join(", ")
125 );
126 }
127
128 let mut system_configuration = SystemVars::new().set_unsafe(config.unsafe_mode);
129 if config.all_features {
130 system_configuration.enable_all_feature_flags_by_default();
131 }
132
133 let mut state = CatalogState {
134 database_by_name: imbl::OrdMap::new(),
135 database_by_id: imbl::OrdMap::new(),
136 entry_by_id: imbl::OrdMap::new(),
137 entry_by_global_id: imbl::OrdMap::new(),
138 notices_by_dep_id: imbl::OrdMap::new(),
139 ambient_schemas_by_name: imbl::OrdMap::new(),
140 ambient_schemas_by_id: imbl::OrdMap::new(),
141 clusters_by_name: imbl::OrdMap::new(),
142 clusters_by_id: imbl::OrdMap::new(),
143 roles_by_name: imbl::OrdMap::new(),
144 roles_by_id: imbl::OrdMap::new(),
145 network_policies_by_id: imbl::OrdMap::new(),
146 role_auth_by_id: imbl::OrdMap::new(),
147 network_policies_by_name: imbl::OrdMap::new(),
148 system_configuration: Arc::new(system_configuration),
149 scoped_system_parameters: Default::default(),
150 default_privileges: Arc::new(DefaultPrivileges::default()),
151 system_privileges: Arc::new(PrivilegeMap::default()),
152 comments: Arc::new(CommentsMap::default()),
153 source_references: imbl::OrdMap::new(),
154 storage_metadata: Arc::new(StorageMetadata::default()),
155 temporary_schemas: imbl::OrdMap::new(),
156 mock_authentication_nonce: Default::default(),
157 config: mz_sql::catalog::CatalogConfig {
158 start_time: to_datetime((config.now)()),
159 start_instant: Instant::now(),
160 nonce: rand::random(),
161 environment_id: config.environment_id,
162 session_id: Uuid::new_v4(),
163 build_info: config.build_info,
164 now: config.now.clone(),
165 connection_context: config.connection_context,
166 helm_chart_version: config.helm_chart_version,
167 },
168 cluster_replica_sizes: config.cluster_replica_sizes,
169 availability_zones: config.availability_zones,
170 egress_addresses: config.egress_addresses,
171 aws_principal_context: config.aws_principal_context,
172 aws_privatelink_availability_zones: config.aws_privatelink_availability_zones,
173 http_host_name: config.http_host_name,
174 license_key: config.license_key,
175 };
176
177 let deploy_generation = storage.get_deployment_generation().await?;
178
179 let mut updates: Vec<_> = storage.sync_to_current_updates().await?;
180 assert!(!updates.is_empty(), "initial catalog snapshot is missing");
181 let mut txn = storage.transaction().await?;
182
183 let new_builtin_collections = {
185 migrate::durable_migrate(
186 &mut txn,
187 state.config.environment_id.organization_id(),
188 config.boot_ts,
189 )?;
190 if let Some(remote_system_parameters) = config.remote_system_parameters {
193 for (name, value) in remote_system_parameters {
194 txn.upsert_system_config(&name, value)?;
195 }
196 txn.set_system_config_synced_once()?;
197 }
198 let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?;
200 let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap {
201 system_cluster: config.builtin_system_cluster_config,
202 catalog_server_cluster: config.builtin_catalog_server_cluster_config,
203 probe_cluster: config.builtin_probe_cluster_config,
204 support_cluster: config.builtin_support_cluster_config,
205 analytics_cluster: config.builtin_analytics_cluster_config,
206 };
207 add_new_remove_old_builtin_clusters_migration(
208 &mut txn,
209 &builtin_bootstrap_cluster_config_map,
210 config.boot_ts,
211 )?;
212 add_new_remove_old_builtin_introspection_source_migration(&mut txn)?;
213 add_new_remove_old_builtin_cluster_replicas_migration(
214 &mut txn,
215 &builtin_bootstrap_cluster_config_map,
216 config.boot_ts,
217 )?;
218 add_new_remove_old_builtin_roles_migration(&mut txn)?;
219 remove_invalid_config_param_role_defaults_migration(&mut txn)?;
220 remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?;
221
222 new_builtin_collections
223 };
224
225 let op_updates = txn.get_and_commit_op_updates();
226 updates.extend(op_updates);
227
228 let mut builtin_table_updates = Vec::new();
229
230 {
232 for (name, value) in config.system_parameter_defaults {
235 match state.set_system_configuration_default(&name, VarInput::Flat(&value)) {
236 Ok(_) => (),
237 Err(Error {
238 kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
239 }) => {
240 warn!(%name, "cannot load unknown system parameter from catalog storage to set default parameter");
241 }
242 Err(e) => return Err(e.into()),
243 };
244 }
245 state.create_temporary_schema(&SYSTEM_CONN_ID, MZ_SYSTEM_ROLE_ID)?;
246 }
247
248 let mut updates = into_consolidatable_updates_startup(updates, config.boot_ts);
251 differential_dataflow::consolidation::consolidate_updates(&mut updates);
252 soft_assert_no_log!(
253 updates.iter().all(|(_, _, diff)| *diff == Diff::ONE),
254 "consolidated updates should be positive during startup: {updates:?}"
255 );
256
257 let mut pre_item_updates = Vec::new();
258 let mut system_item_updates = Vec::new();
259 let mut item_updates = Vec::new();
260 let mut post_item_updates = Vec::new();
261 let mut audit_log_updates = Vec::new();
262 for (kind, ts, diff) in updates {
263 match kind {
264 BootstrapStateUpdateKind::Role(_)
265 | BootstrapStateUpdateKind::RoleAuth(_)
266 | BootstrapStateUpdateKind::Database(_)
267 | BootstrapStateUpdateKind::Schema(_)
268 | BootstrapStateUpdateKind::DefaultPrivilege(_)
269 | BootstrapStateUpdateKind::SystemPrivilege(_)
270 | BootstrapStateUpdateKind::SystemConfiguration(_)
271 | BootstrapStateUpdateKind::ClusterSystemConfiguration(_)
272 | BootstrapStateUpdateKind::ReplicaSystemConfiguration(_)
273 | BootstrapStateUpdateKind::Cluster(_)
274 | BootstrapStateUpdateKind::NetworkPolicy(_)
275 | BootstrapStateUpdateKind::ClusterReplica(_) => {
276 pre_item_updates.push(StateUpdate {
277 kind: kind.into(),
278 ts,
279 diff: diff.try_into().expect("valid diff"),
280 })
281 }
282 BootstrapStateUpdateKind::IntrospectionSourceIndex(_)
283 | BootstrapStateUpdateKind::SystemObjectMapping(_) => {
284 system_item_updates.push(StateUpdate {
285 kind: kind.into(),
286 ts,
287 diff: diff.try_into().expect("valid diff"),
288 })
289 }
290 BootstrapStateUpdateKind::Item(_) => item_updates.push(StateUpdate {
291 kind: kind.into(),
292 ts,
293 diff: diff.try_into().expect("valid diff"),
294 }),
295 BootstrapStateUpdateKind::Comment(_)
296 | BootstrapStateUpdateKind::StorageCollectionMetadata(_)
297 | BootstrapStateUpdateKind::SourceReferences(_)
298 | BootstrapStateUpdateKind::UnfinalizedShard(_) => {
299 post_item_updates.push((kind, ts, diff));
300 }
301 BootstrapStateUpdateKind::AuditLog(_) => {
302 audit_log_updates.push(StateUpdate {
303 kind: kind.into(),
304 ts,
305 diff: diff.try_into().expect("valid diff"),
306 });
307 }
308 }
309 }
310
311 let (builtin_table_update, _catalog_updates) = state
312 .apply_updates(pre_item_updates, &mut LocalExpressionCache::Closed)
313 .await;
314 builtin_table_updates.extend(builtin_table_update);
315
316 {
320 if let Some(password) = config.external_login_password_mz_system {
321 let role_auth = RoleAuth {
322 role_id: MZ_SYSTEM_ROLE_ID,
323 password_hash: Some(
326 scram256_hash(&password, &NonZeroU32::new(600_000).expect("known valid"))
327 .map_err(|_| {
328 AdapterError::Internal("Failed to hash mz_system password.".to_owned())
329 })?,
330 ),
331 updated_at: SYSTEM_TIME(),
332 };
333 state
334 .role_auth_by_id
335 .insert(MZ_SYSTEM_ROLE_ID, role_auth.clone());
336 let builtin_table_update = state.generate_builtin_table_update(
337 mz_catalog::memory::objects::StateUpdateKind::RoleAuth(role_auth.into()),
338 mz_catalog::memory::objects::StateDiff::Addition,
339 );
340 builtin_table_updates.extend(builtin_table_update);
341 }
342 }
343
344 let expr_cache_start = Instant::now();
345 info!("startup: coordinator init: catalog open: expr cache open beginning");
346 let enable_expr_cache_dyncfg = ENABLE_EXPRESSION_CACHE.get(state.system_config().dyncfgs());
349 let expr_cache_enabled = config
350 .enable_expression_cache_override
351 .unwrap_or(enable_expr_cache_dyncfg);
352 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) = if expr_cache_enabled {
353 info!(
354 ?config.enable_expression_cache_override,
355 ?enable_expr_cache_dyncfg,
356 "using expression cache for startup"
357 );
358 let current_ids = txn
359 .get_items()
360 .flat_map(|item| {
361 let gid = item.global_id.clone();
362 let gids: Vec<_> = item.extra_versions.values().cloned().collect();
363 std::iter::once(gid).chain(gids)
364 })
365 .chain(
366 txn.get_system_object_mappings()
367 .map(|som| som.unique_identifier.global_id),
368 )
369 .collect();
370 let dyncfgs = config.persist_client.dyncfgs().clone();
371 let build_version = if config.build_info.is_dev() {
372 config
375 .build_info
376 .semver_version_build()
377 .expect("build ID is not available on your platform!")
378 } else {
379 config.build_info.semver_version()
380 };
381 let expr_cache_config = ExpressionCacheConfig {
382 build_version,
383 shard_id: txn
384 .get_expression_cache_shard()
385 .expect("expression cache shard should exist for opened catalogs"),
386 persist: config.persist_client,
387 current_ids,
388 remove_prior_versions: !config.read_only,
389 compact_shard: config.read_only,
390 dyncfgs,
391 };
392 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) =
393 ExpressionCacheHandle::spawn_expression_cache(expr_cache_config).await;
394 (
395 Some(expr_cache_handle),
396 cached_local_exprs,
397 cached_global_exprs,
398 )
399 } else {
400 (None, BTreeMap::new(), BTreeMap::new())
401 };
402 let mut local_expr_cache = LocalExpressionCache::new(cached_local_exprs);
403 info!(
404 "startup: coordinator init: catalog open: expr cache open complete in {:?}",
405 expr_cache_start.elapsed()
406 );
407
408 let (builtin_table_update, _catalog_updates) = state
414 .apply_updates(system_item_updates, &mut local_expr_cache)
415 .await;
416 builtin_table_updates.extend(builtin_table_update);
417
418 let last_seen_version =
419 get_migration_version(&txn).map_or_else(|| "new".into(), |v| v.to_string());
420
421 let mz_authentication_mock_nonce =
422 txn.get_authentication_mock_nonce().ok_or_else(|| {
423 Error::new(ErrorKind::SettingError("authentication nonce".to_string()))
424 })?;
425
426 state.mock_authentication_nonce = Some(mz_authentication_mock_nonce);
427
428 let (builtin_table_update, _catalog_updates) = if !config.skip_migrations {
430 let migrate_result = migrate::migrate(
431 &mut state,
432 &mut txn,
433 &mut local_expr_cache,
434 item_updates,
435 config.now,
436 config.boot_ts,
437 )
438 .await
439 .map_err(|e| {
440 Error::new(ErrorKind::FailedCatalogMigration {
441 last_seen_version: last_seen_version.clone(),
442 this_version: config.build_info.version,
443 cause: e.to_string(),
444 })
445 })?;
446 if !migrate_result.post_item_updates.is_empty() {
447 post_item_updates.extend(migrate_result.post_item_updates);
450 if let Some(max_ts) = post_item_updates.iter().map(|(_, ts, _)| ts).max().cloned() {
452 for (_, ts, _) in &mut post_item_updates {
453 *ts = max_ts;
454 }
455 }
456 differential_dataflow::consolidation::consolidate_updates(&mut post_item_updates);
457 }
458
459 (
460 migrate_result.builtin_table_updates,
461 migrate_result.catalog_updates,
462 )
463 } else {
464 state
465 .apply_updates(item_updates, &mut local_expr_cache)
466 .await
467 };
468 builtin_table_updates.extend(builtin_table_update);
469
470 let post_item_updates = post_item_updates
471 .into_iter()
472 .map(|(kind, ts, diff)| StateUpdate {
473 kind: kind.into(),
474 ts,
475 diff: diff.try_into().expect("valid diff"),
476 })
477 .collect();
478 let (builtin_table_update, _catalog_updates) = state
479 .apply_updates(post_item_updates, &mut local_expr_cache)
480 .await;
481 builtin_table_updates.extend(builtin_table_update);
482
483 for audit_log_update in audit_log_updates {
487 builtin_table_updates.extend(
488 state.generate_builtin_table_update(audit_log_update.kind, audit_log_update.diff),
489 );
490 }
491
492 let schema_migration_result = builtin_schema_migration::run(
494 config.build_info,
495 deploy_generation,
496 &mut txn,
497 config.builtin_item_migration_config,
498 )
499 .await?;
500
501 let state_updates = txn.get_and_commit_op_updates();
502
503 let (table_updates, _catalog_updates) = state
509 .apply_updates(state_updates, &mut local_expr_cache)
510 .await;
511 builtin_table_updates.extend(table_updates);
512 let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates);
513
514 set_migration_version(&mut txn, config.build_info.semver_version())?;
516
517 txn.commit(config.boot_ts).await?;
518
519 schema_migration_result.cleanup_action.await;
521
522 Ok(InitializeStateResult {
523 state,
524 migrated_storage_collections_0dt: schema_migration_result.replaced_items,
525 new_builtin_collections: new_builtin_collections.into_iter().collect(),
526 builtin_table_updates,
527 last_seen_version,
528 expr_cache_handle,
529 cached_global_exprs,
530 uncached_local_exprs: local_expr_cache.into_uncached_exprs(),
531 })
532 }
533
534 #[instrument(name = "catalog::open")]
545 pub fn open(config: Config<'_>) -> BoxFuture<'static, Result<OpenCatalogResult, AdapterError>> {
546 async move {
547 let mut storage = config.storage;
548
549 let InitializeStateResult {
550 state,
551 migrated_storage_collections_0dt,
552 new_builtin_collections,
553 mut builtin_table_updates,
554 last_seen_version: _,
555 expr_cache_handle,
556 cached_global_exprs,
557 uncached_local_exprs,
558 } =
559 Self::initialize_state(config.state, &mut storage)
563 .instrument(tracing::info_span!("catalog::initialize_state"))
564 .boxed()
565 .await?;
566
567 let catalog = Catalog {
568 state,
569 expr_cache_handle,
570 transient_revision: 1,
571 shared_transient_revision: Arc::new(AtomicU64::new(1)),
572 storage: Arc::new(tokio::sync::Mutex::new(storage)),
573 };
574
575 for (op, func) in OP_IMPLS.iter() {
578 match func {
579 mz_sql::func::Func::Scalar(impls) => {
580 for imp in impls {
581 builtin_table_updates.push(catalog.state.resolve_builtin_table_update(
582 catalog.state.pack_op_update(op, imp.details(), Diff::ONE),
583 ));
584 }
585 }
586 _ => unreachable!("all operators must be scalar functions"),
587 }
588 }
589
590 for ip in &catalog.state.egress_addresses {
591 builtin_table_updates.push(
592 catalog
593 .state
594 .resolve_builtin_table_update(catalog.state.pack_egress_ip_update(ip)?),
595 );
596 }
597
598 if !catalog.state.license_key.id.is_empty() {
599 builtin_table_updates.push(
600 catalog.state.resolve_builtin_table_update(
601 catalog
602 .state
603 .pack_license_key_update(&catalog.state.license_key)?,
604 ),
605 );
606 }
607
608 catalog.storage().await.mark_bootstrap_complete().await;
609
610 Ok(OpenCatalogResult {
611 catalog,
612 migrated_storage_collections_0dt,
613 new_builtin_collections,
614 builtin_table_updates,
615 cached_global_exprs,
616 uncached_local_exprs,
617 })
618 }
619 .instrument(tracing::info_span!("catalog::open"))
620 .boxed()
621 }
622
623 async fn initialize_storage_state(
630 &mut self,
631 storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
632 ) -> Result<(), mz_catalog::durable::CatalogError> {
633 let collections = self
634 .entries()
635 .filter(|entry| entry.item().is_storage_collection())
636 .flat_map(|entry| entry.global_ids())
637 .collect();
638
639 let mut state = self.state.clone();
642
643 let mut storage = self.storage().await;
644 let shard_id = storage.shard_id();
645 let mut txn = storage.transaction().await?;
646
647 let item_id = self.resolve_builtin_storage_collection(&MZ_CATALOG_RAW);
650 let global_id = self.get_entry(&item_id).latest_global_id();
651 match txn.get_collection_metadata().get(&global_id) {
652 None => {
653 txn.insert_collection_metadata([(global_id, shard_id)].into())
654 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
655 }
656 Some(id) => assert_eq!(*id, shard_id),
657 }
658
659 storage_collections
660 .initialize_state(&mut txn, collections)
661 .await
662 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
663
664 let updates = txn.get_and_commit_op_updates();
665 let (builtin_updates, catalog_updates) = state
666 .apply_updates(updates, &mut LocalExpressionCache::Closed)
667 .await;
668 assert!(
669 builtin_updates.is_empty(),
670 "storage is not allowed to generate catalog changes that would cause changes to builtin tables"
671 );
672 assert!(
673 catalog_updates.is_empty(),
674 "storage is not allowed to generate catalog changes that would change the catalog or controller state"
675 );
676 let commit_ts = txn.upper();
677 txn.commit(commit_ts).await?;
678 drop(storage);
679
680 self.state = state;
682 Ok(())
683 }
684
685 pub async fn initialize_controller(
688 &mut self,
689 config: mz_controller::ControllerConfig,
690 envd_epoch: core::num::NonZeroI64,
691 read_only: bool,
692 ) -> Result<mz_controller::Controller, mz_catalog::durable::CatalogError> {
693 let controller_start = Instant::now();
694 info!("startup: controller init: beginning");
695
696 let controller = {
697 let mut storage = self.storage().await;
698 let mut tx = storage.transaction().await?;
699 mz_controller::prepare_initialization(&mut tx)
700 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
701 let updates = tx.get_and_commit_op_updates();
702 assert!(
703 updates.is_empty(),
704 "initializing controller should not produce updates: {updates:?}"
705 );
706 let commit_ts = tx.upper();
707 tx.commit(commit_ts).await?;
708
709 let read_only_tx = storage.transaction().await?;
710
711 mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await
712 };
713
714 self.initialize_storage_state(&controller.storage_collections)
715 .await?;
716
717 info!(
718 "startup: controller init: complete in {:?}",
719 controller_start.elapsed()
720 );
721
722 Ok(controller)
723 }
724
725 pub async fn expire(self) {
727 if let Some(storage) = Arc::into_inner(self.storage) {
730 let storage = storage.into_inner();
731 storage.expire().await;
732 }
733 }
734}
735
736impl CatalogState {
737 fn set_system_configuration_default(
739 &mut self,
740 name: &str,
741 value: VarInput,
742 ) -> Result<(), Error> {
743 Ok(Arc::make_mut(&mut self.system_configuration).set_default(name, value)?)
744 }
745}
746
747fn add_new_remove_old_builtin_items_migration(
751 txn: &mut mz_catalog::durable::Transaction<'_>,
752) -> Result<Vec<GlobalId>, mz_catalog::durable::CatalogError> {
753 let mut new_builtin_mappings = Vec::new();
754 let mut builtin_descs = HashSet::new();
756
757 let mut builtins = Vec::new();
760 for builtin in BUILTINS::iter() {
761 let desc = SystemObjectDescription {
762 schema_name: builtin.schema().to_string(),
763 object_type: builtin.catalog_item_type(),
764 object_name: builtin.name().to_string(),
765 };
766 if !builtin_descs.insert(desc.clone()) {
768 panic!(
769 "duplicate builtin description: {:?}, {:?}",
770 SystemObjectDescription {
771 schema_name: builtin.schema().to_string(),
772 object_type: builtin.catalog_item_type(),
773 object_name: builtin.name().to_string(),
774 },
775 builtin
776 );
777 }
778 builtins.push((desc, builtin));
779 }
780
781 let mut system_object_mappings: BTreeMap<_, _> = txn
782 .get_system_object_mappings()
783 .map(|system_object_mapping| {
784 (
785 system_object_mapping.description.clone(),
786 system_object_mapping,
787 )
788 })
789 .collect();
790
791 let (existing_builtins, new_builtins): (Vec<_>, Vec<_>) =
792 builtins.into_iter().partition_map(|(desc, builtin)| {
793 let fingerprint = match builtin.runtime_alterable() {
794 false => builtin.fingerprint(),
795 true => RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into(),
796 };
797 match system_object_mappings.remove(&desc) {
798 Some(system_object_mapping) => {
799 Either::Left((builtin, system_object_mapping, fingerprint))
800 }
801 None => Either::Right((builtin, fingerprint)),
802 }
803 });
804 let new_builtin_ids = txn.allocate_system_item_ids(usize_to_u64(new_builtins.len()))?;
805 let new_builtins: Vec<_> = new_builtins
806 .into_iter()
807 .zip_eq(new_builtin_ids.clone())
808 .collect();
809
810 for ((builtin, fingerprint), (catalog_id, global_id)) in new_builtins.iter().cloned() {
812 new_builtin_mappings.push(SystemObjectMapping {
813 description: SystemObjectDescription {
814 schema_name: builtin.schema().to_string(),
815 object_type: builtin.catalog_item_type(),
816 object_name: builtin.name().to_string(),
817 },
818 unique_identifier: SystemObjectUniqueIdentifier {
819 catalog_id,
820 global_id,
821 fingerprint,
822 },
823 });
824
825 let handled_runtime_alterable = match builtin {
831 Builtin::Connection(c) if c.runtime_alterable => {
832 let mut acl_items = vec![rbac::owner_privilege(
833 mz_sql::catalog::ObjectType::Connection,
834 c.owner_id.clone(),
835 )];
836 acl_items.extend_from_slice(c.access);
837 let versions = BTreeMap::new();
839
840 txn.insert_item(
841 catalog_id,
842 c.oid,
843 global_id,
844 mz_catalog::durable::initialize::resolve_system_schema(c.schema).id,
845 c.name,
846 c.sql.into(),
847 *c.owner_id,
848 acl_items,
849 versions,
850 )?;
851 true
852 }
853 _ => false,
854 };
855 assert_eq!(
856 builtin.runtime_alterable(),
857 handled_runtime_alterable,
858 "runtime alterable object was not handled by migration",
859 );
860 }
861 txn.set_system_object_mappings(new_builtin_mappings)?;
862
863 let builtins_with_catalog_ids = existing_builtins
865 .iter()
866 .map(|(b, m, _)| (*b, m.unique_identifier.catalog_id))
867 .chain(
868 new_builtins
869 .into_iter()
870 .map(|((b, _), (catalog_id, _))| (b, catalog_id)),
871 );
872
873 for (builtin, id) in builtins_with_catalog_ids {
874 let (comment_id, desc, comments) = match builtin {
875 Builtin::Source(s) => (CommentObjectId::Source(id), &s.desc, &s.column_comments),
876 Builtin::View(v) => (CommentObjectId::View(id), &v.desc, &v.column_comments),
877 Builtin::Table(t) => (CommentObjectId::Table(id), &t.desc, &t.column_comments),
878 Builtin::MaterializedView(mv) => (
879 CommentObjectId::MaterializedView(id),
880 &mv.desc,
881 &mv.column_comments,
882 ),
883 Builtin::Log(_)
884 | Builtin::Type(_)
885 | Builtin::Func(_)
886 | Builtin::Index(_)
887 | Builtin::Connection(_) => continue,
888 };
889 txn.drop_comments(&BTreeSet::from_iter([
894 CommentObjectId::Table(id),
895 CommentObjectId::View(id),
896 CommentObjectId::MaterializedView(id),
897 CommentObjectId::Source(id),
898 ]))?;
899
900 let mut comments = comments.clone();
901 for (col_idx, name) in desc.iter_names().enumerate() {
902 if let Some(comment) = comments.remove(name.as_str()) {
903 txn.update_comment(comment_id, Some(col_idx + 1), Some(comment.to_owned()))?;
905 }
906 }
907 assert!(
908 comments.is_empty(),
909 "builtin object contains dangling comments that don't correspond to columns {comments:?}"
910 );
911 }
912
913 let mut deleted_system_objects = BTreeSet::new();
916 let mut deleted_runtime_alterable_system_ids = BTreeSet::new();
917 let mut deleted_comments = BTreeSet::new();
918 for (desc, mapping) in system_object_mappings {
919 deleted_system_objects.insert(mapping.description);
920 if mapping.unique_identifier.fingerprint == RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL {
921 deleted_runtime_alterable_system_ids.insert(mapping.unique_identifier.catalog_id);
922 }
923
924 let id = mapping.unique_identifier.catalog_id;
925 let comment_id = match desc.object_type {
926 CatalogItemType::Table => CommentObjectId::Table(id),
927 CatalogItemType::Source => CommentObjectId::Source(id),
928 CatalogItemType::View => CommentObjectId::View(id),
929 CatalogItemType::MaterializedView => CommentObjectId::MaterializedView(id),
930 CatalogItemType::Sink
931 | CatalogItemType::Index
932 | CatalogItemType::Type
933 | CatalogItemType::Func
934 | CatalogItemType::Secret
935 | CatalogItemType::Connection => continue,
936 };
937 deleted_comments.insert(comment_id);
938 }
939 let delete_exceptions: HashSet<SystemObjectDescription> = [].into();
945 assert!(
949 deleted_system_objects
950 .iter()
951 .filter(|object| object.object_type != CatalogItemType::Index)
953 .all(
954 |deleted_object| is_unstable_schema(&deleted_object.schema_name)
955 || delete_exceptions.contains(deleted_object)
956 ),
957 "only objects in unstable schemas can be deleted, deleted objects: {:?}",
958 deleted_system_objects
959 );
960 txn.drop_comments(&deleted_comments)?;
961 txn.remove_items(&deleted_runtime_alterable_system_ids)?;
962 txn.remove_system_object_mappings(deleted_system_objects)?;
963
964 let new_builtin_collections = new_builtin_ids
966 .into_iter()
967 .map(|(_catalog_id, global_id)| global_id)
968 .collect();
969
970 Ok(new_builtin_collections)
971}
972
973fn add_new_remove_old_builtin_clusters_migration(
974 txn: &mut mz_catalog::durable::Transaction<'_>,
975 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
976 boot_ts: Timestamp,
977) -> Result<(), mz_catalog::durable::CatalogError> {
978 let mut durable_clusters: BTreeMap<_, _> = txn
979 .get_clusters()
980 .filter(|cluster| cluster.id.is_system())
981 .map(|cluster| (cluster.name.to_string(), cluster))
982 .collect();
983
984 for builtin_cluster in BUILTIN_CLUSTERS {
986 if durable_clusters.remove(builtin_cluster.name).is_none() {
987 let cluster_config = builtin_cluster_config_map.get_config(builtin_cluster.name)?;
988
989 let cluster_id = txn.insert_system_cluster(
990 builtin_cluster.name,
991 vec![],
992 builtin_cluster.privileges.to_vec(),
993 builtin_cluster.owner_id.to_owned(),
994 mz_catalog::durable::ClusterConfig {
995 variant: mz_catalog::durable::ClusterVariant::Managed(ClusterVariantManaged {
996 size: cluster_config.size,
997 availability_zones: vec![],
998 replication_factor: cluster_config.replication_factor,
999 logging: default_logging_config(),
1000 optimizer_feature_overrides: Default::default(),
1001 schedule: Default::default(),
1002 auto_scaling_strategy: None,
1003 reconfiguration: None,
1004 burst: None,
1005 }),
1006 workload_class: None,
1007 },
1008 &HashSet::new(),
1009 )?;
1010
1011 let audit_id = txn.allocate_audit_log_id()?;
1012 txn.insert_audit_log_event(VersionedEvent::new(
1013 audit_id,
1014 EventType::Create,
1015 ObjectType::Cluster,
1016 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1017 id: cluster_id.to_string(),
1018 name: builtin_cluster.name.to_string(),
1019 }),
1020 None,
1021 boot_ts.into(),
1022 ));
1023 }
1024 }
1025
1026 let old_clusters = durable_clusters
1028 .values()
1029 .map(|cluster| cluster.id)
1030 .collect();
1031 txn.remove_clusters(&old_clusters)?;
1032
1033 for (_name, cluster) in &durable_clusters {
1034 let audit_id = txn.allocate_audit_log_id()?;
1035 txn.insert_audit_log_event(VersionedEvent::new(
1036 audit_id,
1037 EventType::Drop,
1038 ObjectType::Cluster,
1039 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1040 id: cluster.id.to_string(),
1041 name: cluster.name.clone(),
1042 }),
1043 None,
1044 boot_ts.into(),
1045 ));
1046 }
1047
1048 Ok(())
1049}
1050
1051fn add_new_remove_old_builtin_introspection_source_migration(
1052 txn: &mut mz_catalog::durable::Transaction<'_>,
1053) -> Result<(), AdapterError> {
1054 let mut new_indexes = Vec::new();
1055 let mut removed_indexes = BTreeSet::new();
1056 for cluster in txn.get_clusters() {
1057 let mut introspection_source_index_ids = txn.get_introspection_source_indexes(cluster.id);
1058
1059 let mut new_logs = Vec::new();
1060
1061 for log in BUILTINS::logs() {
1062 if introspection_source_index_ids.remove(log.name).is_none() {
1063 new_logs.push(log);
1064 }
1065 }
1066
1067 for log in new_logs {
1068 let (item_id, gid) =
1069 Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant);
1070 new_indexes.push((cluster.id, log.name.to_string(), item_id, gid));
1071 }
1072
1073 removed_indexes.extend(
1076 introspection_source_index_ids
1077 .into_keys()
1078 .map(|name| (cluster.id, name.to_string())),
1079 );
1080 }
1081 txn.insert_introspection_source_indexes(new_indexes, &HashSet::new())?;
1082 txn.remove_introspection_source_indexes(removed_indexes)?;
1083 Ok(())
1084}
1085
1086fn add_new_remove_old_builtin_roles_migration(
1087 txn: &mut mz_catalog::durable::Transaction<'_>,
1088) -> Result<(), mz_catalog::durable::CatalogError> {
1089 let mut durable_roles: BTreeMap<_, _> = txn
1090 .get_roles()
1091 .filter(|role| role.id.is_system() || role.id.is_predefined())
1092 .map(|role| (role.name.to_string(), role))
1093 .collect();
1094
1095 for builtin_role in BUILTIN_ROLES {
1097 if durable_roles.remove(builtin_role.name).is_none() {
1098 txn.insert_builtin_role(
1099 builtin_role.id,
1100 builtin_role.name.to_string(),
1101 builtin_role.attributes.clone(),
1102 RoleMembership::new(),
1103 RoleVars::default(),
1104 builtin_role.oid,
1105 )?;
1106 }
1107 }
1108
1109 let old_roles = durable_roles.values().map(|role| role.id).collect();
1111 txn.remove_roles(&old_roles)?;
1112
1113 Ok(())
1114}
1115
1116fn add_new_remove_old_builtin_cluster_replicas_migration(
1117 txn: &mut Transaction<'_>,
1118 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1119 boot_ts: Timestamp,
1120) -> Result<(), AdapterError> {
1121 let cluster_lookup: BTreeMap<_, _> = txn
1122 .get_clusters()
1123 .map(|cluster| (cluster.name.clone(), cluster.clone()))
1124 .collect();
1125
1126 let cluster_id_to_name: BTreeMap<ClusterId, String> = cluster_lookup
1127 .values()
1128 .map(|cluster| (cluster.id, cluster.name.clone()))
1129 .collect();
1130
1131 let mut durable_replicas: BTreeMap<ClusterId, BTreeMap<String, ClusterReplica>> = txn
1132 .get_cluster_replicas()
1133 .filter(|replica| replica.replica_id.is_system())
1134 .fold(BTreeMap::new(), |mut acc, replica| {
1135 acc.entry(replica.cluster_id)
1136 .or_insert_with(BTreeMap::new)
1137 .insert(replica.name.to_string(), replica);
1138 acc
1139 });
1140
1141 for builtin_replica in BUILTIN_CLUSTER_REPLICAS {
1143 let cluster = cluster_lookup
1144 .get(builtin_replica.cluster_name)
1145 .expect("builtin cluster replica references non-existent cluster");
1146 let mut empty_map: BTreeMap<String, ClusterReplica> = BTreeMap::new();
1148 let replica_names = durable_replicas
1149 .get_mut(&cluster.id)
1150 .unwrap_or(&mut empty_map);
1151
1152 let builtin_cluster_bootstrap_config =
1153 builtin_cluster_config_map.get_config(builtin_replica.cluster_name)?;
1154 if replica_names.remove(builtin_replica.name).is_none()
1155 && builtin_cluster_bootstrap_config.replication_factor > 0
1159 {
1160 let replica_size = match cluster.config.variant {
1161 ClusterVariant::Managed(ClusterVariantManaged { ref size, .. }) => size.clone(),
1162 ClusterVariant::Unmanaged => builtin_cluster_bootstrap_config.size.clone(),
1163 };
1164
1165 let config = builtin_cluster_replica_config(replica_size.clone());
1166 let replica_id = txn.allocate_system_replica_id()?;
1170 txn.insert_cluster_replica_with_id(
1171 cluster.id,
1172 replica_id,
1173 builtin_replica.name,
1174 config,
1175 MZ_SYSTEM_ROLE_ID,
1176 )?;
1177
1178 let audit_id = txn.allocate_audit_log_id()?;
1179 txn.insert_audit_log_event(VersionedEvent::new(
1180 audit_id,
1181 EventType::Create,
1182 ObjectType::ClusterReplica,
1183 EventDetails::CreateClusterReplicaV4(mz_audit_log::CreateClusterReplicaV4 {
1184 cluster_id: cluster.id.to_string(),
1185 cluster_name: cluster.name.clone(),
1186 replica_id: Some(replica_id.to_string()),
1187 replica_name: builtin_replica.name.to_string(),
1188 logical_size: replica_size,
1189 billed_as: None,
1190 internal: false,
1191 reason: CreateOrDropClusterReplicaReasonV1::System,
1192 scheduling_policies: None,
1193 }),
1194 None,
1195 boot_ts.into(),
1196 ));
1197 }
1198 }
1199
1200 let old_replicas: Vec<_> = durable_replicas
1202 .values()
1203 .flat_map(|replicas| replicas.values())
1204 .collect();
1205 let old_replica_ids = old_replicas.iter().map(|r| r.replica_id).collect();
1206 txn.remove_cluster_replicas(&old_replica_ids)?;
1207
1208 for replica in &old_replicas {
1209 let cluster_name = cluster_id_to_name
1210 .get(&replica.cluster_id)
1211 .cloned()
1212 .unwrap_or_else(|| "<unknown>".to_string());
1213
1214 let audit_id = txn.allocate_audit_log_id()?;
1215 txn.insert_audit_log_event(VersionedEvent::new(
1216 audit_id,
1217 EventType::Drop,
1218 ObjectType::ClusterReplica,
1219 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1220 cluster_id: replica.cluster_id.to_string(),
1221 cluster_name,
1222 replica_id: Some(replica.replica_id.to_string()),
1223 replica_name: replica.name.clone(),
1224 reason: CreateOrDropClusterReplicaReasonV1::System,
1225 scheduling_policies: None,
1226 }),
1227 None,
1228 boot_ts.into(),
1229 ));
1230 }
1231
1232 Ok(())
1233}
1234
1235fn remove_invalid_config_param_role_defaults_migration(
1242 txn: &mut Transaction<'_>,
1243) -> Result<(), AdapterError> {
1244 static BUILD_INFO: mz_build_info::BuildInfo = mz_build_info::build_info!();
1245
1246 let roles_to_migrate: BTreeMap<_, _> = txn
1247 .get_roles()
1248 .filter_map(|mut role| {
1249 let session_vars = SessionVars::new_unchecked(&BUILD_INFO, SYSTEM_USER.clone(), None);
1254
1255 let mut invalid_roles_vars = BTreeMap::new();
1257 for (name, value) in &role.vars.map {
1258 let Ok(session_var) = session_vars.inspect(name) else {
1260 invalid_roles_vars.insert(name.clone(), value.clone());
1261 continue;
1262 };
1263 if session_var.check(value.borrow()).is_err() {
1264 invalid_roles_vars.insert(name.clone(), value.clone());
1265 }
1266 }
1267
1268 if invalid_roles_vars.is_empty() {
1270 return None;
1271 }
1272
1273 tracing::warn!(?role, ?invalid_roles_vars, "removing invalid role vars");
1274
1275 for (name, _value) in invalid_roles_vars {
1277 role.vars.map.remove(&name);
1278 }
1279 Some(role)
1280 })
1281 .map(|role| (role.id, role))
1282 .collect();
1283
1284 txn.update_roles_without_auth(roles_to_migrate)?;
1285
1286 Ok(())
1287}
1288
1289fn remove_pending_cluster_replicas_migration(
1292 tx: &mut Transaction,
1293 boot_ts: mz_repr::Timestamp,
1294) -> Result<(), anyhow::Error> {
1295 let cluster_names: BTreeMap<_, _> = tx.get_clusters().map(|c| (c.id, c.name)).collect();
1297
1298 let occurred_at = boot_ts.into();
1299
1300 for replica in tx.get_cluster_replicas().collect::<Vec<_>>() {
1301 if let mz_catalog::durable::ReplicaLocation::Managed { pending: true, .. } =
1302 replica.config.location
1303 {
1304 let cluster_name = cluster_names
1305 .get(&replica.cluster_id)
1306 .cloned()
1307 .unwrap_or_else(|| "<unknown>".to_string());
1308
1309 info!(
1310 "removing pending cluster replica '{}' from cluster '{}'",
1311 replica.name, cluster_name,
1312 );
1313
1314 tx.remove_cluster_replica(replica.replica_id)?;
1315
1316 let audit_id = tx.allocate_audit_log_id()?;
1320 tx.insert_audit_log_event(VersionedEvent::new(
1321 audit_id,
1322 EventType::Drop,
1323 ObjectType::ClusterReplica,
1324 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1325 cluster_id: replica.cluster_id.to_string(),
1326 cluster_name,
1327 replica_id: Some(replica.replica_id.to_string()),
1328 replica_name: replica.name,
1329 reason: CreateOrDropClusterReplicaReasonV1::System,
1330 scheduling_policies: None,
1331 }),
1332 None,
1333 occurred_at,
1334 ));
1335 }
1336 }
1337 Ok(())
1338}
1339
1340pub(crate) fn builtin_cluster_replica_config(
1341 replica_size: String,
1342) -> mz_catalog::durable::ReplicaConfig {
1343 mz_catalog::durable::ReplicaConfig {
1344 location: mz_catalog::durable::ReplicaLocation::Managed {
1345 availability_zones: Vec::new(),
1346 billed_as: None,
1347 pending: false,
1348 internal: false,
1349 size: replica_size,
1350 },
1351 logging: default_logging_config(),
1352 }
1353}
1354
1355fn default_logging_config() -> ReplicaLogging {
1356 ReplicaLogging {
1357 log_logging: false,
1358 interval: Some(Duration::from_secs(1)),
1359 }
1360}
1361
1362#[derive(Debug)]
1363pub struct BuiltinBootstrapClusterConfigMap {
1364 pub system_cluster: BootstrapBuiltinClusterConfig,
1366 pub catalog_server_cluster: BootstrapBuiltinClusterConfig,
1368 pub probe_cluster: BootstrapBuiltinClusterConfig,
1370 pub support_cluster: BootstrapBuiltinClusterConfig,
1372 pub analytics_cluster: BootstrapBuiltinClusterConfig,
1374}
1375
1376impl BuiltinBootstrapClusterConfigMap {
1377 fn get_config(
1379 &self,
1380 cluster_name: &str,
1381 ) -> Result<BootstrapBuiltinClusterConfig, mz_catalog::durable::CatalogError> {
1382 let cluster_config = if cluster_name == mz_catalog::builtin::MZ_SYSTEM_CLUSTER.name {
1383 &self.system_cluster
1384 } else if cluster_name == mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER.name {
1385 &self.catalog_server_cluster
1386 } else if cluster_name == mz_catalog::builtin::MZ_PROBE_CLUSTER.name {
1387 &self.probe_cluster
1388 } else if cluster_name == mz_catalog::builtin::MZ_SUPPORT_CLUSTER.name {
1389 &self.support_cluster
1390 } else if cluster_name == mz_catalog::builtin::MZ_ANALYTICS_CLUSTER.name {
1391 &self.analytics_cluster
1392 } else {
1393 return Err(mz_catalog::durable::CatalogError::Catalog(
1394 SqlCatalogError::UnexpectedBuiltinCluster(cluster_name.to_owned()),
1395 ));
1396 };
1397 Ok(cluster_config.clone())
1398 }
1399}
1400
1401pub(crate) fn into_consolidatable_updates_startup(
1418 updates: Vec<StateUpdate>,
1419 ts: Timestamp,
1420) -> Vec<(BootstrapStateUpdateKind, Timestamp, Diff)> {
1421 updates
1422 .into_iter()
1423 .map(|StateUpdate { kind, ts: _, diff }| {
1424 let kind: BootstrapStateUpdateKind = kind
1425 .try_into()
1426 .unwrap_or_else(|e| panic!("temporary items do not exist during bootstrap: {e:?}"));
1427 (kind, ts, Diff::from(diff))
1428 })
1429 .collect()
1430}