Skip to main content

mz_adapter/catalog/
apply.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Logic related to applying updates from a [`mz_catalog::durable::DurableCatalogState`] to a
11//! [`CatalogState`].
12
13use std::collections::{BTreeMap, BTreeSet, VecDeque};
14use std::fmt::Debug;
15use std::iter;
16use std::str::FromStr;
17use std::sync::Arc;
18
19use differential_dataflow::consolidation::consolidate_updates;
20use futures::future;
21use itertools::{Either, Itertools};
22use mz_adapter_types::connection::ConnectionId;
23use mz_catalog::SYSTEM_CONN_ID;
24use mz_catalog::builtin::{
25    BUILTIN_LOG_LOOKUP, BUILTIN_LOOKUP, Builtin, BuiltinLog, BuiltinTable, BuiltinView,
26};
27use mz_catalog::durable::objects::{
28    ClusterKey, DatabaseKey, DurableType, ItemKey, NetworkPolicyKey, RoleAuthKey, RoleKey,
29    SchemaKey,
30};
31use mz_catalog::durable::{CatalogError, SystemObjectMapping};
32use mz_catalog::memory::error::{Error, ErrorKind};
33use mz_catalog::memory::objects::{
34    CatalogEntry, CatalogItem, Cluster, ClusterReplica, Database, Func, Index, Log, NetworkPolicy,
35    Role, RoleAuth, Schema, Source, StateDiff, StateUpdate, StateUpdateKind, Table,
36    TableDataSource, TemporaryItem, Type, UpdateFrom,
37};
38use mz_compute_types::config::ComputeReplicaConfig;
39use mz_compute_types::dataflows::DataflowDescription;
40use mz_controller::clusters::{ReplicaConfig, ReplicaLogging};
41use mz_controller_types::ClusterId;
42use mz_expr::MirScalarExpr;
43use mz_ore::collections::CollectionExt;
44use mz_ore::tracing::OpenTelemetryContext;
45use mz_ore::{
46    instrument, soft_assert_eq_or_log, soft_assert_no_log, soft_assert_or_log, soft_panic_or_log,
47};
48use mz_pgrepr::oid::INVALID_OID;
49use mz_repr::adt::mz_acl_item::{MzAclItem, PrivilegeMap};
50use mz_repr::role_id::RoleId;
51use mz_repr::{CatalogItemId, Diff, GlobalId, RelationVersion, Timestamp, VersionedRelationDesc};
52use mz_sql::catalog::CatalogError as SqlCatalogError;
53use mz_sql::catalog::{CatalogItem as SqlCatalogItem, CatalogItemType, CatalogSchema, CatalogType};
54use mz_sql::names::{
55    FullItemName, ItemQualifiers, QualifiedItemName, RawDatabaseSpecifier,
56    ResolvedDatabaseSpecifier, ResolvedIds, SchemaSpecifier,
57};
58use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
59use mz_sql::session::vars::{VarError, VarInput};
60use mz_sql::{plan, rbac};
61use mz_sql_parser::ast::Expr;
62use mz_storage_types::sources::Timeline;
63use mz_transform::dataflow::DataflowMetainfo;
64use mz_transform::notice::OptimizerNotice;
65use tracing::{info_span, warn};
66
67use crate::AdapterError;
68use crate::catalog::state::LocalExpressionCache;
69use crate::catalog::{BuiltinTableUpdate, CatalogState};
70use crate::coord::catalog_implications::parsed_state_updates::{self, ParsedStateUpdate};
71use crate::util::{index_sql, sort_topological};
72
73/// Maintains the state of retractions while applying catalog state updates for a single timestamp.
74/// [`CatalogState`] maintains denormalized state for certain catalog objects. Updating an object
75/// results in applying a retraction for that object followed by applying an addition for that
76/// object. When applying those additions it can be extremely expensive to re-build that
77/// denormalized state from scratch. To avoid that issue we stash the denormalized state from
78/// retractions, so it can be used during additions.
79///
80/// Not all objects maintain denormalized state, so we only stash the retractions for the subset of
81/// objects that maintain denormalized state.
82// TODO(jkosh44) It might be simpler or more future proof to include all object types here, even if
83// the update step is a no-op for certain types.
84#[derive(Debug, Clone, Default)]
85struct InProgressRetractions {
86    roles: BTreeMap<RoleKey, Role>,
87    role_auths: BTreeMap<RoleAuthKey, RoleAuth>,
88    databases: BTreeMap<DatabaseKey, Database>,
89    schemas: BTreeMap<SchemaKey, Schema>,
90    clusters: BTreeMap<ClusterKey, Cluster>,
91    network_policies: BTreeMap<NetworkPolicyKey, NetworkPolicy>,
92    items: BTreeMap<ItemKey, CatalogEntry>,
93    temp_items: BTreeMap<CatalogItemId, CatalogEntry>,
94    introspection_source_indexes: BTreeMap<CatalogItemId, CatalogEntry>,
95    system_object_mappings: BTreeMap<CatalogItemId, CatalogEntry>,
96}
97
98impl CatalogState {
99    /// Update in-memory catalog state from a list of updates made to the durable catalog state.
100    ///
101    /// Returns builtin table updates corresponding to the changes to catalog state.
102    #[must_use]
103    #[instrument]
104    pub(crate) async fn apply_updates(
105        &mut self,
106        updates: Vec<StateUpdate>,
107        local_expression_cache: &mut LocalExpressionCache,
108    ) -> (
109        Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
110        Vec<ParsedStateUpdate>,
111    ) {
112        let mut builtin_table_updates = Vec::with_capacity(updates.len());
113        let mut catalog_updates = Vec::with_capacity(updates.len());
114
115        // First, consolidate updates. The code that applies parsed state
116        // updates _requires_ that the given updates are consolidated. There
117        // must be at most one addition and/or one retraction for a given item,
118        // as identified by that items ID type.
119        let updates = Self::consolidate_updates(updates);
120
121        // Apply updates in groups, according to their timestamps.
122        let mut groups: Vec<Vec<_>> = Vec::new();
123        for (_, updates) in &updates.into_iter().chunk_by(|update| update.ts) {
124            // Bring the updates into the pseudo-topological order that we need
125            // for updating our in-memory state and generating builtin table
126            // updates.
127            let updates = sort_updates(updates.collect());
128            groups.push(updates);
129        }
130
131        for updates in groups {
132            let mut apply_state = ApplyState::Updates(Vec::new());
133            let mut retractions = InProgressRetractions::default();
134
135            for update in updates {
136                let (next_apply_state, (builtin_table_update, catalog_update)) = apply_state
137                    .step(
138                        ApplyState::new(update),
139                        self,
140                        &mut retractions,
141                        local_expression_cache,
142                    )
143                    .await;
144                apply_state = next_apply_state;
145                builtin_table_updates.extend(builtin_table_update);
146                catalog_updates.extend(catalog_update);
147            }
148
149            // Apply remaining state.
150            let (builtin_table_update, catalog_update) = apply_state
151                .apply(self, &mut retractions, local_expression_cache)
152                .await;
153            builtin_table_updates.extend(builtin_table_update);
154            catalog_updates.extend(catalog_update);
155
156            // Clean up plans and optimizer notices for items that
157            // were retracted but not replaced (i.e., truly dropped).
158            let dropped_entries: Vec<CatalogEntry> = retractions
159                .items
160                .into_values()
161                .chain(retractions.temp_items.into_values())
162                .collect();
163            if !dropped_entries.is_empty() {
164                let dropped_notices = self.drop_optimizer_notices(dropped_entries);
165                if self.system_config().enable_mz_notices() {
166                    self.pack_optimizer_notice_updates(
167                        &mut builtin_table_updates,
168                        dropped_notices.iter(),
169                        Diff::MINUS_ONE,
170                    );
171                }
172            }
173        }
174
175        (builtin_table_updates, catalog_updates)
176    }
177
178    /// It can happen that the sequencing logic creates "fluctuating" updates
179    /// for a given catalog ID. For example, when doing a `DROP OWNED BY ...`,
180    /// for a table, there will be a retraction of the original table state,
181    /// then an addition for the same table but stripped of some of the roles
182    /// and access things, and then a retraction for that intermediate table
183    /// state. By consolidating, the intermediate state addition/retraction will
184    /// cancel out and we'll only see the retraction for the original state.
185    fn consolidate_updates(updates: Vec<StateUpdate>) -> Vec<StateUpdate> {
186        let mut updates: Vec<(StateUpdateKind, Timestamp, mz_repr::Diff)> = updates
187            .into_iter()
188            .map(|update| (update.kind, update.ts, update.diff.into()))
189            .collect_vec();
190
191        consolidate_updates(&mut updates);
192
193        updates
194            .into_iter()
195            .map(|(kind, ts, diff)| StateUpdate {
196                kind,
197                ts,
198                diff: diff
199                    .try_into()
200                    .expect("catalog state cannot have diff other than -1 or 1"),
201            })
202            .collect_vec()
203    }
204
205    #[instrument(level = "debug")]
206    fn apply_updates_inner(
207        &mut self,
208        updates: Vec<StateUpdate>,
209        retractions: &mut InProgressRetractions,
210        local_expression_cache: &mut LocalExpressionCache,
211    ) -> Result<
212        (
213            Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
214            Vec<ParsedStateUpdate>,
215        ),
216        CatalogError,
217    > {
218        soft_assert_no_log!(
219            updates.iter().map(|update| update.ts).all_equal(),
220            "all timestamps should be equal: {updates:?}"
221        );
222
223        let mut update_system_config = false;
224
225        let mut builtin_table_updates = Vec::with_capacity(updates.len());
226        let mut catalog_updates = Vec::new();
227
228        for state_update in updates {
229            if matches!(state_update.kind, StateUpdateKind::SystemConfiguration(_)) {
230                update_system_config = true;
231            }
232
233            match state_update.diff {
234                StateDiff::Retraction => {
235                    // We want the parsed catalog updates to match the state of
236                    // the catalog _before_ applying a retraction. So that we can
237                    // still have useful in-memory state to work with.
238                    if let Some(update) =
239                        parsed_state_updates::parse_state_update(self, state_update.clone())
240                    {
241                        catalog_updates.push(update);
242                    }
243
244                    // We want the builtin table retraction to match the state of the catalog
245                    // before applying the update.
246                    builtin_table_updates.extend(self.generate_builtin_table_update(
247                        state_update.kind.clone(),
248                        state_update.diff,
249                    ));
250                    self.apply_update(
251                        state_update.kind,
252                        state_update.diff,
253                        retractions,
254                        local_expression_cache,
255                    )?;
256                }
257                StateDiff::Addition => {
258                    self.apply_update(
259                        state_update.kind.clone(),
260                        state_update.diff,
261                        retractions,
262                        local_expression_cache,
263                    )?;
264                    // We want the builtin table addition to match the state of
265                    // the catalog after applying the update. So that we already
266                    // have useful in-memory state to work with.
267                    builtin_table_updates.extend(self.generate_builtin_table_update(
268                        state_update.kind.clone(),
269                        state_update.diff,
270                    ));
271
272                    // We want the parsed catalog updates to match the state of
273                    // the catalog _after_ applying an addition.
274                    if let Some(update) =
275                        parsed_state_updates::parse_state_update(self, state_update.clone())
276                    {
277                        catalog_updates.push(update);
278                    }
279                }
280            }
281        }
282
283        if update_system_config {
284            self.system_configuration.dyncfg_updates();
285        }
286
287        Ok((builtin_table_updates, catalog_updates))
288    }
289
290    #[instrument(level = "debug")]
291    fn apply_update(
292        &mut self,
293        kind: StateUpdateKind,
294        diff: StateDiff,
295        retractions: &mut InProgressRetractions,
296        local_expression_cache: &mut LocalExpressionCache,
297    ) -> Result<(), CatalogError> {
298        match kind {
299            StateUpdateKind::Role(role) => {
300                self.apply_role_update(role, diff, retractions);
301            }
302            StateUpdateKind::RoleAuth(role_auth) => {
303                self.apply_role_auth_update(role_auth, diff, retractions);
304            }
305            StateUpdateKind::Database(database) => {
306                self.apply_database_update(database, diff, retractions);
307            }
308            StateUpdateKind::Schema(schema) => {
309                self.apply_schema_update(schema, diff, retractions);
310            }
311            StateUpdateKind::DefaultPrivilege(default_privilege) => {
312                self.apply_default_privilege_update(default_privilege, diff, retractions);
313            }
314            StateUpdateKind::SystemPrivilege(system_privilege) => {
315                self.apply_system_privilege_update(system_privilege, diff, retractions);
316            }
317            StateUpdateKind::SystemConfiguration(system_configuration) => {
318                self.apply_system_configuration_update(system_configuration, diff, retractions);
319            }
320            StateUpdateKind::ClusterSystemConfiguration(cfg) => {
321                Self::apply_scoped_system_configuration_update(
322                    &mut self.scoped_system_parameters.cluster,
323                    cfg.cluster_id,
324                    cfg.name,
325                    cfg.value,
326                    diff,
327                );
328            }
329            StateUpdateKind::ReplicaSystemConfiguration(cfg) => {
330                Self::apply_scoped_system_configuration_update(
331                    &mut self.scoped_system_parameters.replica,
332                    cfg.replica_id,
333                    cfg.name,
334                    cfg.value,
335                    diff,
336                );
337            }
338            StateUpdateKind::Cluster(cluster) => {
339                self.apply_cluster_update(cluster, diff, retractions);
340            }
341            StateUpdateKind::NetworkPolicy(network_policy) => {
342                self.apply_network_policy_update(network_policy, diff, retractions);
343            }
344            StateUpdateKind::IntrospectionSourceIndex(introspection_source_index) => {
345                self.apply_introspection_source_index_update(
346                    introspection_source_index,
347                    diff,
348                    retractions,
349                );
350            }
351            StateUpdateKind::ClusterReplica(cluster_replica) => {
352                self.apply_cluster_replica_update(cluster_replica, diff, retractions);
353            }
354            StateUpdateKind::SystemObjectMapping(system_object_mapping) => {
355                self.apply_system_object_mapping_update(
356                    system_object_mapping,
357                    diff,
358                    retractions,
359                    local_expression_cache,
360                );
361            }
362            StateUpdateKind::TemporaryItem(item) => {
363                self.apply_temporary_item_update(item, diff, retractions, local_expression_cache);
364            }
365            StateUpdateKind::Item(item) => {
366                self.apply_item_update(item, diff, retractions, local_expression_cache)?;
367            }
368            StateUpdateKind::Comment(comment) => {
369                self.apply_comment_update(comment, diff, retractions);
370            }
371            StateUpdateKind::SourceReferences(source_reference) => {
372                self.apply_source_references_update(source_reference, diff, retractions);
373            }
374            StateUpdateKind::AuditLog(_audit_log) => {
375                // Audit logs are not stored in-memory.
376            }
377            StateUpdateKind::StorageCollectionMetadata(storage_collection_metadata) => {
378                self.apply_storage_collection_metadata_update(
379                    storage_collection_metadata,
380                    diff,
381                    retractions,
382                );
383            }
384            StateUpdateKind::UnfinalizedShard(unfinalized_shard) => {
385                self.apply_unfinalized_shard_update(unfinalized_shard, diff, retractions);
386            }
387        }
388
389        Ok(())
390    }
391
392    #[instrument(level = "debug")]
393    fn apply_role_auth_update(
394        &mut self,
395        role_auth: mz_catalog::durable::RoleAuth,
396        diff: StateDiff,
397        retractions: &mut InProgressRetractions,
398    ) {
399        apply_with_update(
400            &mut self.role_auth_by_id,
401            role_auth,
402            |role_auth| role_auth.role_id,
403            diff,
404            &mut retractions.role_auths,
405        );
406    }
407
408    #[instrument(level = "debug")]
409    fn apply_role_update(
410        &mut self,
411        role: mz_catalog::durable::Role,
412        diff: StateDiff,
413        retractions: &mut InProgressRetractions,
414    ) {
415        apply_inverted_lookup(&mut self.roles_by_name, &role.name, role.id, diff);
416        apply_with_update(
417            &mut self.roles_by_id,
418            role,
419            |role| role.id,
420            diff,
421            &mut retractions.roles,
422        );
423    }
424
425    #[instrument(level = "debug")]
426    fn apply_database_update(
427        &mut self,
428        database: mz_catalog::durable::Database,
429        diff: StateDiff,
430        retractions: &mut InProgressRetractions,
431    ) {
432        apply_inverted_lookup(
433            &mut self.database_by_name,
434            &database.name,
435            database.id,
436            diff,
437        );
438        apply_with_update(
439            &mut self.database_by_id,
440            database,
441            |database| database.id,
442            diff,
443            &mut retractions.databases,
444        );
445    }
446
447    #[instrument(level = "debug")]
448    fn apply_schema_update(
449        &mut self,
450        schema: mz_catalog::durable::Schema,
451        diff: StateDiff,
452        retractions: &mut InProgressRetractions,
453    ) {
454        match &schema.database_id {
455            Some(database_id) => {
456                let db = self
457                    .database_by_id
458                    .get_mut(database_id)
459                    .expect("catalog out of sync");
460                apply_inverted_lookup(&mut db.schemas_by_name, &schema.name, schema.id, diff);
461                apply_with_update(
462                    &mut db.schemas_by_id,
463                    schema,
464                    |schema| schema.id,
465                    diff,
466                    &mut retractions.schemas,
467                );
468            }
469            None => {
470                apply_inverted_lookup(
471                    &mut self.ambient_schemas_by_name,
472                    &schema.name,
473                    schema.id,
474                    diff,
475                );
476                apply_with_update(
477                    &mut self.ambient_schemas_by_id,
478                    schema,
479                    |schema| schema.id,
480                    diff,
481                    &mut retractions.schemas,
482                );
483            }
484        }
485    }
486
487    #[instrument(level = "debug")]
488    fn apply_default_privilege_update(
489        &mut self,
490        default_privilege: mz_catalog::durable::DefaultPrivilege,
491        diff: StateDiff,
492        _retractions: &mut InProgressRetractions,
493    ) {
494        match diff {
495            StateDiff::Addition => Arc::make_mut(&mut self.default_privileges)
496                .grant(default_privilege.object, default_privilege.acl_item),
497            StateDiff::Retraction => Arc::make_mut(&mut self.default_privileges)
498                .revoke(&default_privilege.object, &default_privilege.acl_item),
499        }
500    }
501
502    #[instrument(level = "debug")]
503    fn apply_system_privilege_update(
504        &mut self,
505        system_privilege: MzAclItem,
506        diff: StateDiff,
507        _retractions: &mut InProgressRetractions,
508    ) {
509        match diff {
510            StateDiff::Addition => {
511                Arc::make_mut(&mut self.system_privileges).grant(system_privilege)
512            }
513            StateDiff::Retraction => {
514                Arc::make_mut(&mut self.system_privileges).revoke(&system_privilege)
515            }
516        }
517    }
518
519    #[instrument(level = "debug")]
520    fn apply_system_configuration_update(
521        &mut self,
522        system_configuration: mz_catalog::durable::SystemConfiguration,
523        diff: StateDiff,
524        _retractions: &mut InProgressRetractions,
525    ) {
526        let res = match diff {
527            StateDiff::Addition => self.insert_system_configuration(
528                &system_configuration.name,
529                VarInput::Flat(&system_configuration.value),
530            ),
531            StateDiff::Retraction => self.remove_system_configuration(&system_configuration.name),
532        };
533        match res {
534            Ok(_) => (),
535            // When system variables are deleted, nothing deletes them from the underlying
536            // durable catalog, which isn't great. Still, we need to be able to ignore
537            // unknown variables.
538            Err(Error {
539                kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
540            }) => {
541                warn!(%name, "unknown system parameter from catalog storage");
542            }
543            Err(e) => panic!("unable to update system variable: {e:?}"),
544        }
545    }
546
547    /// Applies a durable update to one of the in-memory scoped-parameter working
548    /// copies (`scoped_system_parameters.{cluster,replica}`), keyed by object
549    /// id. Mirrors the durable `{cluster,replica}_system_configurations`
550    /// collections; the cluster copy is consumed at plan time via
551    /// [`CatalogState::cluster_scoped_optimizer_overrides`], the replica copy at
552    /// the compute controller's per-replica dyncfg push.
553    ///
554    /// Retraction is conditional on the value matching, so a value change
555    /// (retraction of the old value + addition of the new) is correct regardless
556    /// of the order the two updates are applied in. See the scoped feature flags
557    /// design.
558    ///
559    /// [`CatalogState::cluster_scoped_optimizer_overrides`]: crate::catalog::CatalogState::cluster_scoped_optimizer_overrides
560    fn apply_scoped_system_configuration_update<Id: Ord>(
561        map: &mut BTreeMap<Id, BTreeMap<String, String>>,
562        id: Id,
563        name: String,
564        value: String,
565        diff: StateDiff,
566    ) {
567        match diff {
568            StateDiff::Addition => {
569                map.entry(id).or_default().insert(name, value);
570            }
571            StateDiff::Retraction => {
572                if let Some(values) = map.get_mut(&id) {
573                    if values.get(&name) == Some(&value) {
574                        values.remove(&name);
575                        if values.is_empty() {
576                            map.remove(&id);
577                        }
578                    }
579                }
580            }
581        }
582    }
583
584    #[instrument(level = "debug")]
585    fn apply_cluster_update(
586        &mut self,
587        cluster: mz_catalog::durable::Cluster,
588        diff: StateDiff,
589        retractions: &mut InProgressRetractions,
590    ) {
591        // Soft signal for a managed cluster whose replica size isn't in
592        // the in-memory size map. The `mz_clusters` MV LEFT JOINs the size
593        // table and resolves `disk` to false when the size is missing, so
594        // this case no longer crashes a managed cluster with RF=0.
595        // A managed cluster with at least one running replica still panics
596        // via `concretize_replica_location` in `apply_cluster_replica_update`.
597        if matches!(diff, StateDiff::Addition) {
598            if let mz_catalog::durable::ClusterVariant::Managed(managed) = &cluster.config.variant {
599                if !self.cluster_replica_sizes.0.contains_key(&managed.size) {
600                    soft_panic_or_log!(
601                        "managed cluster {} ({}) references unknown replica size {:?}; \
602                         mz_clusters.disk will resolve to false",
603                        cluster.name,
604                        cluster.id,
605                        managed.size,
606                    );
607                }
608            }
609        }
610        apply_inverted_lookup(&mut self.clusters_by_name, &cluster.name, cluster.id, diff);
611        apply_with_update(
612            &mut self.clusters_by_id,
613            cluster,
614            |cluster| cluster.id,
615            diff,
616            &mut retractions.clusters,
617        );
618    }
619
620    #[instrument(level = "debug")]
621    fn apply_network_policy_update(
622        &mut self,
623        policy: mz_catalog::durable::NetworkPolicy,
624        diff: StateDiff,
625        retractions: &mut InProgressRetractions,
626    ) {
627        apply_inverted_lookup(
628            &mut self.network_policies_by_name,
629            &policy.name,
630            policy.id,
631            diff,
632        );
633        apply_with_update(
634            &mut self.network_policies_by_id,
635            policy,
636            |policy| policy.id,
637            diff,
638            &mut retractions.network_policies,
639        );
640    }
641
642    #[instrument(level = "debug")]
643    fn apply_introspection_source_index_update(
644        &mut self,
645        introspection_source_index: mz_catalog::durable::IntrospectionSourceIndex,
646        diff: StateDiff,
647        retractions: &mut InProgressRetractions,
648    ) {
649        let cluster = self
650            .clusters_by_id
651            .get_mut(&introspection_source_index.cluster_id)
652            .expect("catalog out of sync");
653        let log = BUILTIN_LOG_LOOKUP
654            .get(introspection_source_index.name.as_str())
655            .expect("missing log");
656        apply_inverted_lookup(
657            &mut cluster.log_indexes,
658            &log.variant,
659            introspection_source_index.index_id,
660            diff,
661        );
662
663        match diff {
664            StateDiff::Addition => {
665                if let Some(mut entry) = retractions
666                    .introspection_source_indexes
667                    .remove(&introspection_source_index.item_id)
668                {
669                    // This should only happen during startup as a result of builtin migrations. We
670                    // create a new index item and replace the old one with it.
671                    let (index_name, index) = self.create_introspection_source_index(
672                        introspection_source_index.cluster_id,
673                        log,
674                        introspection_source_index.index_id,
675                    );
676                    assert_eq!(entry.id, introspection_source_index.item_id);
677                    assert_eq!(entry.oid, introspection_source_index.oid);
678                    assert_eq!(entry.name, index_name);
679                    entry.item = index;
680                    self.insert_entry(entry);
681                } else {
682                    self.insert_introspection_source_index(
683                        introspection_source_index.cluster_id,
684                        log,
685                        introspection_source_index.item_id,
686                        introspection_source_index.index_id,
687                        introspection_source_index.oid,
688                    );
689                }
690            }
691            StateDiff::Retraction => {
692                let entry = self.drop_item(introspection_source_index.item_id);
693                retractions
694                    .introspection_source_indexes
695                    .insert(entry.id, entry);
696            }
697        }
698    }
699
700    #[instrument(level = "debug")]
701    fn apply_cluster_replica_update(
702        &mut self,
703        cluster_replica: mz_catalog::durable::ClusterReplica,
704        diff: StateDiff,
705        _retractions: &mut InProgressRetractions,
706    ) {
707        let cluster = self
708            .clusters_by_id
709            .get(&cluster_replica.cluster_id)
710            .expect("catalog out of sync");
711
712        // Mirror the cluster-side soft signal: if a managed replica's size
713        // is no longer in the in-memory size map, skip in-memory
714        // registration rather than panicking. The mz_cluster_replicas MV
715        // resolves `disk` from mz_cluster_replica_size_internal (which
716        // retains rows for disabled sizes), so SQL still returns sensible
717        // results. We tolerate disabled sizes here (allow_disabled=true)
718        // because an existing replica must remain queryable even if the
719        // operator has since disabled its size.
720        if let mz_catalog::durable::ReplicaLocation::Managed { size, .. } =
721            &cluster_replica.config.location
722        {
723            if !self.cluster_replica_sizes.0.contains_key(size) {
724                soft_panic_or_log!(
725                    "cluster replica {}.{} ({}) references unknown replica size {:?}; \
726                     skipping in-memory registration",
727                    cluster.name,
728                    cluster_replica.name,
729                    cluster_replica.replica_id,
730                    size,
731                );
732                return;
733            }
734        }
735
736        // Pass no availability-zone override: a rebuild from the durable record
737        // keeps the AZ list the replica was provisioned under, which may differ
738        // from the cluster's current pool while a graceful reconfiguration is in
739        // flight — the cluster controller tells realized- from target-shape
740        // replicas by exactly this list.
741        let location = self
742            .concretize_replica_location(cluster_replica.config.location, &vec![], None, true)
743            .expect("catalog in unexpected state");
744        let cluster = self
745            .clusters_by_id
746            .get_mut(&cluster_replica.cluster_id)
747            .expect("catalog out of sync");
748        apply_inverted_lookup(
749            &mut cluster.replica_id_by_name_,
750            &cluster_replica.name,
751            cluster_replica.replica_id,
752            diff,
753        );
754        match diff {
755            StateDiff::Retraction => {
756                let prev = cluster.replicas_by_id_.remove(&cluster_replica.replica_id);
757                assert!(
758                    prev.is_some(),
759                    "retraction does not match existing value: {:?}",
760                    cluster_replica.replica_id
761                );
762            }
763            StateDiff::Addition => {
764                let logging = ReplicaLogging {
765                    log_logging: cluster_replica.config.logging.log_logging,
766                    interval: cluster_replica.config.logging.interval,
767                };
768                let config = ReplicaConfig {
769                    location,
770                    compute: ComputeReplicaConfig {
771                        logging,
772                        arrangement_compression: cluster_replica.config.arrangement_compression,
773                    },
774                };
775                let mem_cluster_replica = ClusterReplica {
776                    name: cluster_replica.name.clone(),
777                    cluster_id: cluster_replica.cluster_id,
778                    replica_id: cluster_replica.replica_id,
779                    config,
780                    owner_id: cluster_replica.owner_id,
781                };
782                let prev = cluster
783                    .replicas_by_id_
784                    .insert(cluster_replica.replica_id, mem_cluster_replica);
785                assert_eq!(
786                    prev, None,
787                    "values must be explicitly retracted before inserting a new value: {:?}",
788                    cluster_replica.replica_id
789                );
790            }
791        }
792    }
793
794    #[instrument(level = "debug")]
795    fn apply_system_object_mapping_update(
796        &mut self,
797        system_object_mapping: mz_catalog::durable::SystemObjectMapping,
798        diff: StateDiff,
799        retractions: &mut InProgressRetractions,
800        local_expression_cache: &mut LocalExpressionCache,
801    ) {
802        let item_id = system_object_mapping.unique_identifier.catalog_id;
803        let global_id = system_object_mapping.unique_identifier.global_id;
804
805        if system_object_mapping.unique_identifier.runtime_alterable() {
806            // Runtime-alterable system objects have real entries in the items
807            // collection and so get handled through the normal `insert_item`
808            // and `drop_item` code paths.
809            return;
810        }
811
812        if let StateDiff::Retraction = diff {
813            let entry = self.drop_item(item_id);
814            retractions.system_object_mappings.insert(item_id, entry);
815            return;
816        }
817
818        if let Some(entry) = retractions.system_object_mappings.remove(&item_id) {
819            // This implies that we updated the fingerprint for some builtin item. The retraction
820            // was parsed, planned, and optimized using the compiled in definition, not the
821            // definition from a previous version. So we can just stick the old entry back into the
822            // catalog.
823            self.insert_entry(entry);
824            return;
825        }
826
827        let builtin = BUILTIN_LOOKUP
828            .get(&system_object_mapping.description)
829            .expect("missing builtin")
830            .1;
831        let schema_name = builtin.schema();
832        let schema_id = self
833            .ambient_schemas_by_name
834            .get(schema_name)
835            .unwrap_or_else(|| panic!("unknown ambient schema: {schema_name}"));
836        let name = QualifiedItemName {
837            qualifiers: ItemQualifiers {
838                database_spec: ResolvedDatabaseSpecifier::Ambient,
839                schema_spec: SchemaSpecifier::Id(*schema_id),
840            },
841            item: builtin.name().into(),
842        };
843        match builtin {
844            Builtin::Log(log) => {
845                let mut acl_items = vec![rbac::owner_privilege(
846                    mz_sql::catalog::ObjectType::Source,
847                    MZ_SYSTEM_ROLE_ID,
848                )];
849                acl_items.extend_from_slice(&log.access);
850                self.insert_item(
851                    item_id,
852                    log.oid,
853                    name.clone(),
854                    CatalogItem::Log(Log {
855                        variant: log.variant,
856                        global_id,
857                    }),
858                    MZ_SYSTEM_ROLE_ID,
859                    PrivilegeMap::from_mz_acl_items(acl_items),
860                );
861            }
862
863            Builtin::Table(table) => {
864                let mut acl_items = vec![rbac::owner_privilege(
865                    mz_sql::catalog::ObjectType::Table,
866                    MZ_SYSTEM_ROLE_ID,
867                )];
868                acl_items.extend_from_slice(&table.access);
869
870                self.insert_item(
871                    item_id,
872                    table.oid,
873                    name.clone(),
874                    CatalogItem::Table(Table {
875                        create_sql: None,
876                        desc: VersionedRelationDesc::new(table.desc.clone()),
877                        collections: [(RelationVersion::root(), global_id)].into_iter().collect(),
878                        conn_id: None,
879                        resolved_ids: ResolvedIds::empty(),
880                        custom_logical_compaction_window: table.is_retained_metrics_object.then(
881                            || {
882                                self.system_config()
883                                    .metrics_retention()
884                                    .try_into()
885                                    .expect("invalid metrics retention")
886                            },
887                        ),
888                        is_retained_metrics_object: table.is_retained_metrics_object,
889                        data_source: TableDataSource::TableWrites {
890                            defaults: vec![Expr::null(); table.desc.arity()],
891                        },
892                    }),
893                    MZ_SYSTEM_ROLE_ID,
894                    PrivilegeMap::from_mz_acl_items(acl_items),
895                );
896            }
897            Builtin::Index(index) => {
898                let custom_logical_compaction_window =
899                    index.is_retained_metrics_object.then(|| {
900                        self.system_config()
901                            .metrics_retention()
902                            .try_into()
903                            .expect("invalid metrics retention")
904                    });
905                // Indexes can't be versioned.
906                let versions = BTreeMap::new();
907
908                let item = self
909                    .parse_item(
910                        global_id,
911                        &index.create_sql(),
912                        &versions,
913                        None,
914                        index.is_retained_metrics_object,
915                        custom_logical_compaction_window,
916                        local_expression_cache,
917                        None,
918                    )
919                    .unwrap_or_else(|e| {
920                        panic!(
921                            "internal error: failed to load bootstrap index:\n\
922                                    {}\n\
923                                    error:\n\
924                                    {:?}\n\n\
925                                    make sure that the schema name is specified in the builtin index's create sql statement.",
926                            index.name, e
927                        )
928                    });
929                let CatalogItem::Index(_) = item else {
930                    panic!(
931                        "internal error: builtin index {}'s SQL does not begin with \"CREATE INDEX\".",
932                        index.name
933                    );
934                };
935
936                self.insert_item(
937                    item_id,
938                    index.oid,
939                    name,
940                    item,
941                    MZ_SYSTEM_ROLE_ID,
942                    PrivilegeMap::default(),
943                );
944            }
945            Builtin::View(_) => {
946                // parse_views is responsible for inserting all builtin views.
947                unreachable!("views added elsewhere");
948            }
949
950            // Note: Element types must be loaded before array types.
951            Builtin::Type(typ) => {
952                let typ = self.resolve_builtin_type_references(typ);
953                if let CatalogType::Array { element_reference } = typ.details.typ {
954                    let entry = self.get_entry_mut(&element_reference);
955                    let item_type = match &mut entry.item {
956                        CatalogItem::Type(item_type) => item_type,
957                        _ => unreachable!("types can only reference other types"),
958                    };
959                    item_type.details.array_id = Some(item_id);
960                }
961
962                let schema_id = self.resolve_system_schema(typ.schema);
963
964                self.insert_item(
965                    item_id,
966                    typ.oid,
967                    QualifiedItemName {
968                        qualifiers: ItemQualifiers {
969                            database_spec: ResolvedDatabaseSpecifier::Ambient,
970                            schema_spec: SchemaSpecifier::Id(schema_id),
971                        },
972                        item: typ.name.to_owned(),
973                    },
974                    CatalogItem::Type(Type {
975                        create_sql: None,
976                        global_id,
977                        details: typ.details.clone(),
978                        resolved_ids: ResolvedIds::empty(),
979                    }),
980                    MZ_SYSTEM_ROLE_ID,
981                    PrivilegeMap::from_mz_acl_items(vec![
982                        rbac::default_builtin_object_privilege(mz_sql::catalog::ObjectType::Type),
983                        rbac::owner_privilege(mz_sql::catalog::ObjectType::Type, MZ_SYSTEM_ROLE_ID),
984                    ]),
985                );
986            }
987
988            Builtin::Func(func) => {
989                // This OID is never used. `func` has a `Vec` of implementations and
990                // each implementation has its own OID. Those are the OIDs that are
991                // actually used by the system.
992                let oid = INVALID_OID;
993                self.insert_item(
994                    item_id,
995                    oid,
996                    name.clone(),
997                    CatalogItem::Func(Func {
998                        inner: func.inner,
999                        global_id,
1000                    }),
1001                    MZ_SYSTEM_ROLE_ID,
1002                    PrivilegeMap::default(),
1003                );
1004            }
1005
1006            Builtin::Source(coll) => {
1007                let mut acl_items = vec![rbac::owner_privilege(
1008                    mz_sql::catalog::ObjectType::Source,
1009                    MZ_SYSTEM_ROLE_ID,
1010                )];
1011                acl_items.extend_from_slice(&coll.access);
1012
1013                self.insert_item(
1014                    item_id,
1015                    coll.oid,
1016                    name.clone(),
1017                    CatalogItem::Source(Source {
1018                        create_sql: None,
1019                        data_source: coll.data_source.clone(),
1020                        desc: coll.desc.clone(),
1021                        global_id,
1022                        timeline: Timeline::EpochMilliseconds,
1023                        resolved_ids: ResolvedIds::empty(),
1024                        custom_logical_compaction_window: coll.is_retained_metrics_object.then(
1025                            || {
1026                                self.system_config()
1027                                    .metrics_retention()
1028                                    .try_into()
1029                                    .expect("invalid metrics retention")
1030                            },
1031                        ),
1032                        is_retained_metrics_object: coll.is_retained_metrics_object,
1033                    }),
1034                    MZ_SYSTEM_ROLE_ID,
1035                    PrivilegeMap::from_mz_acl_items(acl_items),
1036                );
1037            }
1038            Builtin::MaterializedView(mv) => {
1039                let mut acl_items = vec![rbac::owner_privilege(
1040                    mz_sql::catalog::ObjectType::MaterializedView,
1041                    MZ_SYSTEM_ROLE_ID,
1042                )];
1043                acl_items.extend_from_slice(&mv.access);
1044
1045                let custom_logical_compaction_window = mv.is_retained_metrics_object.then(|| {
1046                    self.system_config()
1047                        .metrics_retention()
1048                        .try_into()
1049                        .expect("invalid metrics retention")
1050                });
1051
1052                // Builtin materialized views can't be versioned.
1053                let versions = BTreeMap::new();
1054
1055                let mut item = self
1056                    .parse_item(
1057                        global_id,
1058                        &mv.create_sql(),
1059                        &versions,
1060                        None,
1061                        mv.is_retained_metrics_object,
1062                        custom_logical_compaction_window,
1063                        local_expression_cache,
1064                        None,
1065                    )
1066                    .unwrap_or_else(|e| {
1067                        panic!(
1068                            "internal error: failed to load bootstrap materialized view:\n\
1069                             {}\n\
1070                             error:\n\
1071                             {e:?}\n\n\
1072                             make sure that the schema name is specified in the builtin \
1073                             materialized view's create sql statement.",
1074                            mv.name,
1075                        )
1076                    });
1077                let CatalogItem::MaterializedView(catalog_mv) = &mut item else {
1078                    panic!(
1079                        "internal error: builtin materialized view {}'s SQL does not begin \
1080                         with \"CREATE MATERIALIZED VIEW\".",
1081                        mv.name,
1082                    );
1083                };
1084
1085                // The optimizer can only infer keys from MV definitions, but cannot infer
1086                // uniqueness present in the input data. Extend with the keys declared in the
1087                // builtin definition, to allow supplying additional key knowledge.
1088                let mut desc = catalog_mv.desc.latest();
1089                for key in &mv.desc.typ().keys {
1090                    desc = desc.with_key(key.clone());
1091                }
1092                catalog_mv.desc = VersionedRelationDesc::new(desc);
1093
1094                self.insert_item(
1095                    item_id,
1096                    mv.oid,
1097                    name,
1098                    item,
1099                    MZ_SYSTEM_ROLE_ID,
1100                    PrivilegeMap::from_mz_acl_items(acl_items),
1101                );
1102            }
1103            Builtin::Connection(connection) => {
1104                // Connections can't be versioned.
1105                let versions = BTreeMap::new();
1106                let mut item = self
1107                    .parse_item(
1108                        global_id,
1109                        connection.sql,
1110                        &versions,
1111                        None,
1112                        false,
1113                        None,
1114                        local_expression_cache,
1115                        None,
1116                    )
1117                    .unwrap_or_else(|e| {
1118                        panic!(
1119                            "internal error: failed to load bootstrap connection:\n\
1120                                    {}\n\
1121                                    error:\n\
1122                                    {:?}\n\n\
1123                                    make sure that the schema name is specified in the builtin connection's create sql statement.",
1124                            connection.name, e
1125                        )
1126                    });
1127                let CatalogItem::Connection(_) = &mut item else {
1128                    panic!(
1129                        "internal error: builtin connection {}'s SQL does not begin with \"CREATE CONNECTION\".",
1130                        connection.name
1131                    );
1132                };
1133
1134                let mut acl_items = vec![rbac::owner_privilege(
1135                    mz_sql::catalog::ObjectType::Connection,
1136                    connection.owner_id.clone(),
1137                )];
1138                acl_items.extend_from_slice(connection.access);
1139
1140                self.insert_item(
1141                    item_id,
1142                    connection.oid,
1143                    name.clone(),
1144                    item,
1145                    connection.owner_id.clone(),
1146                    PrivilegeMap::from_mz_acl_items(acl_items),
1147                );
1148            }
1149        }
1150    }
1151
1152    #[instrument(level = "debug")]
1153    fn apply_temporary_item_update(
1154        &mut self,
1155        temporary_item: TemporaryItem,
1156        diff: StateDiff,
1157        retractions: &mut InProgressRetractions,
1158        local_expression_cache: &mut LocalExpressionCache,
1159    ) {
1160        match diff {
1161            StateDiff::Addition => {
1162                let TemporaryItem {
1163                    id,
1164                    oid,
1165                    global_id,
1166                    schema_id,
1167                    name,
1168                    conn_id,
1169                    create_sql,
1170                    owner_id,
1171                    privileges,
1172                    extra_versions,
1173                } = temporary_item;
1174                // Lazily create the temporary schema if it doesn't exist yet.
1175                // We need the conn_id to create the schema, and it should always be Some for temp items.
1176                let temp_conn_id = conn_id
1177                    .as_ref()
1178                    .expect("temporary items must have a connection id");
1179                if !self.temporary_schemas.contains_key(temp_conn_id) {
1180                    self.create_temporary_schema(temp_conn_id, owner_id)
1181                        .expect("failed to create temporary schema");
1182                }
1183                let schema = self.find_temp_schema(&schema_id);
1184                let name = QualifiedItemName {
1185                    qualifiers: ItemQualifiers {
1186                        database_spec: schema.database().clone(),
1187                        schema_spec: schema.id().clone(),
1188                    },
1189                    item: name.clone(),
1190                };
1191
1192                let entry = match retractions.temp_items.remove(&id) {
1193                    Some(mut retraction) => {
1194                        assert_eq!(retraction.id, id);
1195
1196                        // We only reparse the SQL if it's changed. Otherwise, we use the existing
1197                        // item. This is a performance optimization and not needed for correctness.
1198                        // This makes it difficult to use the `UpdateFrom` trait, but the structure
1199                        // is still the same as the trait.
1200                        if retraction.create_sql() != create_sql {
1201                            let mut catalog_item = self
1202                                .deserialize_item(
1203                                    global_id,
1204                                    &create_sql,
1205                                    &extra_versions,
1206                                    local_expression_cache,
1207                                    Some(retraction.item),
1208                                )
1209                                .unwrap_or_else(|e| {
1210                                    panic!("{e:?}: invalid persisted SQL: {create_sql}")
1211                                });
1212                            // Have to patch up the item because parsing doesn't
1213                            // take into account temporary schemas/conn_id.
1214                            // NOTE(aljoscha): I don't like how we're patching
1215                            // this in here, but it's but one of the ways in
1216                            // which temporary items are a bit weird. So, here
1217                            // we are ...
1218                            catalog_item.set_conn_id(conn_id);
1219                            // Deserializing replans the SQL, and the planner's
1220                            // canonical printing can differ from `create_sql`,
1221                            // for example when a feature flag changed how
1222                            // references are printed since `create_sql` was
1223                            // produced. Keep the exact input. A later op in the
1224                            // same transaction retracts this item by
1225                            // re-serializing it, and that retraction must
1226                            // cancel byte-for-byte against this addition during
1227                            // consolidation, else two retractions of the same
1228                            // id survive and applying them panics.
1229                            catalog_item.set_create_sql(create_sql);
1230                            retraction.item = catalog_item;
1231                        }
1232
1233                        retraction.id = id;
1234                        retraction.oid = oid;
1235                        retraction.name = name;
1236                        retraction.owner_id = owner_id;
1237                        retraction.privileges = PrivilegeMap::from_mz_acl_items(privileges);
1238                        retraction
1239                    }
1240                    None => {
1241                        let mut catalog_item = self
1242                            .deserialize_item(
1243                                global_id,
1244                                &create_sql,
1245                                &extra_versions,
1246                                local_expression_cache,
1247                                None,
1248                            )
1249                            .unwrap_or_else(|e| {
1250                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1251                            });
1252
1253                        // Have to patch up the item because parsing doesn't
1254                        // take into account temporary schemas/conn_id.
1255                        // NOTE(aljoscha): I don't like how we're patching this
1256                        // in here, but it's but one of the ways in which
1257                        // temporary items are a bit weird. So, here we are ...
1258                        catalog_item.set_conn_id(conn_id);
1259                        // Keep the exact input create_sql, not the replanned
1260                        // printing. See the comment on the reparse above.
1261                        catalog_item.set_create_sql(create_sql);
1262
1263                        CatalogEntry {
1264                            item: catalog_item,
1265                            referenced_by: Vec::new(),
1266                            used_by: Vec::new(),
1267                            id,
1268                            oid,
1269                            name,
1270                            owner_id,
1271                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1272                        }
1273                    }
1274                };
1275                self.insert_entry(entry);
1276            }
1277            StateDiff::Retraction => {
1278                let entry = self.drop_item(temporary_item.id);
1279                retractions.temp_items.insert(temporary_item.id, entry);
1280            }
1281        }
1282    }
1283
1284    #[instrument(level = "debug")]
1285    fn apply_item_update(
1286        &mut self,
1287        item: mz_catalog::durable::Item,
1288        diff: StateDiff,
1289        retractions: &mut InProgressRetractions,
1290        local_expression_cache: &mut LocalExpressionCache,
1291    ) -> Result<(), CatalogError> {
1292        match diff {
1293            StateDiff::Addition => {
1294                let key = item.key();
1295                let mz_catalog::durable::Item {
1296                    id,
1297                    oid,
1298                    global_id,
1299                    schema_id,
1300                    name,
1301                    create_sql,
1302                    owner_id,
1303                    privileges,
1304                    extra_versions,
1305                } = item;
1306                let schema = self.find_non_temp_schema(&schema_id);
1307                let name = QualifiedItemName {
1308                    qualifiers: ItemQualifiers {
1309                        database_spec: schema.database().clone(),
1310                        schema_spec: schema.id().clone(),
1311                    },
1312                    item: name.clone(),
1313                };
1314                let entry = match retractions.items.remove(&key) {
1315                    Some(retraction) => {
1316                        assert_eq!(retraction.id, item.id);
1317
1318                        let item = self
1319                            .deserialize_item(
1320                                global_id,
1321                                &create_sql,
1322                                &extra_versions,
1323                                local_expression_cache,
1324                                Some(retraction.item),
1325                            )
1326                            .unwrap_or_else(|e| {
1327                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1328                            });
1329
1330                        CatalogEntry {
1331                            item,
1332                            id,
1333                            oid,
1334                            name,
1335                            owner_id,
1336                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1337                            referenced_by: retraction.referenced_by,
1338                            used_by: retraction.used_by,
1339                        }
1340                    }
1341                    None => {
1342                        let catalog_item = self
1343                            .deserialize_item(
1344                                global_id,
1345                                &create_sql,
1346                                &extra_versions,
1347                                local_expression_cache,
1348                                None,
1349                            )
1350                            .unwrap_or_else(|e| {
1351                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1352                            });
1353                        CatalogEntry {
1354                            item: catalog_item,
1355                            referenced_by: Vec::new(),
1356                            used_by: Vec::new(),
1357                            id,
1358                            oid,
1359                            name,
1360                            owner_id,
1361                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1362                        }
1363                    }
1364                };
1365
1366                self.insert_entry(entry);
1367            }
1368            StateDiff::Retraction => {
1369                let entry = self.drop_item(item.id);
1370                let key = item.into_key_value().0;
1371                retractions.items.insert(key, entry);
1372            }
1373        }
1374        Ok(())
1375    }
1376
1377    #[instrument(level = "debug")]
1378    fn apply_comment_update(
1379        &mut self,
1380        comment: mz_catalog::durable::Comment,
1381        diff: StateDiff,
1382        _retractions: &mut InProgressRetractions,
1383    ) {
1384        match diff {
1385            StateDiff::Addition => {
1386                let prev = Arc::make_mut(&mut self.comments).update_comment(
1387                    comment.object_id,
1388                    comment.sub_component,
1389                    Some(comment.comment),
1390                );
1391                assert_eq!(
1392                    prev, None,
1393                    "values must be explicitly retracted before inserting a new value"
1394                );
1395            }
1396            StateDiff::Retraction => {
1397                let prev = Arc::make_mut(&mut self.comments).update_comment(
1398                    comment.object_id,
1399                    comment.sub_component,
1400                    None,
1401                );
1402                assert_eq!(
1403                    prev,
1404                    Some(comment.comment),
1405                    "retraction does not match existing value: ({:?}, {:?})",
1406                    comment.object_id,
1407                    comment.sub_component,
1408                );
1409            }
1410        }
1411    }
1412
1413    #[instrument(level = "debug")]
1414    fn apply_source_references_update(
1415        &mut self,
1416        source_references: mz_catalog::durable::SourceReferences,
1417        diff: StateDiff,
1418        _retractions: &mut InProgressRetractions,
1419    ) {
1420        match diff {
1421            StateDiff::Addition => {
1422                let prev = self
1423                    .source_references
1424                    .insert(source_references.source_id, source_references.into());
1425                assert!(
1426                    prev.is_none(),
1427                    "values must be explicitly retracted before inserting a new value: {prev:?}"
1428                );
1429            }
1430            StateDiff::Retraction => {
1431                let prev = self.source_references.remove(&source_references.source_id);
1432                assert!(
1433                    prev.is_some(),
1434                    "retraction for a non-existent existing value: {source_references:?}"
1435                );
1436            }
1437        }
1438    }
1439
1440    #[instrument(level = "debug")]
1441    fn apply_storage_collection_metadata_update(
1442        &mut self,
1443        storage_collection_metadata: mz_catalog::durable::StorageCollectionMetadata,
1444        diff: StateDiff,
1445        _retractions: &mut InProgressRetractions,
1446    ) {
1447        apply_inverted_lookup(
1448            &mut Arc::make_mut(&mut self.storage_metadata).collection_metadata,
1449            &storage_collection_metadata.id,
1450            storage_collection_metadata.shard,
1451            diff,
1452        );
1453    }
1454
1455    #[instrument(level = "debug")]
1456    fn apply_unfinalized_shard_update(
1457        &mut self,
1458        unfinalized_shard: mz_catalog::durable::UnfinalizedShard,
1459        diff: StateDiff,
1460        _retractions: &mut InProgressRetractions,
1461    ) {
1462        match diff {
1463            StateDiff::Addition => {
1464                let newly_inserted = Arc::make_mut(&mut self.storage_metadata)
1465                    .unfinalized_shards
1466                    .insert(unfinalized_shard.shard);
1467                assert!(
1468                    newly_inserted,
1469                    "values must be explicitly retracted before inserting a new value: {unfinalized_shard:?}",
1470                );
1471            }
1472            StateDiff::Retraction => {
1473                let removed = Arc::make_mut(&mut self.storage_metadata)
1474                    .unfinalized_shards
1475                    .remove(&unfinalized_shard.shard);
1476                assert!(
1477                    removed,
1478                    "retraction does not match existing value: {unfinalized_shard:?}"
1479                );
1480            }
1481        }
1482    }
1483
1484    /// Generate a list of `BuiltinTableUpdate`s that correspond to a single update made to the
1485    /// durable catalog.
1486    #[instrument(level = "debug")]
1487    pub(crate) fn generate_builtin_table_update(
1488        &self,
1489        kind: StateUpdateKind,
1490        diff: StateDiff,
1491    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
1492        let diff = diff.into();
1493        match kind {
1494            // mz_roles and mz_role_parameters are MaterializedViews backed by
1495            // mz_internal.mz_catalog_raw, so role rows do not produce builtin
1496            // table updates here.
1497            StateUpdateKind::Role(_) => Vec::new(),
1498            StateUpdateKind::RoleAuth(role_auth) => {
1499                vec![self.pack_role_auth_update(role_auth.role_id, diff)]
1500            }
1501            // mz_default_privileges and mz_system_privileges are MaterializedViews
1502            // backed by mz_internal.mz_catalog_raw, so privilege rows do not
1503            // produce builtin table updates here.
1504            StateUpdateKind::DefaultPrivilege(_) => Vec::new(),
1505            StateUpdateKind::SystemPrivilege(_) => Vec::new(),
1506            StateUpdateKind::SystemConfiguration(_) => Vec::new(),
1507            // mz_internal.mz_{cluster,replica}_system_parameters are
1508            // MaterializedViews backed by mz_internal.mz_catalog_raw, so the
1509            // durable scoped-configuration rows do not produce builtin table
1510            // updates here. (The in-memory working copy used for resolution is
1511            // maintained separately in `apply_*_system_configuration_update`.)
1512            StateUpdateKind::ClusterSystemConfiguration(_) => Vec::new(),
1513            StateUpdateKind::ReplicaSystemConfiguration(_) => Vec::new(),
1514            // mz_clusters and mz_cluster_schedules are MaterializedViews backed
1515            // by mz_internal.mz_catalog_raw, so cluster rows do not produce
1516            // builtin table updates here.
1517            StateUpdateKind::Cluster(_) => Vec::new(),
1518            StateUpdateKind::IntrospectionSourceIndex(introspection_source_index) => {
1519                self.pack_item_update(introspection_source_index.item_id, diff)
1520            }
1521            // mz_cluster_replicas is a MaterializedView backed by
1522            // mz_internal.mz_catalog_raw.
1523            StateUpdateKind::ClusterReplica(_) => Vec::new(),
1524            StateUpdateKind::SystemObjectMapping(system_object_mapping) => {
1525                // Runtime-alterable system objects have real entries in the
1526                // items collection and so get handled through the normal
1527                // `StateUpdateKind::Item`.`
1528                if !system_object_mapping.unique_identifier.runtime_alterable() {
1529                    self.pack_item_update(system_object_mapping.unique_identifier.catalog_id, diff)
1530                } else {
1531                    vec![]
1532                }
1533            }
1534            StateUpdateKind::TemporaryItem(item) => self.pack_item_update(item.id, diff),
1535            StateUpdateKind::Item(item) => self.pack_item_update(item.id, diff),
1536            StateUpdateKind::Comment(_) => Vec::new(),
1537            StateUpdateKind::SourceReferences(source_references) => {
1538                self.pack_source_references_update(&source_references, diff)
1539            }
1540            // mz_audit_events is a MaterializedView backed by
1541            // mz_internal.mz_catalog_raw, so audit log rows do not produce
1542            // builtin table updates here.
1543            StateUpdateKind::AuditLog(_) => Vec::new(),
1544            StateUpdateKind::Database(_)
1545            | StateUpdateKind::Schema(_)
1546            | StateUpdateKind::NetworkPolicy(_)
1547            | StateUpdateKind::StorageCollectionMetadata(_)
1548            | StateUpdateKind::UnfinalizedShard(_) => Vec::new(),
1549        }
1550    }
1551
1552    fn get_entry_mut(&mut self, id: &CatalogItemId) -> &mut CatalogEntry {
1553        self.entry_by_id
1554            .get_mut(id)
1555            .unwrap_or_else(|| panic!("catalog out of sync, missing id {id}"))
1556    }
1557
1558    /// Set the optimized plan for the item identified by `id`.
1559    ///
1560    /// # Panics
1561    /// If the item is not an `Index` or `MaterializedView`.
1562    pub(super) fn set_optimized_plan(
1563        &mut self,
1564        id: GlobalId,
1565        plan: DataflowDescription<mz_expr::OptimizedMirRelationExpr>,
1566    ) {
1567        let item_id = self.entry_by_global_id[&id];
1568        let entry = self.get_entry_mut(&item_id);
1569        match entry.item_mut() {
1570            CatalogItem::Index(idx) => idx.optimized_plan = Some(Arc::new(plan)),
1571            CatalogItem::MaterializedView(mv) => mv.optimized_plan = Some(Arc::new(plan)),
1572            other => panic!("set_optimized_plan called on {} ({:?})", id, other.typ()),
1573        }
1574    }
1575
1576    /// Set the physical plan for the item identified by `id`.
1577    ///
1578    /// # Panics
1579    /// If the item is not an `Index` or `MaterializedView`.
1580    pub(super) fn set_physical_plan(
1581        &mut self,
1582        id: GlobalId,
1583        plan: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
1584    ) {
1585        let item_id = self.entry_by_global_id[&id];
1586        let entry = self.get_entry_mut(&item_id);
1587        match entry.item_mut() {
1588            CatalogItem::Index(idx) => idx.physical_plan = Some(Arc::new(plan)),
1589            CatalogItem::MaterializedView(mv) => mv.physical_plan = Some(Arc::new(plan)),
1590            other => panic!("set_physical_plan called on {} ({:?})", id, other.typ()),
1591        }
1592    }
1593
1594    /// Set the `DataflowMetainfo` for the item identified by `id`.
1595    ///
1596    /// # Panics
1597    /// If the item is not an `Index` or `MaterializedView`.
1598    pub(super) fn set_dataflow_metainfo(
1599        &mut self,
1600        id: GlobalId,
1601        metainfo: DataflowMetainfo<Arc<OptimizerNotice>>,
1602    ) {
1603        // Add entries to the `notices_by_dep_id` lookup map.
1604        for notice in metainfo.optimizer_notices.iter() {
1605            for dep_id in notice.dependencies.iter() {
1606                self.notices_by_dep_id
1607                    .entry(*dep_id)
1608                    .or_default()
1609                    .push(Arc::clone(notice));
1610            }
1611            if let Some(item_id) = notice.item_id {
1612                soft_assert_eq_or_log!(
1613                    item_id,
1614                    id,
1615                    "notice.item_id should match the id for whom we are saving the notice"
1616                );
1617            }
1618        }
1619        // Set the metainfo on the catalog object.
1620        let item_id = self.entry_by_global_id[&id];
1621        let entry = self.get_entry_mut(&item_id);
1622        match entry.item_mut() {
1623            CatalogItem::Index(idx) => idx.dataflow_metainfo = Some(metainfo),
1624            CatalogItem::MaterializedView(mv) => mv.dataflow_metainfo = Some(metainfo),
1625            other => panic!("set_dataflow_metainfo called on {} ({:?})", id, other.typ()),
1626        }
1627    }
1628
1629    /// Clean up optimizer notices for the given dropped catalog entries.
1630    ///
1631    /// This extracts notices directly from the owned entries (which
1632    /// have already been removed from the catalog maps), cleans up
1633    /// the `notices_by_dep_id` reverse index, and removes notices
1634    /// from other (still-live) catalog objects that depended on the
1635    /// dropped items.
1636    ///
1637    /// Returns the set of all dropped notices for builtin table
1638    /// retraction.
1639    #[mz_ore::instrument(level = "trace")]
1640    pub(super) fn drop_optimizer_notices(
1641        &mut self,
1642        dropped_entries: Vec<CatalogEntry>,
1643    ) -> BTreeSet<Arc<OptimizerNotice>> {
1644        let mut dropped_notices = BTreeSet::new();
1645        let mut drop_ids = BTreeSet::new();
1646
1647        // Extract notices directly from the owned dropped entries.
1648        for mut entry in dropped_entries {
1649            drop_ids.extend(entry.global_ids());
1650            let metainfo = match entry.item_mut() {
1651                CatalogItem::Index(idx) => idx.dataflow_metainfo.take(),
1652                CatalogItem::MaterializedView(mv) => mv.dataflow_metainfo.take(),
1653                _ => None,
1654            };
1655            if let Some(mut metainfo) = metainfo {
1656                soft_assert_or_log!(
1657                    metainfo.optimizer_notices.iter().all_unique(),
1658                    "should have been pushed there by \
1659                     `push_optimizer_notice_dedup`"
1660                );
1661                for n in metainfo.optimizer_notices.drain(..) {
1662                    // Clean up notices_by_dep_id for this notice's
1663                    // dependencies.
1664                    for dep_id in n.dependencies.iter() {
1665                        if let Some(notices) = self.notices_by_dep_id.get_mut(dep_id) {
1666                            notices.retain(|x| &n != x);
1667                            if notices.is_empty() {
1668                                self.notices_by_dep_id.remove(dep_id);
1669                            }
1670                        }
1671                    }
1672                    dropped_notices.insert(n);
1673                }
1674            }
1675        }
1676
1677        // Remove notices_by_dep_id entries keyed by the dropped IDs.
1678        // These are notices on OTHER items that depend on a dropped
1679        // item. We need to remove them from those items' metainfo.
1680        for id in &drop_ids {
1681            if let Some(notices) = self.notices_by_dep_id.remove(id) {
1682                for n in notices.into_iter() {
1683                    // Remove the notice from the catalog object it
1684                    // lives on (if that object still exists — it
1685                    // may have been dropped too, in which case the
1686                    // notice was already collected above).
1687                    if let Some(item_id) = n.item_id.as_ref() {
1688                        if let Some(entry) = self.try_get_entry_by_global_id(item_id) {
1689                            let catalog_item_id = entry.id();
1690                            let entry = self.get_entry_mut(&catalog_item_id);
1691                            let item = entry.item_mut();
1692                            match item {
1693                                CatalogItem::Index(idx) => {
1694                                    if let Some(ref mut m) = idx.dataflow_metainfo {
1695                                        m.optimizer_notices.retain(|x| &n != x);
1696                                    }
1697                                }
1698                                CatalogItem::MaterializedView(mv) => {
1699                                    if let Some(ref mut m) = mv.dataflow_metainfo {
1700                                        m.optimizer_notices.retain(|x| &n != x);
1701                                    }
1702                                }
1703                                _ => {}
1704                            }
1705                        }
1706                    }
1707                    dropped_notices.insert(n);
1708                }
1709            }
1710        }
1711
1712        // Clean up notices_by_dep_id entries for dependency IDs
1713        // that are NOT being dropped but had dropped notices.
1714        let todo_dep_ids: BTreeSet<GlobalId> = dropped_notices
1715            .iter()
1716            .flat_map(|n| n.dependencies.iter())
1717            .filter(|dep_id| !drop_ids.contains(dep_id))
1718            .copied()
1719            .collect();
1720        for id in todo_dep_ids {
1721            if let Some(notices) = self.notices_by_dep_id.get_mut(&id) {
1722                notices.retain(|n| !dropped_notices.contains(n));
1723                if notices.is_empty() {
1724                    self.notices_by_dep_id.remove(&id);
1725                }
1726            }
1727        }
1728
1729        dropped_notices
1730    }
1731
1732    fn get_schema_mut(
1733        &mut self,
1734        database_spec: &ResolvedDatabaseSpecifier,
1735        schema_spec: &SchemaSpecifier,
1736        conn_id: &ConnectionId,
1737    ) -> &mut Schema {
1738        // Keep in sync with `get_schemas`
1739        match (database_spec, schema_spec) {
1740            (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Temporary) => self
1741                .temporary_schemas
1742                .get_mut(conn_id)
1743                .expect("catalog out of sync"),
1744            (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Id(id)) => self
1745                .ambient_schemas_by_id
1746                .get_mut(id)
1747                .expect("catalog out of sync"),
1748            (ResolvedDatabaseSpecifier::Id(database_id), SchemaSpecifier::Id(schema_id)) => self
1749                .database_by_id
1750                .get_mut(database_id)
1751                .expect("catalog out of sync")
1752                .schemas_by_id
1753                .get_mut(schema_id)
1754                .expect("catalog out of sync"),
1755            (ResolvedDatabaseSpecifier::Id(_), SchemaSpecifier::Temporary) => {
1756                unreachable!("temporary schemas are in the ambient database")
1757            }
1758        }
1759    }
1760
1761    /// Install builtin views to the catalog. This is its own function so that views can be
1762    /// optimized in parallel.
1763    ///
1764    /// The implementation is similar to `apply_updates_for_bootstrap` and determines dependency
1765    /// problems by sniffing out specific errors and then retrying once those dependencies are
1766    /// complete. This doesn't work for everything (casts, function implementations) so we also need
1767    /// to have a bucket for everything at the end. Additionally, because this executes in parellel,
1768    /// we must maintain a completed set otherwise races could result in orphaned views languishing
1769    /// in awaiting with nothing retriggering the attempt.
1770    #[instrument(name = "catalog::parse_views")]
1771    async fn parse_builtin_views(
1772        state: &mut CatalogState,
1773        builtin_views: Vec<(&'static BuiltinView, CatalogItemId, GlobalId)>,
1774        retractions: &mut InProgressRetractions,
1775        local_expression_cache: &mut LocalExpressionCache,
1776    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
1777        let mut builtin_table_updates = Vec::with_capacity(builtin_views.len());
1778        let (updates, additions): (Vec<_>, Vec<_>) =
1779            builtin_views
1780                .into_iter()
1781                .partition_map(|(view, item_id, gid)| {
1782                    match retractions.system_object_mappings.remove(&item_id) {
1783                        Some(entry) => Either::Left(entry),
1784                        None => Either::Right((view, item_id, gid)),
1785                    }
1786                });
1787
1788        for entry in updates {
1789            // This implies that we updated the fingerprint for some builtin view. The retraction
1790            // was parsed, planned, and optimized using the compiled in definition, not the
1791            // definition from a previous version. So we can just stick the old entry back into the
1792            // catalog.
1793            let item_id = entry.id();
1794            state.insert_entry(entry);
1795            builtin_table_updates.extend(state.pack_item_update(item_id, Diff::ONE));
1796        }
1797
1798        let mut handles = Vec::new();
1799        let mut awaiting_id_dependencies: BTreeMap<CatalogItemId, Vec<CatalogItemId>> =
1800            BTreeMap::new();
1801        let mut awaiting_name_dependencies: BTreeMap<String, Vec<CatalogItemId>> = BTreeMap::new();
1802        // Some errors are due to the implementation of casts or SQL functions that depend on some
1803        // view. Instead of figuring out the exact view dependency, delay these until the end.
1804        let mut awaiting_all = Vec::new();
1805        // Completed views, needed to avoid race conditions.
1806        let mut completed_ids: BTreeSet<CatalogItemId> = BTreeSet::new();
1807        let mut completed_names: BTreeSet<String> = BTreeSet::new();
1808
1809        // Avoid some reference lifetime issues by not passing `builtin` into the spawned task.
1810        let mut views: BTreeMap<CatalogItemId, (&BuiltinView, GlobalId)> = additions
1811            .into_iter()
1812            .map(|(view, item_id, gid)| (item_id, (view, gid)))
1813            .collect();
1814        let item_ids: Vec<_> = views.keys().copied().collect();
1815
1816        let mut ready: VecDeque<CatalogItemId> = views.keys().cloned().collect();
1817        while !handles.is_empty() || !ready.is_empty() || !awaiting_all.is_empty() {
1818            if handles.is_empty() && ready.is_empty() {
1819                // Enqueue the views that were waiting for all the others.
1820                ready.extend(awaiting_all.drain(..));
1821            }
1822
1823            // Spawn tasks for all ready views.
1824            if !ready.is_empty() {
1825                let spawn_state = Arc::new(state.clone());
1826                while let Some(id) = ready.pop_front() {
1827                    let (view, global_id) = views.get(&id).expect("must exist");
1828                    let global_id = *global_id;
1829                    let create_sql = view.create_sql();
1830                    // Views can't be versioned.
1831                    let versions = BTreeMap::new();
1832
1833                    let span = info_span!(parent: None, "parse builtin view", name = view.name);
1834                    OpenTelemetryContext::obtain().attach_as_parent_to(&span);
1835                    let task_state = Arc::clone(&spawn_state);
1836                    let cached_expr = local_expression_cache.remove_cached_expression(&global_id);
1837                    let handle = mz_ore::task::spawn_blocking(
1838                        || "parse view",
1839                        move || {
1840                            span.in_scope(|| {
1841                                let res = task_state.parse_item_inner(
1842                                    global_id,
1843                                    &create_sql,
1844                                    &versions,
1845                                    None,
1846                                    false,
1847                                    None,
1848                                    cached_expr,
1849                                    None,
1850                                );
1851                                (id, global_id, res)
1852                            })
1853                        },
1854                    );
1855                    handles.push(handle);
1856                }
1857            }
1858
1859            // Wait for a view to be ready.
1860            let (selected, _idx, remaining) = future::select_all(handles).await;
1861            handles = remaining;
1862            let (id, global_id, res) = selected;
1863            let mut insert_cached_expr = |cached_expr| {
1864                if let Some(cached_expr) = cached_expr {
1865                    local_expression_cache.insert_cached_expression(global_id, cached_expr);
1866                }
1867            };
1868            match res {
1869                Ok((item, uncached_expr)) => {
1870                    if let Some((uncached_expr, optimizer_features)) = uncached_expr {
1871                        local_expression_cache.insert_uncached_expression(
1872                            global_id,
1873                            uncached_expr,
1874                            optimizer_features,
1875                        );
1876                    }
1877                    // Add item to catalog.
1878                    let (view, _gid) = views.remove(&id).expect("must exist");
1879                    let schema_id = state
1880                        .ambient_schemas_by_name
1881                        .get(view.schema)
1882                        .unwrap_or_else(|| panic!("unknown ambient schema: {}", view.schema));
1883                    let qname = QualifiedItemName {
1884                        qualifiers: ItemQualifiers {
1885                            database_spec: ResolvedDatabaseSpecifier::Ambient,
1886                            schema_spec: SchemaSpecifier::Id(*schema_id),
1887                        },
1888                        item: view.name.into(),
1889                    };
1890                    let mut acl_items = vec![rbac::owner_privilege(
1891                        mz_sql::catalog::ObjectType::View,
1892                        MZ_SYSTEM_ROLE_ID,
1893                    )];
1894                    acl_items.extend_from_slice(&view.access);
1895
1896                    state.insert_item(
1897                        id,
1898                        view.oid,
1899                        qname,
1900                        item,
1901                        MZ_SYSTEM_ROLE_ID,
1902                        PrivilegeMap::from_mz_acl_items(acl_items),
1903                    );
1904
1905                    // Enqueue any items waiting on this dependency.
1906                    let mut resolved_dependent_items = Vec::new();
1907                    if let Some(dependent_items) = awaiting_id_dependencies.remove(&id) {
1908                        resolved_dependent_items.extend(dependent_items);
1909                    }
1910                    let entry = state.get_entry(&id);
1911                    let full_name = state.resolve_full_name(entry.name(), None).to_string();
1912                    if let Some(dependent_items) = awaiting_name_dependencies.remove(&full_name) {
1913                        resolved_dependent_items.extend(dependent_items);
1914                    }
1915                    ready.extend(resolved_dependent_items);
1916
1917                    completed_ids.insert(id);
1918                    completed_names.insert(full_name);
1919                }
1920                // If we were missing a dependency, wait for it to be added.
1921                Err((
1922                    AdapterError::PlanError(plan::PlanError::InvalidId(missing_dep)),
1923                    cached_expr,
1924                )) => {
1925                    insert_cached_expr(cached_expr);
1926                    if completed_ids.contains(&missing_dep) {
1927                        ready.push_back(id);
1928                    } else {
1929                        awaiting_id_dependencies
1930                            .entry(missing_dep)
1931                            .or_default()
1932                            .push(id);
1933                    }
1934                }
1935                // If we were missing a dependency, wait for it to be added.
1936                Err((
1937                    AdapterError::PlanError(plan::PlanError::Catalog(
1938                        SqlCatalogError::UnknownItem(missing_dep),
1939                    )),
1940                    cached_expr,
1941                )) => {
1942                    insert_cached_expr(cached_expr);
1943                    match CatalogItemId::from_str(&missing_dep) {
1944                        Ok(missing_dep) => {
1945                            if completed_ids.contains(&missing_dep) {
1946                                ready.push_back(id);
1947                            } else {
1948                                awaiting_id_dependencies
1949                                    .entry(missing_dep)
1950                                    .or_default()
1951                                    .push(id);
1952                            }
1953                        }
1954                        Err(_) => {
1955                            if completed_names.contains(&missing_dep) {
1956                                ready.push_back(id);
1957                            } else {
1958                                awaiting_name_dependencies
1959                                    .entry(missing_dep)
1960                                    .or_default()
1961                                    .push(id);
1962                            }
1963                        }
1964                    }
1965                }
1966                Err((
1967                    AdapterError::PlanError(plan::PlanError::InvalidCast { .. }),
1968                    cached_expr,
1969                )) => {
1970                    insert_cached_expr(cached_expr);
1971                    awaiting_all.push(id);
1972                }
1973                Err((e, _)) => {
1974                    let (bad_view, _gid) = views.get(&id).expect("must exist");
1975                    panic!(
1976                        "internal error: failed to load bootstrap view:\n\
1977                            {name}\n\
1978                            error:\n\
1979                            {e:?}\n\n\
1980                            Make sure that the schema name is specified in the builtin view's create sql statement.
1981                            ",
1982                        name = bad_view.name,
1983                    )
1984                }
1985            }
1986        }
1987
1988        assert!(awaiting_id_dependencies.is_empty());
1989        assert!(
1990            awaiting_name_dependencies.is_empty(),
1991            "awaiting_name_dependencies: {awaiting_name_dependencies:?}"
1992        );
1993        assert!(awaiting_all.is_empty());
1994        assert!(views.is_empty());
1995
1996        // Generate a builtin table update for all the new views.
1997        builtin_table_updates.extend(
1998            item_ids
1999                .into_iter()
2000                .flat_map(|id| state.pack_item_update(id, Diff::ONE)),
2001        );
2002
2003        builtin_table_updates
2004    }
2005
2006    /// Associates a name, `CatalogItemId`, and entry.
2007    fn insert_entry(&mut self, entry: CatalogEntry) {
2008        if !entry.id.is_system() {
2009            if let Some(cluster_id) = entry.item.cluster_id() {
2010                self.clusters_by_id
2011                    .get_mut(&cluster_id)
2012                    .expect("catalog out of sync")
2013                    .bound_objects
2014                    .insert(entry.id);
2015            };
2016        }
2017
2018        for u in entry.references().items() {
2019            match self.entry_by_id.get_mut(u) {
2020                Some(metadata) => metadata.referenced_by.push(entry.id()),
2021                None => panic!(
2022                    "Catalog: missing dependent catalog item {} while installing {}",
2023                    &u,
2024                    self.resolve_full_name(entry.name(), entry.conn_id())
2025                ),
2026            }
2027        }
2028        for u in entry.uses() {
2029            // Ignore self for self-referential tasks (e.g. Continual Tasks), if
2030            // present.
2031            if u == entry.id() {
2032                continue;
2033            }
2034            match self.entry_by_id.get_mut(&u) {
2035                Some(metadata) => metadata.used_by.push(entry.id()),
2036                None => panic!(
2037                    "Catalog: missing dependent catalog item {} while installing {}",
2038                    &u,
2039                    self.resolve_full_name(entry.name(), entry.conn_id())
2040                ),
2041            }
2042        }
2043        for gid in entry.item.global_ids() {
2044            self.entry_by_global_id.insert(gid, entry.id());
2045        }
2046        let conn_id = entry.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
2047        // Lazily create the temporary schema if this is a temporary item and the schema
2048        // doesn't exist yet.
2049        if entry.name().qualifiers.schema_spec == SchemaSpecifier::Temporary
2050            && !self.temporary_schemas.contains_key(conn_id)
2051        {
2052            self.create_temporary_schema(conn_id, entry.owner_id)
2053                .expect("failed to create temporary schema");
2054        }
2055        let schema = self.get_schema_mut(
2056            &entry.name().qualifiers.database_spec,
2057            &entry.name().qualifiers.schema_spec,
2058            conn_id,
2059        );
2060
2061        let prev_id = match entry.item() {
2062            CatalogItem::Func(_) => schema
2063                .functions
2064                .insert(entry.name().item.clone(), entry.id()),
2065            CatalogItem::Type(_) => schema.types.insert(entry.name().item.clone(), entry.id()),
2066            _ => schema.items.insert(entry.name().item.clone(), entry.id()),
2067        };
2068
2069        assert!(
2070            prev_id.is_none(),
2071            "builtin name collision on {:?}",
2072            entry.name().item.clone()
2073        );
2074
2075        self.entry_by_id.insert(entry.id(), entry.clone());
2076    }
2077
2078    /// Associates a name, [`CatalogItemId`], and entry.
2079    fn insert_item(
2080        &mut self,
2081        id: CatalogItemId,
2082        oid: u32,
2083        name: QualifiedItemName,
2084        item: CatalogItem,
2085        owner_id: RoleId,
2086        privileges: PrivilegeMap,
2087    ) {
2088        let entry = CatalogEntry {
2089            item,
2090            name,
2091            id,
2092            oid,
2093            used_by: Vec::new(),
2094            referenced_by: Vec::new(),
2095            owner_id,
2096            privileges,
2097        };
2098
2099        self.insert_entry(entry);
2100    }
2101
2102    #[mz_ore::instrument(level = "trace")]
2103    fn drop_item(&mut self, id: CatalogItemId) -> CatalogEntry {
2104        let metadata = self.entry_by_id.remove(&id).expect("catalog out of sync");
2105        for u in metadata.references().items() {
2106            if let Some(dep_metadata) = self.entry_by_id.get_mut(u) {
2107                dep_metadata.referenced_by.retain(|u| *u != metadata.id())
2108            }
2109        }
2110        for u in metadata.uses() {
2111            if let Some(dep_metadata) = self.entry_by_id.get_mut(&u) {
2112                dep_metadata.used_by.retain(|u| *u != metadata.id())
2113            }
2114        }
2115        for gid in metadata.global_ids() {
2116            self.entry_by_global_id.remove(&gid);
2117        }
2118
2119        let conn_id = metadata.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
2120        let schema = self.get_schema_mut(
2121            &metadata.name().qualifiers.database_spec,
2122            &metadata.name().qualifiers.schema_spec,
2123            conn_id,
2124        );
2125        if metadata.item_type() == CatalogItemType::Type {
2126            schema
2127                .types
2128                .remove(&metadata.name().item)
2129                .expect("catalog out of sync");
2130        } else {
2131            // Functions would need special handling, but we don't yet support
2132            // dropping functions.
2133            assert_ne!(metadata.item_type(), CatalogItemType::Func);
2134
2135            schema
2136                .items
2137                .remove(&metadata.name().item)
2138                .expect("catalog out of sync");
2139        };
2140
2141        if !id.is_system() {
2142            if let Some(cluster_id) = metadata.item().cluster_id() {
2143                assert!(
2144                    self.clusters_by_id
2145                        .get_mut(&cluster_id)
2146                        .expect("catalog out of sync")
2147                        .bound_objects
2148                        .remove(&id),
2149                    "catalog out of sync"
2150                );
2151            }
2152        }
2153
2154        metadata
2155    }
2156
2157    fn insert_introspection_source_index(
2158        &mut self,
2159        cluster_id: ClusterId,
2160        log: &'static BuiltinLog,
2161        item_id: CatalogItemId,
2162        global_id: GlobalId,
2163        oid: u32,
2164    ) {
2165        let (index_name, index) =
2166            self.create_introspection_source_index(cluster_id, log, global_id);
2167        self.insert_item(
2168            item_id,
2169            oid,
2170            index_name,
2171            index,
2172            MZ_SYSTEM_ROLE_ID,
2173            PrivilegeMap::default(),
2174        );
2175    }
2176
2177    fn create_introspection_source_index(
2178        &self,
2179        cluster_id: ClusterId,
2180        log: &'static BuiltinLog,
2181        global_id: GlobalId,
2182    ) -> (QualifiedItemName, CatalogItem) {
2183        let source_name = FullItemName {
2184            database: RawDatabaseSpecifier::Ambient,
2185            schema: log.schema.into(),
2186            item: log.name.into(),
2187        };
2188        let index_name = format!("{}_{}_primary_idx", log.name, cluster_id);
2189        let mut index_name = QualifiedItemName {
2190            qualifiers: ItemQualifiers {
2191                database_spec: ResolvedDatabaseSpecifier::Ambient,
2192                schema_spec: SchemaSpecifier::Id(self.get_mz_introspection_schema_id()),
2193            },
2194            item: index_name.clone(),
2195        };
2196        index_name = self.find_available_name(index_name, &SYSTEM_CONN_ID);
2197        let index_item_name = index_name.item.clone();
2198        let (log_item_id, log_global_id) = self.resolve_builtin_log(log);
2199        let index = CatalogItem::Index(Index {
2200            global_id,
2201            on: log_global_id,
2202            keys: log
2203                .variant
2204                .index_by()
2205                .into_iter()
2206                .map(MirScalarExpr::column)
2207                .collect(),
2208            create_sql: index_sql(
2209                index_item_name,
2210                cluster_id,
2211                source_name,
2212                &log.variant.desc(),
2213                &log.variant.index_by(),
2214            ),
2215            conn_id: None,
2216            resolved_ids: [(log_item_id, log_global_id)].into_iter().collect(),
2217            cluster_id,
2218            is_retained_metrics_object: false,
2219            custom_logical_compaction_window: None,
2220            optimized_plan: None,
2221            physical_plan: None,
2222            dataflow_metainfo: None,
2223        });
2224        (index_name, index)
2225    }
2226
2227    /// Insert system configuration `name` with `value`.
2228    ///
2229    /// Return a `bool` value indicating whether the configuration was modified
2230    /// by the call.
2231    fn insert_system_configuration(&mut self, name: &str, value: VarInput) -> Result<bool, Error> {
2232        Ok(Arc::make_mut(&mut self.system_configuration).set(name, value)?)
2233    }
2234
2235    /// Reset system configuration `name`.
2236    ///
2237    /// Return a `bool` value indicating whether the configuration was modified
2238    /// by the call.
2239    fn remove_system_configuration(&mut self, name: &str) -> Result<bool, Error> {
2240        Ok(Arc::make_mut(&mut self.system_configuration).reset(name)?)
2241    }
2242}
2243
2244/// Sort [`StateUpdate`]s in dependency order.
2245///
2246/// # Panics
2247///
2248/// This function assumes that all provided `updates` have the same timestamp and will panic
2249/// otherwise. It also requires that the provided `updates` are consolidated, i.e. all contained
2250/// `StateUpdateKinds` are unique.
2251fn sort_updates(updates: Vec<StateUpdate>) -> Vec<StateUpdate> {
2252    fn push_update<T>(
2253        update: T,
2254        diff: StateDiff,
2255        retractions: &mut Vec<T>,
2256        additions: &mut Vec<T>,
2257    ) {
2258        match diff {
2259            StateDiff::Retraction => retractions.push(update),
2260            StateDiff::Addition => additions.push(update),
2261        }
2262    }
2263
2264    soft_assert_no_log!(
2265        updates.iter().map(|update| update.ts).all_equal(),
2266        "all timestamps should be equal: {updates:?}"
2267    );
2268    soft_assert_no_log!(
2269        {
2270            let mut dedup = BTreeSet::new();
2271            updates.iter().all(|update| dedup.insert(&update.kind))
2272        },
2273        "updates should be consolidated: {updates:?}"
2274    );
2275
2276    // Partition updates by type so that we can weave different update types into the right spots.
2277    let mut pre_cluster_retractions = Vec::new();
2278    let mut pre_cluster_additions = Vec::new();
2279    let mut cluster_retractions = Vec::new();
2280    let mut cluster_additions = Vec::new();
2281    let mut builtin_item_updates = Vec::new();
2282    let mut item_retractions = Vec::new();
2283    let mut item_additions = Vec::new();
2284    let mut temp_item_retractions = Vec::new();
2285    let mut temp_item_additions = Vec::new();
2286    let mut post_item_retractions = Vec::new();
2287    let mut post_item_additions = Vec::new();
2288    for update in updates {
2289        let diff = update.diff.clone();
2290        match update.kind {
2291            StateUpdateKind::Role(_)
2292            | StateUpdateKind::RoleAuth(_)
2293            | StateUpdateKind::Database(_)
2294            | StateUpdateKind::Schema(_)
2295            | StateUpdateKind::DefaultPrivilege(_)
2296            | StateUpdateKind::SystemPrivilege(_)
2297            | StateUpdateKind::SystemConfiguration(_)
2298            | StateUpdateKind::NetworkPolicy(_) => push_update(
2299                update,
2300                diff,
2301                &mut pre_cluster_retractions,
2302                &mut pre_cluster_additions,
2303            ),
2304            StateUpdateKind::Cluster(_)
2305            | StateUpdateKind::ClusterSystemConfiguration(_)
2306            | StateUpdateKind::IntrospectionSourceIndex(_)
2307            | StateUpdateKind::ClusterReplica(_)
2308            | StateUpdateKind::ReplicaSystemConfiguration(_) => push_update(
2309                update,
2310                diff,
2311                &mut cluster_retractions,
2312                &mut cluster_additions,
2313            ),
2314            StateUpdateKind::SystemObjectMapping(system_object_mapping) => {
2315                builtin_item_updates.push((system_object_mapping, update.ts, update.diff))
2316            }
2317            StateUpdateKind::TemporaryItem(item) => push_update(
2318                (item, update.ts, update.diff),
2319                diff,
2320                &mut temp_item_retractions,
2321                &mut temp_item_additions,
2322            ),
2323            StateUpdateKind::Item(item) => push_update(
2324                (item, update.ts, update.diff),
2325                diff,
2326                &mut item_retractions,
2327                &mut item_additions,
2328            ),
2329            StateUpdateKind::Comment(_)
2330            | StateUpdateKind::SourceReferences(_)
2331            | StateUpdateKind::AuditLog(_)
2332            | StateUpdateKind::StorageCollectionMetadata(_)
2333            | StateUpdateKind::UnfinalizedShard(_) => push_update(
2334                update,
2335                diff,
2336                &mut post_item_retractions,
2337                &mut post_item_additions,
2338            ),
2339        }
2340    }
2341
2342    // Sort builtin item updates by dependency. The builtin definition order must be a dependency
2343    // order, so we can use that to avoid a more expensive topo sorting.
2344    let builtin_item_updates = builtin_item_updates
2345        .into_iter()
2346        .map(|(system_object_mapping, ts, diff)| {
2347            let idx = BUILTIN_LOOKUP
2348                .get(&system_object_mapping.description)
2349                .expect("missing builtin")
2350                .0;
2351            (idx, system_object_mapping, ts, diff)
2352        })
2353        .sorted_by_key(|(idx, _, _, _)| *idx)
2354        .map(|(_, system_object_mapping, ts, diff)| (system_object_mapping, ts, diff));
2355
2356    // Partition builtins based on whether or not they should be applied before or after clusters.
2357    // Clusters depend on sources (introspection logs), so sources need to be applied first.
2358    // Everything else can be applied after clusters.
2359    let mut builtin_source_retractions = Vec::new();
2360    let mut builtin_source_additions = Vec::new();
2361    let mut other_builtin_retractions = Vec::new();
2362    let mut other_builtin_additions = Vec::new();
2363    for (builtin_item_update, ts, diff) in builtin_item_updates {
2364        let object_type = builtin_item_update.description.object_type;
2365        let update = StateUpdate {
2366            kind: StateUpdateKind::SystemObjectMapping(builtin_item_update),
2367            ts,
2368            diff,
2369        };
2370        if object_type == CatalogItemType::Source {
2371            push_update(
2372                update,
2373                diff,
2374                &mut builtin_source_retractions,
2375                &mut builtin_source_additions,
2376            );
2377        } else {
2378            push_update(
2379                update,
2380                diff,
2381                &mut other_builtin_retractions,
2382                &mut other_builtin_additions,
2383            );
2384        }
2385    }
2386
2387    /// Sort items by their dependencies using topological sort.
2388    ///
2389    /// # Panics
2390    ///
2391    /// This function requires that all provided items have unique item IDs.
2392    fn sort_items_topological(items: &mut Vec<(mz_catalog::durable::Item, Timestamp, StateDiff)>) {
2393        tracing::debug!(?items, "sorting items by dependencies");
2394
2395        let key_fn = |item: &(mz_catalog::durable::Item, _, _)| item.0.id;
2396        let dependencies_fn = |item: &(mz_catalog::durable::Item, _, _)| {
2397            let statement = mz_sql::parse::parse(&item.0.create_sql)
2398                .expect("valid create_sql")
2399                .into_element()
2400                .ast;
2401            mz_sql::names::dependencies(&statement).expect("failed to find dependencies of item")
2402        };
2403        sort_topological(items, key_fn, dependencies_fn);
2404    }
2405
2406    /// Sort item updates by dependency.
2407    ///
2408    /// First we group items into groups that are totally ordered by dependency. For example, when
2409    /// sorting all items by dependency we know that all tables can come after all sources, because
2410    /// a source can never depend on a table. Second, we sort the items in each group in
2411    /// topological order, or by ID, depending on the type.
2412    ///
2413    /// It used to be the case that the ID order of ALL items matched the dependency order. However,
2414    /// certain migrations shuffled item IDs around s.t. this was no longer true. A much better
2415    /// approach would be to investigate each item, discover their exact dependencies, and then
2416    /// perform a topological sort. This is non-trivial because we only have the CREATE SQL of each
2417    /// item here. Within the SQL the dependent items are sometimes referred to by ID and sometimes
2418    /// referred to by name.
2419    ///
2420    /// The logic of this function should match [`sort_temp_item_updates`].
2421    fn sort_item_updates(
2422        item_updates: Vec<(mz_catalog::durable::Item, Timestamp, StateDiff)>,
2423    ) -> VecDeque<(mz_catalog::durable::Item, Timestamp, StateDiff)> {
2424        // Partition items into groups s.t. each item in one group has a predefined order with all
2425        // items in other groups. For example, all sinks are ordered greater than all tables.
2426        let mut types = Vec::new();
2427        // N.B. Functions can depend on system tables, but not user tables.
2428        // TODO(udf): This will change when UDFs are supported.
2429        let mut funcs = Vec::new();
2430        let mut secrets = Vec::new();
2431        let mut connections = Vec::new();
2432        let mut sources = Vec::new();
2433        let mut tables = Vec::new();
2434        let mut derived_items = Vec::new();
2435        let mut sinks = Vec::new();
2436        for update in item_updates {
2437            match update.0.item_type() {
2438                CatalogItemType::Type => types.push(update),
2439                CatalogItemType::Func => funcs.push(update),
2440                CatalogItemType::Secret => secrets.push(update),
2441                CatalogItemType::Connection => connections.push(update),
2442                CatalogItemType::Source => sources.push(update),
2443                CatalogItemType::Table => tables.push(update),
2444                CatalogItemType::View
2445                | CatalogItemType::MaterializedView
2446                | CatalogItemType::Index => derived_items.push(update),
2447                CatalogItemType::Sink => sinks.push(update),
2448            }
2449        }
2450
2451        // For some groups, the items in them can depend on each other and can be `ALTER`ed so that
2452        // an item ends up depending on an item with a greater ID. Thus we need to perform
2453        // topological sort for these groups.
2454        sort_items_topological(&mut connections);
2455        sort_items_topological(&mut derived_items);
2456
2457        // Other groups we can simply sort by ID.
2458        for group in [
2459            &mut types,
2460            &mut funcs,
2461            &mut secrets,
2462            &mut sources,
2463            &mut tables,
2464            &mut sinks,
2465        ] {
2466            group.sort_by_key(|(item, _, _)| item.id);
2467        }
2468
2469        iter::empty()
2470            .chain(types)
2471            .chain(funcs)
2472            .chain(secrets)
2473            .chain(connections)
2474            .chain(sources)
2475            .chain(tables)
2476            .chain(derived_items)
2477            .chain(sinks)
2478            .collect()
2479    }
2480
2481    let item_retractions = sort_item_updates(item_retractions);
2482    let item_additions = sort_item_updates(item_additions);
2483
2484    /// Sort temporary item updates by dependency.
2485    ///
2486    /// The logic of this function should match [`sort_item_updates`].
2487    fn sort_temp_item_updates(
2488        temp_item_updates: Vec<(TemporaryItem, Timestamp, StateDiff)>,
2489    ) -> VecDeque<(TemporaryItem, Timestamp, StateDiff)> {
2490        // Partition items into groups s.t. each item in one group has a predefined order with all
2491        // items in other groups. For example, all sinks are ordered greater than all tables.
2492        let mut types = Vec::new();
2493        // N.B. Functions can depend on system tables, but not user tables.
2494        let mut funcs = Vec::new();
2495        let mut secrets = Vec::new();
2496        let mut connections = Vec::new();
2497        let mut sources = Vec::new();
2498        let mut tables = Vec::new();
2499        let mut derived_items = Vec::new();
2500        let mut sinks = Vec::new();
2501        for update in temp_item_updates {
2502            match update.0.item_type() {
2503                CatalogItemType::Type => types.push(update),
2504                CatalogItemType::Func => funcs.push(update),
2505                CatalogItemType::Secret => secrets.push(update),
2506                CatalogItemType::Connection => connections.push(update),
2507                CatalogItemType::Source => sources.push(update),
2508                CatalogItemType::Table => tables.push(update),
2509                CatalogItemType::View
2510                | CatalogItemType::MaterializedView
2511                | CatalogItemType::Index => derived_items.push(update),
2512                CatalogItemType::Sink => sinks.push(update),
2513            }
2514        }
2515
2516        // Within each group, sort by ID.
2517        for group in [
2518            &mut types,
2519            &mut funcs,
2520            &mut secrets,
2521            &mut connections,
2522            &mut sources,
2523            &mut tables,
2524            &mut derived_items,
2525            &mut sinks,
2526        ] {
2527            group.sort_by_key(|(item, _, _)| item.id);
2528        }
2529
2530        iter::empty()
2531            .chain(types)
2532            .chain(funcs)
2533            .chain(secrets)
2534            .chain(connections)
2535            .chain(sources)
2536            .chain(tables)
2537            .chain(derived_items)
2538            .chain(sinks)
2539            .collect()
2540    }
2541    let temp_item_retractions = sort_temp_item_updates(temp_item_retractions);
2542    let temp_item_additions = sort_temp_item_updates(temp_item_additions);
2543
2544    /// Concatenate sorted persistent and temporary item updates, persistent
2545    /// first.
2546    ///
2547    /// Both inputs are already in dependency (type-group) order internally. A
2548    /// non-temporary item can never depend on a temporary one (enforced at
2549    /// creation, see `ErrorKind::InvalidTemporaryDependency`), so emitting every
2550    /// persistent item before every temporary item keeps dependencies ahead of
2551    /// dependents for additions. (Retractions reuse this via a reversal by the
2552    /// caller, which puts temporary items first. Also, a retraction only drops
2553    /// the item and does not re-resolve `create_sql`.)
2554    ///
2555    /// NOTE: Do not interleave the two by raw id. The inputs are ordered by
2556    /// dependency group, which is not id order, so an id merge can place a
2557    /// temporary dependent ahead of the persistent item it references and panic
2558    /// apply with an unresolvable id.
2559    fn merge_item_updates(
2560        item_updates: VecDeque<(mz_catalog::durable::Item, Timestamp, StateDiff)>,
2561        temp_item_updates: VecDeque<(TemporaryItem, Timestamp, StateDiff)>,
2562    ) -> Vec<StateUpdate> {
2563        let mut state_updates = Vec::with_capacity(item_updates.len() + temp_item_updates.len());
2564        for (item, ts, diff) in item_updates {
2565            state_updates.push(StateUpdate {
2566                kind: StateUpdateKind::Item(item),
2567                ts,
2568                diff,
2569            });
2570        }
2571        for (temp_item, ts, diff) in temp_item_updates {
2572            state_updates.push(StateUpdate {
2573                kind: StateUpdateKind::TemporaryItem(temp_item),
2574                ts,
2575                diff,
2576            });
2577        }
2578        state_updates
2579    }
2580    let item_retractions = merge_item_updates(item_retractions, temp_item_retractions);
2581    let item_additions = merge_item_updates(item_additions, temp_item_additions);
2582
2583    // Put everything back together.
2584    iter::empty()
2585        // All retractions must be reversed.
2586        .chain(post_item_retractions.into_iter().rev())
2587        .chain(item_retractions.into_iter().rev())
2588        .chain(other_builtin_retractions.into_iter().rev())
2589        .chain(cluster_retractions.into_iter().rev())
2590        .chain(builtin_source_retractions.into_iter().rev())
2591        .chain(pre_cluster_retractions.into_iter().rev())
2592        .chain(pre_cluster_additions)
2593        .chain(builtin_source_additions)
2594        .chain(cluster_additions)
2595        .chain(other_builtin_additions)
2596        .chain(item_additions)
2597        .chain(post_item_additions)
2598        .collect()
2599}
2600
2601/// Groups of updates of certain types are applied in batches to improve
2602/// performance. A constraint is that updates must be applied in order. This
2603/// process is modeled as a state machine that batches then applies groups of
2604/// updates.
2605enum ApplyState {
2606    /// Additions of builtin views.
2607    BuiltinViewAdditions(Vec<(&'static BuiltinView, CatalogItemId, GlobalId)>),
2608    /// Item updates that aren't builtin view additions.
2609    ///
2610    /// This contains all updates whose application requires calling
2611    /// `parse_item` and thus toggling the `enable_for_item_parsing` feature
2612    /// flags.
2613    Items(Vec<StateUpdate>),
2614    /// All other updates.
2615    Updates(Vec<StateUpdate>),
2616}
2617
2618impl ApplyState {
2619    fn new(update: StateUpdate) -> Self {
2620        use StateUpdateKind::*;
2621        match &update.kind {
2622            SystemObjectMapping(som)
2623                if som.description.object_type == CatalogItemType::View
2624                    && update.diff == StateDiff::Addition =>
2625            {
2626                let view_addition = lookup_builtin_view_addition(som.clone());
2627                Self::BuiltinViewAdditions(vec![view_addition])
2628            }
2629
2630            IntrospectionSourceIndex(_) | SystemObjectMapping(_) | TemporaryItem(_) | Item(_) => {
2631                Self::Items(vec![update])
2632            }
2633
2634            Role(_)
2635            | RoleAuth(_)
2636            | Database(_)
2637            | Schema(_)
2638            | DefaultPrivilege(_)
2639            | SystemPrivilege(_)
2640            | SystemConfiguration(_)
2641            | ClusterSystemConfiguration(_)
2642            | ReplicaSystemConfiguration(_)
2643            | Cluster(_)
2644            | NetworkPolicy(_)
2645            | ClusterReplica(_)
2646            | SourceReferences(_)
2647            | Comment(_)
2648            | AuditLog(_)
2649            | StorageCollectionMetadata(_)
2650            | UnfinalizedShard(_) => Self::Updates(vec![update]),
2651        }
2652    }
2653
2654    /// Apply all updates that have been batched in `self`.
2655    ///
2656    /// We make sure to enable all "enable_for_item_parsing" feature flags when applying item
2657    /// updates during bootstrap. See [`CatalogState::with_enable_for_item_parsing`] for more
2658    /// details.
2659    async fn apply(
2660        self,
2661        state: &mut CatalogState,
2662        retractions: &mut InProgressRetractions,
2663        local_expression_cache: &mut LocalExpressionCache,
2664    ) -> (
2665        Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
2666        Vec<ParsedStateUpdate>,
2667    ) {
2668        match self {
2669            Self::BuiltinViewAdditions(builtin_view_additions) => {
2670                let restore = Arc::clone(&state.system_configuration);
2671                Arc::make_mut(&mut state.system_configuration).enable_for_item_parsing();
2672                let builtin_table_updates = CatalogState::parse_builtin_views(
2673                    state,
2674                    builtin_view_additions,
2675                    retractions,
2676                    local_expression_cache,
2677                )
2678                .await;
2679                state.system_configuration = restore;
2680                (builtin_table_updates, Vec::new())
2681            }
2682            Self::Items(updates) => state.with_enable_for_item_parsing(|state| {
2683                state
2684                    .apply_updates_inner(updates, retractions, local_expression_cache)
2685                    .expect("corrupt catalog")
2686            }),
2687            Self::Updates(updates) => state
2688                .apply_updates_inner(updates, retractions, local_expression_cache)
2689                .expect("corrupt catalog"),
2690        }
2691    }
2692
2693    async fn step(
2694        self,
2695        next: Self,
2696        state: &mut CatalogState,
2697        retractions: &mut InProgressRetractions,
2698        local_expression_cache: &mut LocalExpressionCache,
2699    ) -> (
2700        Self,
2701        (
2702            Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
2703            Vec<ParsedStateUpdate>,
2704        ),
2705    ) {
2706        match (self, next) {
2707            (
2708                Self::BuiltinViewAdditions(mut builtin_view_additions),
2709                Self::BuiltinViewAdditions(next_builtin_view_additions),
2710            ) => {
2711                // Continue batching builtin view additions.
2712                builtin_view_additions.extend(next_builtin_view_additions);
2713                (
2714                    Self::BuiltinViewAdditions(builtin_view_additions),
2715                    (Vec::new(), Vec::new()),
2716                )
2717            }
2718            (Self::Items(mut updates), Self::Items(next_updates)) => {
2719                // Continue batching item updates.
2720                updates.extend(next_updates);
2721                (Self::Items(updates), (Vec::new(), Vec::new()))
2722            }
2723            (Self::Updates(mut updates), Self::Updates(next_updates)) => {
2724                // Continue batching updates.
2725                updates.extend(next_updates);
2726                (Self::Updates(updates), (Vec::new(), Vec::new()))
2727            }
2728            (apply_state, next_apply_state) => {
2729                // Apply the current batch and start batching new apply state.
2730                let updates = apply_state
2731                    .apply(state, retractions, local_expression_cache)
2732                    .await;
2733                (next_apply_state, updates)
2734            }
2735        }
2736    }
2737}
2738
2739/// Trait abstracting over map operations needed by [`apply_inverted_lookup`] and
2740/// [`apply_with_update`]. Both [`BTreeMap`] and [`imbl::OrdMap`] implement this.
2741trait MutableMap<K, V> {
2742    fn insert(&mut self, key: K, value: V) -> Option<V>;
2743    fn remove(&mut self, key: &K) -> Option<V>;
2744}
2745
2746impl<K: Ord, V> MutableMap<K, V> for BTreeMap<K, V> {
2747    fn insert(&mut self, key: K, value: V) -> Option<V> {
2748        BTreeMap::insert(self, key, value)
2749    }
2750    fn remove(&mut self, key: &K) -> Option<V> {
2751        BTreeMap::remove(self, key)
2752    }
2753}
2754
2755impl<K: Ord + Clone, V: Clone> MutableMap<K, V> for imbl::OrdMap<K, V> {
2756    fn insert(&mut self, key: K, value: V) -> Option<V> {
2757        imbl::OrdMap::insert(self, key, value)
2758    }
2759    fn remove(&mut self, key: &K) -> Option<V> {
2760        imbl::OrdMap::remove(self, key)
2761    }
2762}
2763
2764/// Helper method to updated inverted lookup maps. The keys are generally names and the values are
2765/// generally IDs.
2766///
2767/// Importantly, when retracting it's expected that the existing value will match `value` exactly.
2768fn apply_inverted_lookup<K, V>(map: &mut impl MutableMap<K, V>, key: &K, value: V, diff: StateDiff)
2769where
2770    K: Ord + Clone + Debug,
2771    V: PartialEq + Debug,
2772{
2773    match diff {
2774        StateDiff::Retraction => {
2775            let prev = map.remove(key);
2776            assert_eq!(
2777                prev,
2778                Some(value),
2779                "retraction does not match existing value: {key:?}"
2780            );
2781        }
2782        StateDiff::Addition => {
2783            let prev = map.insert(key.clone(), value);
2784            assert_eq!(
2785                prev, None,
2786                "values must be explicitly retracted before inserting a new value: {key:?}"
2787            );
2788        }
2789    }
2790}
2791
2792/// Helper method to update catalog state, that may need to be updated from a previously retracted
2793/// object.
2794fn apply_with_update<K, V, D>(
2795    map: &mut impl MutableMap<K, V>,
2796    durable: D,
2797    key_fn: impl FnOnce(&D) -> K,
2798    diff: StateDiff,
2799    retractions: &mut BTreeMap<D::Key, V>,
2800) where
2801    K: Ord,
2802    V: UpdateFrom<D> + PartialEq + Debug,
2803    D: DurableType,
2804    D::Key: Ord,
2805{
2806    match diff {
2807        StateDiff::Retraction => {
2808            let mem_key = key_fn(&durable);
2809            let value = map
2810                .remove(&mem_key)
2811                .expect("retraction does not match existing value: {key:?}");
2812            let durable_key = durable.into_key_value().0;
2813            retractions.insert(durable_key, value);
2814        }
2815        StateDiff::Addition => {
2816            let mem_key = key_fn(&durable);
2817            let durable_key = durable.key();
2818            let value = match retractions.remove(&durable_key) {
2819                Some(mut retraction) => {
2820                    retraction.update_from(durable);
2821                    retraction
2822                }
2823                None => durable.into(),
2824            };
2825            let prev = map.insert(mem_key, value);
2826            assert_eq!(
2827                prev, None,
2828                "values must be explicitly retracted before inserting a new value"
2829            );
2830        }
2831    }
2832}
2833
2834/// Looks up a [`BuiltinView`] from a [`SystemObjectMapping`].
2835fn lookup_builtin_view_addition(
2836    mapping: SystemObjectMapping,
2837) -> (&'static BuiltinView, CatalogItemId, GlobalId) {
2838    let (_, builtin) = BUILTIN_LOOKUP
2839        .get(&mapping.description)
2840        .expect("missing builtin view");
2841    let Builtin::View(view) = builtin else {
2842        unreachable!("programming error, expected BuiltinView found {builtin:?}");
2843    };
2844
2845    (
2846        view,
2847        mapping.unique_identifier.catalog_id,
2848        mapping.unique_identifier.global_id,
2849    )
2850}