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 { logging },
771                };
772                let mem_cluster_replica = ClusterReplica {
773                    name: cluster_replica.name.clone(),
774                    cluster_id: cluster_replica.cluster_id,
775                    replica_id: cluster_replica.replica_id,
776                    config,
777                    owner_id: cluster_replica.owner_id,
778                };
779                let prev = cluster
780                    .replicas_by_id_
781                    .insert(cluster_replica.replica_id, mem_cluster_replica);
782                assert_eq!(
783                    prev, None,
784                    "values must be explicitly retracted before inserting a new value: {:?}",
785                    cluster_replica.replica_id
786                );
787            }
788        }
789    }
790
791    #[instrument(level = "debug")]
792    fn apply_system_object_mapping_update(
793        &mut self,
794        system_object_mapping: mz_catalog::durable::SystemObjectMapping,
795        diff: StateDiff,
796        retractions: &mut InProgressRetractions,
797        local_expression_cache: &mut LocalExpressionCache,
798    ) {
799        let item_id = system_object_mapping.unique_identifier.catalog_id;
800        let global_id = system_object_mapping.unique_identifier.global_id;
801
802        if system_object_mapping.unique_identifier.runtime_alterable() {
803            // Runtime-alterable system objects have real entries in the items
804            // collection and so get handled through the normal `insert_item`
805            // and `drop_item` code paths.
806            return;
807        }
808
809        if let StateDiff::Retraction = diff {
810            let entry = self.drop_item(item_id);
811            retractions.system_object_mappings.insert(item_id, entry);
812            return;
813        }
814
815        if let Some(entry) = retractions.system_object_mappings.remove(&item_id) {
816            // This implies that we updated the fingerprint for some builtin item. The retraction
817            // was parsed, planned, and optimized using the compiled in definition, not the
818            // definition from a previous version. So we can just stick the old entry back into the
819            // catalog.
820            self.insert_entry(entry);
821            return;
822        }
823
824        let builtin = BUILTIN_LOOKUP
825            .get(&system_object_mapping.description)
826            .expect("missing builtin")
827            .1;
828        let schema_name = builtin.schema();
829        let schema_id = self
830            .ambient_schemas_by_name
831            .get(schema_name)
832            .unwrap_or_else(|| panic!("unknown ambient schema: {schema_name}"));
833        let name = QualifiedItemName {
834            qualifiers: ItemQualifiers {
835                database_spec: ResolvedDatabaseSpecifier::Ambient,
836                schema_spec: SchemaSpecifier::Id(*schema_id),
837            },
838            item: builtin.name().into(),
839        };
840        match builtin {
841            Builtin::Log(log) => {
842                let mut acl_items = vec![rbac::owner_privilege(
843                    mz_sql::catalog::ObjectType::Source,
844                    MZ_SYSTEM_ROLE_ID,
845                )];
846                acl_items.extend_from_slice(&log.access);
847                self.insert_item(
848                    item_id,
849                    log.oid,
850                    name.clone(),
851                    CatalogItem::Log(Log {
852                        variant: log.variant,
853                        global_id,
854                    }),
855                    MZ_SYSTEM_ROLE_ID,
856                    PrivilegeMap::from_mz_acl_items(acl_items),
857                );
858            }
859
860            Builtin::Table(table) => {
861                let mut acl_items = vec![rbac::owner_privilege(
862                    mz_sql::catalog::ObjectType::Table,
863                    MZ_SYSTEM_ROLE_ID,
864                )];
865                acl_items.extend_from_slice(&table.access);
866
867                self.insert_item(
868                    item_id,
869                    table.oid,
870                    name.clone(),
871                    CatalogItem::Table(Table {
872                        create_sql: None,
873                        desc: VersionedRelationDesc::new(table.desc.clone()),
874                        collections: [(RelationVersion::root(), global_id)].into_iter().collect(),
875                        conn_id: None,
876                        resolved_ids: ResolvedIds::empty(),
877                        custom_logical_compaction_window: table.is_retained_metrics_object.then(
878                            || {
879                                self.system_config()
880                                    .metrics_retention()
881                                    .try_into()
882                                    .expect("invalid metrics retention")
883                            },
884                        ),
885                        is_retained_metrics_object: table.is_retained_metrics_object,
886                        data_source: TableDataSource::TableWrites {
887                            defaults: vec![Expr::null(); table.desc.arity()],
888                        },
889                    }),
890                    MZ_SYSTEM_ROLE_ID,
891                    PrivilegeMap::from_mz_acl_items(acl_items),
892                );
893            }
894            Builtin::Index(index) => {
895                let custom_logical_compaction_window =
896                    index.is_retained_metrics_object.then(|| {
897                        self.system_config()
898                            .metrics_retention()
899                            .try_into()
900                            .expect("invalid metrics retention")
901                    });
902                // Indexes can't be versioned.
903                let versions = BTreeMap::new();
904
905                let item = self
906                    .parse_item(
907                        global_id,
908                        &index.create_sql(),
909                        &versions,
910                        None,
911                        index.is_retained_metrics_object,
912                        custom_logical_compaction_window,
913                        local_expression_cache,
914                        None,
915                    )
916                    .unwrap_or_else(|e| {
917                        panic!(
918                            "internal error: failed to load bootstrap index:\n\
919                                    {}\n\
920                                    error:\n\
921                                    {:?}\n\n\
922                                    make sure that the schema name is specified in the builtin index's create sql statement.",
923                            index.name, e
924                        )
925                    });
926                let CatalogItem::Index(_) = item else {
927                    panic!(
928                        "internal error: builtin index {}'s SQL does not begin with \"CREATE INDEX\".",
929                        index.name
930                    );
931                };
932
933                self.insert_item(
934                    item_id,
935                    index.oid,
936                    name,
937                    item,
938                    MZ_SYSTEM_ROLE_ID,
939                    PrivilegeMap::default(),
940                );
941            }
942            Builtin::View(_) => {
943                // parse_views is responsible for inserting all builtin views.
944                unreachable!("views added elsewhere");
945            }
946
947            // Note: Element types must be loaded before array types.
948            Builtin::Type(typ) => {
949                let typ = self.resolve_builtin_type_references(typ);
950                if let CatalogType::Array { element_reference } = typ.details.typ {
951                    let entry = self.get_entry_mut(&element_reference);
952                    let item_type = match &mut entry.item {
953                        CatalogItem::Type(item_type) => item_type,
954                        _ => unreachable!("types can only reference other types"),
955                    };
956                    item_type.details.array_id = Some(item_id);
957                }
958
959                let schema_id = self.resolve_system_schema(typ.schema);
960
961                self.insert_item(
962                    item_id,
963                    typ.oid,
964                    QualifiedItemName {
965                        qualifiers: ItemQualifiers {
966                            database_spec: ResolvedDatabaseSpecifier::Ambient,
967                            schema_spec: SchemaSpecifier::Id(schema_id),
968                        },
969                        item: typ.name.to_owned(),
970                    },
971                    CatalogItem::Type(Type {
972                        create_sql: None,
973                        global_id,
974                        details: typ.details.clone(),
975                        resolved_ids: ResolvedIds::empty(),
976                    }),
977                    MZ_SYSTEM_ROLE_ID,
978                    PrivilegeMap::from_mz_acl_items(vec![
979                        rbac::default_builtin_object_privilege(mz_sql::catalog::ObjectType::Type),
980                        rbac::owner_privilege(mz_sql::catalog::ObjectType::Type, MZ_SYSTEM_ROLE_ID),
981                    ]),
982                );
983            }
984
985            Builtin::Func(func) => {
986                // This OID is never used. `func` has a `Vec` of implementations and
987                // each implementation has its own OID. Those are the OIDs that are
988                // actually used by the system.
989                let oid = INVALID_OID;
990                self.insert_item(
991                    item_id,
992                    oid,
993                    name.clone(),
994                    CatalogItem::Func(Func {
995                        inner: func.inner,
996                        global_id,
997                    }),
998                    MZ_SYSTEM_ROLE_ID,
999                    PrivilegeMap::default(),
1000                );
1001            }
1002
1003            Builtin::Source(coll) => {
1004                let mut acl_items = vec![rbac::owner_privilege(
1005                    mz_sql::catalog::ObjectType::Source,
1006                    MZ_SYSTEM_ROLE_ID,
1007                )];
1008                acl_items.extend_from_slice(&coll.access);
1009
1010                self.insert_item(
1011                    item_id,
1012                    coll.oid,
1013                    name.clone(),
1014                    CatalogItem::Source(Source {
1015                        create_sql: None,
1016                        data_source: coll.data_source.clone(),
1017                        desc: coll.desc.clone(),
1018                        global_id,
1019                        timeline: Timeline::EpochMilliseconds,
1020                        resolved_ids: ResolvedIds::empty(),
1021                        custom_logical_compaction_window: coll.is_retained_metrics_object.then(
1022                            || {
1023                                self.system_config()
1024                                    .metrics_retention()
1025                                    .try_into()
1026                                    .expect("invalid metrics retention")
1027                            },
1028                        ),
1029                        is_retained_metrics_object: coll.is_retained_metrics_object,
1030                    }),
1031                    MZ_SYSTEM_ROLE_ID,
1032                    PrivilegeMap::from_mz_acl_items(acl_items),
1033                );
1034            }
1035            Builtin::MaterializedView(mv) => {
1036                let mut acl_items = vec![rbac::owner_privilege(
1037                    mz_sql::catalog::ObjectType::MaterializedView,
1038                    MZ_SYSTEM_ROLE_ID,
1039                )];
1040                acl_items.extend_from_slice(&mv.access);
1041
1042                let custom_logical_compaction_window = mv.is_retained_metrics_object.then(|| {
1043                    self.system_config()
1044                        .metrics_retention()
1045                        .try_into()
1046                        .expect("invalid metrics retention")
1047                });
1048
1049                // Builtin materialized views can't be versioned.
1050                let versions = BTreeMap::new();
1051
1052                let mut item = self
1053                    .parse_item(
1054                        global_id,
1055                        &mv.create_sql(),
1056                        &versions,
1057                        None,
1058                        mv.is_retained_metrics_object,
1059                        custom_logical_compaction_window,
1060                        local_expression_cache,
1061                        None,
1062                    )
1063                    .unwrap_or_else(|e| {
1064                        panic!(
1065                            "internal error: failed to load bootstrap materialized view:\n\
1066                             {}\n\
1067                             error:\n\
1068                             {e:?}\n\n\
1069                             make sure that the schema name is specified in the builtin \
1070                             materialized view's create sql statement.",
1071                            mv.name,
1072                        )
1073                    });
1074                let CatalogItem::MaterializedView(catalog_mv) = &mut item else {
1075                    panic!(
1076                        "internal error: builtin materialized view {}'s SQL does not begin \
1077                         with \"CREATE MATERIALIZED VIEW\".",
1078                        mv.name,
1079                    );
1080                };
1081
1082                // The optimizer can only infer keys from MV definitions, but cannot infer
1083                // uniqueness present in the input data. Extend with the keys declared in the
1084                // builtin definition, to allow supplying additional key knowledge.
1085                let mut desc = catalog_mv.desc.latest();
1086                for key in &mv.desc.typ().keys {
1087                    desc = desc.with_key(key.clone());
1088                }
1089                catalog_mv.desc = VersionedRelationDesc::new(desc);
1090
1091                self.insert_item(
1092                    item_id,
1093                    mv.oid,
1094                    name,
1095                    item,
1096                    MZ_SYSTEM_ROLE_ID,
1097                    PrivilegeMap::from_mz_acl_items(acl_items),
1098                );
1099            }
1100            Builtin::Connection(connection) => {
1101                // Connections can't be versioned.
1102                let versions = BTreeMap::new();
1103                let mut item = self
1104                    .parse_item(
1105                        global_id,
1106                        connection.sql,
1107                        &versions,
1108                        None,
1109                        false,
1110                        None,
1111                        local_expression_cache,
1112                        None,
1113                    )
1114                    .unwrap_or_else(|e| {
1115                        panic!(
1116                            "internal error: failed to load bootstrap connection:\n\
1117                                    {}\n\
1118                                    error:\n\
1119                                    {:?}\n\n\
1120                                    make sure that the schema name is specified in the builtin connection's create sql statement.",
1121                            connection.name, e
1122                        )
1123                    });
1124                let CatalogItem::Connection(_) = &mut item else {
1125                    panic!(
1126                        "internal error: builtin connection {}'s SQL does not begin with \"CREATE CONNECTION\".",
1127                        connection.name
1128                    );
1129                };
1130
1131                let mut acl_items = vec![rbac::owner_privilege(
1132                    mz_sql::catalog::ObjectType::Connection,
1133                    connection.owner_id.clone(),
1134                )];
1135                acl_items.extend_from_slice(connection.access);
1136
1137                self.insert_item(
1138                    item_id,
1139                    connection.oid,
1140                    name.clone(),
1141                    item,
1142                    connection.owner_id.clone(),
1143                    PrivilegeMap::from_mz_acl_items(acl_items),
1144                );
1145            }
1146        }
1147    }
1148
1149    #[instrument(level = "debug")]
1150    fn apply_temporary_item_update(
1151        &mut self,
1152        temporary_item: TemporaryItem,
1153        diff: StateDiff,
1154        retractions: &mut InProgressRetractions,
1155        local_expression_cache: &mut LocalExpressionCache,
1156    ) {
1157        match diff {
1158            StateDiff::Addition => {
1159                let TemporaryItem {
1160                    id,
1161                    oid,
1162                    global_id,
1163                    schema_id,
1164                    name,
1165                    conn_id,
1166                    create_sql,
1167                    owner_id,
1168                    privileges,
1169                    extra_versions,
1170                } = temporary_item;
1171                // Lazily create the temporary schema if it doesn't exist yet.
1172                // We need the conn_id to create the schema, and it should always be Some for temp items.
1173                let temp_conn_id = conn_id
1174                    .as_ref()
1175                    .expect("temporary items must have a connection id");
1176                if !self.temporary_schemas.contains_key(temp_conn_id) {
1177                    self.create_temporary_schema(temp_conn_id, owner_id)
1178                        .expect("failed to create temporary schema");
1179                }
1180                let schema = self.find_temp_schema(&schema_id);
1181                let name = QualifiedItemName {
1182                    qualifiers: ItemQualifiers {
1183                        database_spec: schema.database().clone(),
1184                        schema_spec: schema.id().clone(),
1185                    },
1186                    item: name.clone(),
1187                };
1188
1189                let entry = match retractions.temp_items.remove(&id) {
1190                    Some(mut retraction) => {
1191                        assert_eq!(retraction.id, id);
1192
1193                        // We only reparse the SQL if it's changed. Otherwise, we use the existing
1194                        // item. This is a performance optimization and not needed for correctness.
1195                        // This makes it difficult to use the `UpdateFrom` trait, but the structure
1196                        // is still the same as the trait.
1197                        if retraction.create_sql() != create_sql {
1198                            let mut catalog_item = self
1199                                .deserialize_item(
1200                                    global_id,
1201                                    &create_sql,
1202                                    &extra_versions,
1203                                    local_expression_cache,
1204                                    Some(retraction.item),
1205                                )
1206                                .unwrap_or_else(|e| {
1207                                    panic!("{e:?}: invalid persisted SQL: {create_sql}")
1208                                });
1209                            // Have to patch up the item because parsing doesn't
1210                            // take into account temporary schemas/conn_id.
1211                            // NOTE(aljoscha): I don't like how we're patching
1212                            // this in here, but it's but one of the ways in
1213                            // which temporary items are a bit weird. So, here
1214                            // we are ...
1215                            catalog_item.set_conn_id(conn_id);
1216                            // Deserializing replans the SQL, and the planner's
1217                            // canonical printing can differ from `create_sql`,
1218                            // for example when a feature flag changed how
1219                            // references are printed since `create_sql` was
1220                            // produced. Keep the exact input. A later op in the
1221                            // same transaction retracts this item by
1222                            // re-serializing it, and that retraction must
1223                            // cancel byte-for-byte against this addition during
1224                            // consolidation, else two retractions of the same
1225                            // id survive and applying them panics.
1226                            catalog_item.set_create_sql(create_sql);
1227                            retraction.item = catalog_item;
1228                        }
1229
1230                        retraction.id = id;
1231                        retraction.oid = oid;
1232                        retraction.name = name;
1233                        retraction.owner_id = owner_id;
1234                        retraction.privileges = PrivilegeMap::from_mz_acl_items(privileges);
1235                        retraction
1236                    }
1237                    None => {
1238                        let mut catalog_item = self
1239                            .deserialize_item(
1240                                global_id,
1241                                &create_sql,
1242                                &extra_versions,
1243                                local_expression_cache,
1244                                None,
1245                            )
1246                            .unwrap_or_else(|e| {
1247                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1248                            });
1249
1250                        // Have to patch up the item because parsing doesn't
1251                        // take into account temporary schemas/conn_id.
1252                        // NOTE(aljoscha): I don't like how we're patching this
1253                        // in here, but it's but one of the ways in which
1254                        // temporary items are a bit weird. So, here we are ...
1255                        catalog_item.set_conn_id(conn_id);
1256                        // Keep the exact input create_sql, not the replanned
1257                        // printing. See the comment on the reparse above.
1258                        catalog_item.set_create_sql(create_sql);
1259
1260                        CatalogEntry {
1261                            item: catalog_item,
1262                            referenced_by: Vec::new(),
1263                            used_by: Vec::new(),
1264                            id,
1265                            oid,
1266                            name,
1267                            owner_id,
1268                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1269                        }
1270                    }
1271                };
1272                self.insert_entry(entry);
1273            }
1274            StateDiff::Retraction => {
1275                let entry = self.drop_item(temporary_item.id);
1276                retractions.temp_items.insert(temporary_item.id, entry);
1277            }
1278        }
1279    }
1280
1281    #[instrument(level = "debug")]
1282    fn apply_item_update(
1283        &mut self,
1284        item: mz_catalog::durable::Item,
1285        diff: StateDiff,
1286        retractions: &mut InProgressRetractions,
1287        local_expression_cache: &mut LocalExpressionCache,
1288    ) -> Result<(), CatalogError> {
1289        match diff {
1290            StateDiff::Addition => {
1291                let key = item.key();
1292                let mz_catalog::durable::Item {
1293                    id,
1294                    oid,
1295                    global_id,
1296                    schema_id,
1297                    name,
1298                    create_sql,
1299                    owner_id,
1300                    privileges,
1301                    extra_versions,
1302                } = item;
1303                let schema = self.find_non_temp_schema(&schema_id);
1304                let name = QualifiedItemName {
1305                    qualifiers: ItemQualifiers {
1306                        database_spec: schema.database().clone(),
1307                        schema_spec: schema.id().clone(),
1308                    },
1309                    item: name.clone(),
1310                };
1311                let entry = match retractions.items.remove(&key) {
1312                    Some(retraction) => {
1313                        assert_eq!(retraction.id, item.id);
1314
1315                        let item = self
1316                            .deserialize_item(
1317                                global_id,
1318                                &create_sql,
1319                                &extra_versions,
1320                                local_expression_cache,
1321                                Some(retraction.item),
1322                            )
1323                            .unwrap_or_else(|e| {
1324                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1325                            });
1326
1327                        CatalogEntry {
1328                            item,
1329                            id,
1330                            oid,
1331                            name,
1332                            owner_id,
1333                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1334                            referenced_by: retraction.referenced_by,
1335                            used_by: retraction.used_by,
1336                        }
1337                    }
1338                    None => {
1339                        let catalog_item = self
1340                            .deserialize_item(
1341                                global_id,
1342                                &create_sql,
1343                                &extra_versions,
1344                                local_expression_cache,
1345                                None,
1346                            )
1347                            .unwrap_or_else(|e| {
1348                                panic!("{e:?}: invalid persisted SQL: {create_sql}")
1349                            });
1350                        CatalogEntry {
1351                            item: catalog_item,
1352                            referenced_by: Vec::new(),
1353                            used_by: Vec::new(),
1354                            id,
1355                            oid,
1356                            name,
1357                            owner_id,
1358                            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1359                        }
1360                    }
1361                };
1362
1363                self.insert_entry(entry);
1364            }
1365            StateDiff::Retraction => {
1366                let entry = self.drop_item(item.id);
1367                let key = item.into_key_value().0;
1368                retractions.items.insert(key, entry);
1369            }
1370        }
1371        Ok(())
1372    }
1373
1374    #[instrument(level = "debug")]
1375    fn apply_comment_update(
1376        &mut self,
1377        comment: mz_catalog::durable::Comment,
1378        diff: StateDiff,
1379        _retractions: &mut InProgressRetractions,
1380    ) {
1381        match diff {
1382            StateDiff::Addition => {
1383                let prev = Arc::make_mut(&mut self.comments).update_comment(
1384                    comment.object_id,
1385                    comment.sub_component,
1386                    Some(comment.comment),
1387                );
1388                assert_eq!(
1389                    prev, None,
1390                    "values must be explicitly retracted before inserting a new value"
1391                );
1392            }
1393            StateDiff::Retraction => {
1394                let prev = Arc::make_mut(&mut self.comments).update_comment(
1395                    comment.object_id,
1396                    comment.sub_component,
1397                    None,
1398                );
1399                assert_eq!(
1400                    prev,
1401                    Some(comment.comment),
1402                    "retraction does not match existing value: ({:?}, {:?})",
1403                    comment.object_id,
1404                    comment.sub_component,
1405                );
1406            }
1407        }
1408    }
1409
1410    #[instrument(level = "debug")]
1411    fn apply_source_references_update(
1412        &mut self,
1413        source_references: mz_catalog::durable::SourceReferences,
1414        diff: StateDiff,
1415        _retractions: &mut InProgressRetractions,
1416    ) {
1417        match diff {
1418            StateDiff::Addition => {
1419                let prev = self
1420                    .source_references
1421                    .insert(source_references.source_id, source_references.into());
1422                assert!(
1423                    prev.is_none(),
1424                    "values must be explicitly retracted before inserting a new value: {prev:?}"
1425                );
1426            }
1427            StateDiff::Retraction => {
1428                let prev = self.source_references.remove(&source_references.source_id);
1429                assert!(
1430                    prev.is_some(),
1431                    "retraction for a non-existent existing value: {source_references:?}"
1432                );
1433            }
1434        }
1435    }
1436
1437    #[instrument(level = "debug")]
1438    fn apply_storage_collection_metadata_update(
1439        &mut self,
1440        storage_collection_metadata: mz_catalog::durable::StorageCollectionMetadata,
1441        diff: StateDiff,
1442        _retractions: &mut InProgressRetractions,
1443    ) {
1444        apply_inverted_lookup(
1445            &mut Arc::make_mut(&mut self.storage_metadata).collection_metadata,
1446            &storage_collection_metadata.id,
1447            storage_collection_metadata.shard,
1448            diff,
1449        );
1450    }
1451
1452    #[instrument(level = "debug")]
1453    fn apply_unfinalized_shard_update(
1454        &mut self,
1455        unfinalized_shard: mz_catalog::durable::UnfinalizedShard,
1456        diff: StateDiff,
1457        _retractions: &mut InProgressRetractions,
1458    ) {
1459        match diff {
1460            StateDiff::Addition => {
1461                let newly_inserted = Arc::make_mut(&mut self.storage_metadata)
1462                    .unfinalized_shards
1463                    .insert(unfinalized_shard.shard);
1464                assert!(
1465                    newly_inserted,
1466                    "values must be explicitly retracted before inserting a new value: {unfinalized_shard:?}",
1467                );
1468            }
1469            StateDiff::Retraction => {
1470                let removed = Arc::make_mut(&mut self.storage_metadata)
1471                    .unfinalized_shards
1472                    .remove(&unfinalized_shard.shard);
1473                assert!(
1474                    removed,
1475                    "retraction does not match existing value: {unfinalized_shard:?}"
1476                );
1477            }
1478        }
1479    }
1480
1481    /// Generate a list of `BuiltinTableUpdate`s that correspond to a single update made to the
1482    /// durable catalog.
1483    #[instrument(level = "debug")]
1484    pub(crate) fn generate_builtin_table_update(
1485        &self,
1486        kind: StateUpdateKind,
1487        diff: StateDiff,
1488    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
1489        let diff = diff.into();
1490        match kind {
1491            // mz_roles and mz_role_parameters are MaterializedViews backed by
1492            // mz_internal.mz_catalog_raw, so role rows do not produce builtin
1493            // table updates here.
1494            StateUpdateKind::Role(_) => Vec::new(),
1495            StateUpdateKind::RoleAuth(role_auth) => {
1496                vec![self.pack_role_auth_update(role_auth.role_id, diff)]
1497            }
1498            // mz_default_privileges and mz_system_privileges are MaterializedViews
1499            // backed by mz_internal.mz_catalog_raw, so privilege rows do not
1500            // produce builtin table updates here.
1501            StateUpdateKind::DefaultPrivilege(_) => Vec::new(),
1502            StateUpdateKind::SystemPrivilege(_) => Vec::new(),
1503            StateUpdateKind::SystemConfiguration(_) => Vec::new(),
1504            // mz_internal.mz_{cluster,replica}_system_parameters are
1505            // MaterializedViews backed by mz_internal.mz_catalog_raw, so the
1506            // durable scoped-configuration rows do not produce builtin table
1507            // updates here. (The in-memory working copy used for resolution is
1508            // maintained separately in `apply_*_system_configuration_update`.)
1509            StateUpdateKind::ClusterSystemConfiguration(_) => Vec::new(),
1510            StateUpdateKind::ReplicaSystemConfiguration(_) => Vec::new(),
1511            // mz_clusters and mz_cluster_schedules are MaterializedViews backed
1512            // by mz_internal.mz_catalog_raw, so cluster rows do not produce
1513            // builtin table updates here.
1514            StateUpdateKind::Cluster(_) => Vec::new(),
1515            StateUpdateKind::IntrospectionSourceIndex(introspection_source_index) => {
1516                self.pack_item_update(introspection_source_index.item_id, diff)
1517            }
1518            // mz_cluster_replicas is a MaterializedView backed by
1519            // mz_internal.mz_catalog_raw.
1520            StateUpdateKind::ClusterReplica(_) => Vec::new(),
1521            StateUpdateKind::SystemObjectMapping(system_object_mapping) => {
1522                // Runtime-alterable system objects have real entries in the
1523                // items collection and so get handled through the normal
1524                // `StateUpdateKind::Item`.`
1525                if !system_object_mapping.unique_identifier.runtime_alterable() {
1526                    self.pack_item_update(system_object_mapping.unique_identifier.catalog_id, diff)
1527                } else {
1528                    vec![]
1529                }
1530            }
1531            StateUpdateKind::TemporaryItem(item) => self.pack_item_update(item.id, diff),
1532            StateUpdateKind::Item(item) => self.pack_item_update(item.id, diff),
1533            StateUpdateKind::Comment(_) => Vec::new(),
1534            StateUpdateKind::SourceReferences(source_references) => {
1535                self.pack_source_references_update(&source_references, diff)
1536            }
1537            // mz_audit_events is a MaterializedView backed by
1538            // mz_internal.mz_catalog_raw, so audit log rows do not produce
1539            // builtin table updates here.
1540            StateUpdateKind::AuditLog(_) => Vec::new(),
1541            StateUpdateKind::Database(_)
1542            | StateUpdateKind::Schema(_)
1543            | StateUpdateKind::NetworkPolicy(_)
1544            | StateUpdateKind::StorageCollectionMetadata(_)
1545            | StateUpdateKind::UnfinalizedShard(_) => Vec::new(),
1546        }
1547    }
1548
1549    fn get_entry_mut(&mut self, id: &CatalogItemId) -> &mut CatalogEntry {
1550        self.entry_by_id
1551            .get_mut(id)
1552            .unwrap_or_else(|| panic!("catalog out of sync, missing id {id}"))
1553    }
1554
1555    /// Set the optimized plan for the item identified by `id`.
1556    ///
1557    /// # Panics
1558    /// If the item is not an `Index` or `MaterializedView`.
1559    pub(super) fn set_optimized_plan(
1560        &mut self,
1561        id: GlobalId,
1562        plan: DataflowDescription<mz_expr::OptimizedMirRelationExpr>,
1563    ) {
1564        let item_id = self.entry_by_global_id[&id];
1565        let entry = self.get_entry_mut(&item_id);
1566        match entry.item_mut() {
1567            CatalogItem::Index(idx) => idx.optimized_plan = Some(Arc::new(plan)),
1568            CatalogItem::MaterializedView(mv) => mv.optimized_plan = Some(Arc::new(plan)),
1569            other => panic!("set_optimized_plan called on {} ({:?})", id, other.typ()),
1570        }
1571    }
1572
1573    /// Set the physical plan for the item identified by `id`.
1574    ///
1575    /// # Panics
1576    /// If the item is not an `Index` or `MaterializedView`.
1577    pub(super) fn set_physical_plan(
1578        &mut self,
1579        id: GlobalId,
1580        plan: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
1581    ) {
1582        let item_id = self.entry_by_global_id[&id];
1583        let entry = self.get_entry_mut(&item_id);
1584        match entry.item_mut() {
1585            CatalogItem::Index(idx) => idx.physical_plan = Some(Arc::new(plan)),
1586            CatalogItem::MaterializedView(mv) => mv.physical_plan = Some(Arc::new(plan)),
1587            other => panic!("set_physical_plan called on {} ({:?})", id, other.typ()),
1588        }
1589    }
1590
1591    /// Set the `DataflowMetainfo` for the item identified by `id`.
1592    ///
1593    /// # Panics
1594    /// If the item is not an `Index` or `MaterializedView`.
1595    pub(super) fn set_dataflow_metainfo(
1596        &mut self,
1597        id: GlobalId,
1598        metainfo: DataflowMetainfo<Arc<OptimizerNotice>>,
1599    ) {
1600        // Add entries to the `notices_by_dep_id` lookup map.
1601        for notice in metainfo.optimizer_notices.iter() {
1602            for dep_id in notice.dependencies.iter() {
1603                self.notices_by_dep_id
1604                    .entry(*dep_id)
1605                    .or_default()
1606                    .push(Arc::clone(notice));
1607            }
1608            if let Some(item_id) = notice.item_id {
1609                soft_assert_eq_or_log!(
1610                    item_id,
1611                    id,
1612                    "notice.item_id should match the id for whom we are saving the notice"
1613                );
1614            }
1615        }
1616        // Set the metainfo on the catalog object.
1617        let item_id = self.entry_by_global_id[&id];
1618        let entry = self.get_entry_mut(&item_id);
1619        match entry.item_mut() {
1620            CatalogItem::Index(idx) => idx.dataflow_metainfo = Some(metainfo),
1621            CatalogItem::MaterializedView(mv) => mv.dataflow_metainfo = Some(metainfo),
1622            other => panic!("set_dataflow_metainfo called on {} ({:?})", id, other.typ()),
1623        }
1624    }
1625
1626    /// Clean up optimizer notices for the given dropped catalog entries.
1627    ///
1628    /// This extracts notices directly from the owned entries (which
1629    /// have already been removed from the catalog maps), cleans up
1630    /// the `notices_by_dep_id` reverse index, and removes notices
1631    /// from other (still-live) catalog objects that depended on the
1632    /// dropped items.
1633    ///
1634    /// Returns the set of all dropped notices for builtin table
1635    /// retraction.
1636    #[mz_ore::instrument(level = "trace")]
1637    pub(super) fn drop_optimizer_notices(
1638        &mut self,
1639        dropped_entries: Vec<CatalogEntry>,
1640    ) -> BTreeSet<Arc<OptimizerNotice>> {
1641        let mut dropped_notices = BTreeSet::new();
1642        let mut drop_ids = BTreeSet::new();
1643
1644        // Extract notices directly from the owned dropped entries.
1645        for mut entry in dropped_entries {
1646            drop_ids.extend(entry.global_ids());
1647            let metainfo = match entry.item_mut() {
1648                CatalogItem::Index(idx) => idx.dataflow_metainfo.take(),
1649                CatalogItem::MaterializedView(mv) => mv.dataflow_metainfo.take(),
1650                _ => None,
1651            };
1652            if let Some(mut metainfo) = metainfo {
1653                soft_assert_or_log!(
1654                    metainfo.optimizer_notices.iter().all_unique(),
1655                    "should have been pushed there by \
1656                     `push_optimizer_notice_dedup`"
1657                );
1658                for n in metainfo.optimizer_notices.drain(..) {
1659                    // Clean up notices_by_dep_id for this notice's
1660                    // dependencies.
1661                    for dep_id in n.dependencies.iter() {
1662                        if let Some(notices) = self.notices_by_dep_id.get_mut(dep_id) {
1663                            notices.retain(|x| &n != x);
1664                            if notices.is_empty() {
1665                                self.notices_by_dep_id.remove(dep_id);
1666                            }
1667                        }
1668                    }
1669                    dropped_notices.insert(n);
1670                }
1671            }
1672        }
1673
1674        // Remove notices_by_dep_id entries keyed by the dropped IDs.
1675        // These are notices on OTHER items that depend on a dropped
1676        // item. We need to remove them from those items' metainfo.
1677        for id in &drop_ids {
1678            if let Some(notices) = self.notices_by_dep_id.remove(id) {
1679                for n in notices.into_iter() {
1680                    // Remove the notice from the catalog object it
1681                    // lives on (if that object still exists — it
1682                    // may have been dropped too, in which case the
1683                    // notice was already collected above).
1684                    if let Some(item_id) = n.item_id.as_ref() {
1685                        if let Some(entry) = self.try_get_entry_by_global_id(item_id) {
1686                            let catalog_item_id = entry.id();
1687                            let entry = self.get_entry_mut(&catalog_item_id);
1688                            let item = entry.item_mut();
1689                            match item {
1690                                CatalogItem::Index(idx) => {
1691                                    if let Some(ref mut m) = idx.dataflow_metainfo {
1692                                        m.optimizer_notices.retain(|x| &n != x);
1693                                    }
1694                                }
1695                                CatalogItem::MaterializedView(mv) => {
1696                                    if let Some(ref mut m) = mv.dataflow_metainfo {
1697                                        m.optimizer_notices.retain(|x| &n != x);
1698                                    }
1699                                }
1700                                _ => {}
1701                            }
1702                        }
1703                    }
1704                    dropped_notices.insert(n);
1705                }
1706            }
1707        }
1708
1709        // Clean up notices_by_dep_id entries for dependency IDs
1710        // that are NOT being dropped but had dropped notices.
1711        let todo_dep_ids: BTreeSet<GlobalId> = dropped_notices
1712            .iter()
1713            .flat_map(|n| n.dependencies.iter())
1714            .filter(|dep_id| !drop_ids.contains(dep_id))
1715            .copied()
1716            .collect();
1717        for id in todo_dep_ids {
1718            if let Some(notices) = self.notices_by_dep_id.get_mut(&id) {
1719                notices.retain(|n| !dropped_notices.contains(n));
1720                if notices.is_empty() {
1721                    self.notices_by_dep_id.remove(&id);
1722                }
1723            }
1724        }
1725
1726        dropped_notices
1727    }
1728
1729    fn get_schema_mut(
1730        &mut self,
1731        database_spec: &ResolvedDatabaseSpecifier,
1732        schema_spec: &SchemaSpecifier,
1733        conn_id: &ConnectionId,
1734    ) -> &mut Schema {
1735        // Keep in sync with `get_schemas`
1736        match (database_spec, schema_spec) {
1737            (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Temporary) => self
1738                .temporary_schemas
1739                .get_mut(conn_id)
1740                .expect("catalog out of sync"),
1741            (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Id(id)) => self
1742                .ambient_schemas_by_id
1743                .get_mut(id)
1744                .expect("catalog out of sync"),
1745            (ResolvedDatabaseSpecifier::Id(database_id), SchemaSpecifier::Id(schema_id)) => self
1746                .database_by_id
1747                .get_mut(database_id)
1748                .expect("catalog out of sync")
1749                .schemas_by_id
1750                .get_mut(schema_id)
1751                .expect("catalog out of sync"),
1752            (ResolvedDatabaseSpecifier::Id(_), SchemaSpecifier::Temporary) => {
1753                unreachable!("temporary schemas are in the ambient database")
1754            }
1755        }
1756    }
1757
1758    /// Install builtin views to the catalog. This is its own function so that views can be
1759    /// optimized in parallel.
1760    ///
1761    /// The implementation is similar to `apply_updates_for_bootstrap` and determines dependency
1762    /// problems by sniffing out specific errors and then retrying once those dependencies are
1763    /// complete. This doesn't work for everything (casts, function implementations) so we also need
1764    /// to have a bucket for everything at the end. Additionally, because this executes in parellel,
1765    /// we must maintain a completed set otherwise races could result in orphaned views languishing
1766    /// in awaiting with nothing retriggering the attempt.
1767    #[instrument(name = "catalog::parse_views")]
1768    async fn parse_builtin_views(
1769        state: &mut CatalogState,
1770        builtin_views: Vec<(&'static BuiltinView, CatalogItemId, GlobalId)>,
1771        retractions: &mut InProgressRetractions,
1772        local_expression_cache: &mut LocalExpressionCache,
1773    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
1774        let mut builtin_table_updates = Vec::with_capacity(builtin_views.len());
1775        let (updates, additions): (Vec<_>, Vec<_>) =
1776            builtin_views
1777                .into_iter()
1778                .partition_map(|(view, item_id, gid)| {
1779                    match retractions.system_object_mappings.remove(&item_id) {
1780                        Some(entry) => Either::Left(entry),
1781                        None => Either::Right((view, item_id, gid)),
1782                    }
1783                });
1784
1785        for entry in updates {
1786            // This implies that we updated the fingerprint for some builtin view. The retraction
1787            // was parsed, planned, and optimized using the compiled in definition, not the
1788            // definition from a previous version. So we can just stick the old entry back into the
1789            // catalog.
1790            let item_id = entry.id();
1791            state.insert_entry(entry);
1792            builtin_table_updates.extend(state.pack_item_update(item_id, Diff::ONE));
1793        }
1794
1795        let mut handles = Vec::new();
1796        let mut awaiting_id_dependencies: BTreeMap<CatalogItemId, Vec<CatalogItemId>> =
1797            BTreeMap::new();
1798        let mut awaiting_name_dependencies: BTreeMap<String, Vec<CatalogItemId>> = BTreeMap::new();
1799        // Some errors are due to the implementation of casts or SQL functions that depend on some
1800        // view. Instead of figuring out the exact view dependency, delay these until the end.
1801        let mut awaiting_all = Vec::new();
1802        // Completed views, needed to avoid race conditions.
1803        let mut completed_ids: BTreeSet<CatalogItemId> = BTreeSet::new();
1804        let mut completed_names: BTreeSet<String> = BTreeSet::new();
1805
1806        // Avoid some reference lifetime issues by not passing `builtin` into the spawned task.
1807        let mut views: BTreeMap<CatalogItemId, (&BuiltinView, GlobalId)> = additions
1808            .into_iter()
1809            .map(|(view, item_id, gid)| (item_id, (view, gid)))
1810            .collect();
1811        let item_ids: Vec<_> = views.keys().copied().collect();
1812
1813        let mut ready: VecDeque<CatalogItemId> = views.keys().cloned().collect();
1814        while !handles.is_empty() || !ready.is_empty() || !awaiting_all.is_empty() {
1815            if handles.is_empty() && ready.is_empty() {
1816                // Enqueue the views that were waiting for all the others.
1817                ready.extend(awaiting_all.drain(..));
1818            }
1819
1820            // Spawn tasks for all ready views.
1821            if !ready.is_empty() {
1822                let spawn_state = Arc::new(state.clone());
1823                while let Some(id) = ready.pop_front() {
1824                    let (view, global_id) = views.get(&id).expect("must exist");
1825                    let global_id = *global_id;
1826                    let create_sql = view.create_sql();
1827                    // Views can't be versioned.
1828                    let versions = BTreeMap::new();
1829
1830                    let span = info_span!(parent: None, "parse builtin view", name = view.name);
1831                    OpenTelemetryContext::obtain().attach_as_parent_to(&span);
1832                    let task_state = Arc::clone(&spawn_state);
1833                    let cached_expr = local_expression_cache.remove_cached_expression(&global_id);
1834                    let handle = mz_ore::task::spawn_blocking(
1835                        || "parse view",
1836                        move || {
1837                            span.in_scope(|| {
1838                                let res = task_state.parse_item_inner(
1839                                    global_id,
1840                                    &create_sql,
1841                                    &versions,
1842                                    None,
1843                                    false,
1844                                    None,
1845                                    cached_expr,
1846                                    None,
1847                                );
1848                                (id, global_id, res)
1849                            })
1850                        },
1851                    );
1852                    handles.push(handle);
1853                }
1854            }
1855
1856            // Wait for a view to be ready.
1857            let (selected, _idx, remaining) = future::select_all(handles).await;
1858            handles = remaining;
1859            let (id, global_id, res) = selected;
1860            let mut insert_cached_expr = |cached_expr| {
1861                if let Some(cached_expr) = cached_expr {
1862                    local_expression_cache.insert_cached_expression(global_id, cached_expr);
1863                }
1864            };
1865            match res {
1866                Ok((item, uncached_expr)) => {
1867                    if let Some((uncached_expr, optimizer_features)) = uncached_expr {
1868                        local_expression_cache.insert_uncached_expression(
1869                            global_id,
1870                            uncached_expr,
1871                            optimizer_features,
1872                        );
1873                    }
1874                    // Add item to catalog.
1875                    let (view, _gid) = views.remove(&id).expect("must exist");
1876                    let schema_id = state
1877                        .ambient_schemas_by_name
1878                        .get(view.schema)
1879                        .unwrap_or_else(|| panic!("unknown ambient schema: {}", view.schema));
1880                    let qname = QualifiedItemName {
1881                        qualifiers: ItemQualifiers {
1882                            database_spec: ResolvedDatabaseSpecifier::Ambient,
1883                            schema_spec: SchemaSpecifier::Id(*schema_id),
1884                        },
1885                        item: view.name.into(),
1886                    };
1887                    let mut acl_items = vec![rbac::owner_privilege(
1888                        mz_sql::catalog::ObjectType::View,
1889                        MZ_SYSTEM_ROLE_ID,
1890                    )];
1891                    acl_items.extend_from_slice(&view.access);
1892
1893                    state.insert_item(
1894                        id,
1895                        view.oid,
1896                        qname,
1897                        item,
1898                        MZ_SYSTEM_ROLE_ID,
1899                        PrivilegeMap::from_mz_acl_items(acl_items),
1900                    );
1901
1902                    // Enqueue any items waiting on this dependency.
1903                    let mut resolved_dependent_items = Vec::new();
1904                    if let Some(dependent_items) = awaiting_id_dependencies.remove(&id) {
1905                        resolved_dependent_items.extend(dependent_items);
1906                    }
1907                    let entry = state.get_entry(&id);
1908                    let full_name = state.resolve_full_name(entry.name(), None).to_string();
1909                    if let Some(dependent_items) = awaiting_name_dependencies.remove(&full_name) {
1910                        resolved_dependent_items.extend(dependent_items);
1911                    }
1912                    ready.extend(resolved_dependent_items);
1913
1914                    completed_ids.insert(id);
1915                    completed_names.insert(full_name);
1916                }
1917                // If we were missing a dependency, wait for it to be added.
1918                Err((
1919                    AdapterError::PlanError(plan::PlanError::InvalidId(missing_dep)),
1920                    cached_expr,
1921                )) => {
1922                    insert_cached_expr(cached_expr);
1923                    if completed_ids.contains(&missing_dep) {
1924                        ready.push_back(id);
1925                    } else {
1926                        awaiting_id_dependencies
1927                            .entry(missing_dep)
1928                            .or_default()
1929                            .push(id);
1930                    }
1931                }
1932                // If we were missing a dependency, wait for it to be added.
1933                Err((
1934                    AdapterError::PlanError(plan::PlanError::Catalog(
1935                        SqlCatalogError::UnknownItem(missing_dep),
1936                    )),
1937                    cached_expr,
1938                )) => {
1939                    insert_cached_expr(cached_expr);
1940                    match CatalogItemId::from_str(&missing_dep) {
1941                        Ok(missing_dep) => {
1942                            if completed_ids.contains(&missing_dep) {
1943                                ready.push_back(id);
1944                            } else {
1945                                awaiting_id_dependencies
1946                                    .entry(missing_dep)
1947                                    .or_default()
1948                                    .push(id);
1949                            }
1950                        }
1951                        Err(_) => {
1952                            if completed_names.contains(&missing_dep) {
1953                                ready.push_back(id);
1954                            } else {
1955                                awaiting_name_dependencies
1956                                    .entry(missing_dep)
1957                                    .or_default()
1958                                    .push(id);
1959                            }
1960                        }
1961                    }
1962                }
1963                Err((
1964                    AdapterError::PlanError(plan::PlanError::InvalidCast { .. }),
1965                    cached_expr,
1966                )) => {
1967                    insert_cached_expr(cached_expr);
1968                    awaiting_all.push(id);
1969                }
1970                Err((e, _)) => {
1971                    let (bad_view, _gid) = views.get(&id).expect("must exist");
1972                    panic!(
1973                        "internal error: failed to load bootstrap view:\n\
1974                            {name}\n\
1975                            error:\n\
1976                            {e:?}\n\n\
1977                            Make sure that the schema name is specified in the builtin view's create sql statement.
1978                            ",
1979                        name = bad_view.name,
1980                    )
1981                }
1982            }
1983        }
1984
1985        assert!(awaiting_id_dependencies.is_empty());
1986        assert!(
1987            awaiting_name_dependencies.is_empty(),
1988            "awaiting_name_dependencies: {awaiting_name_dependencies:?}"
1989        );
1990        assert!(awaiting_all.is_empty());
1991        assert!(views.is_empty());
1992
1993        // Generate a builtin table update for all the new views.
1994        builtin_table_updates.extend(
1995            item_ids
1996                .into_iter()
1997                .flat_map(|id| state.pack_item_update(id, Diff::ONE)),
1998        );
1999
2000        builtin_table_updates
2001    }
2002
2003    /// Associates a name, `CatalogItemId`, and entry.
2004    fn insert_entry(&mut self, entry: CatalogEntry) {
2005        if !entry.id.is_system() {
2006            if let Some(cluster_id) = entry.item.cluster_id() {
2007                self.clusters_by_id
2008                    .get_mut(&cluster_id)
2009                    .expect("catalog out of sync")
2010                    .bound_objects
2011                    .insert(entry.id);
2012            };
2013        }
2014
2015        for u in entry.references().items() {
2016            match self.entry_by_id.get_mut(u) {
2017                Some(metadata) => metadata.referenced_by.push(entry.id()),
2018                None => panic!(
2019                    "Catalog: missing dependent catalog item {} while installing {}",
2020                    &u,
2021                    self.resolve_full_name(entry.name(), entry.conn_id())
2022                ),
2023            }
2024        }
2025        for u in entry.uses() {
2026            // Ignore self for self-referential tasks (e.g. Continual Tasks), if
2027            // present.
2028            if u == entry.id() {
2029                continue;
2030            }
2031            match self.entry_by_id.get_mut(&u) {
2032                Some(metadata) => metadata.used_by.push(entry.id()),
2033                None => panic!(
2034                    "Catalog: missing dependent catalog item {} while installing {}",
2035                    &u,
2036                    self.resolve_full_name(entry.name(), entry.conn_id())
2037                ),
2038            }
2039        }
2040        for gid in entry.item.global_ids() {
2041            self.entry_by_global_id.insert(gid, entry.id());
2042        }
2043        let conn_id = entry.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
2044        // Lazily create the temporary schema if this is a temporary item and the schema
2045        // doesn't exist yet.
2046        if entry.name().qualifiers.schema_spec == SchemaSpecifier::Temporary
2047            && !self.temporary_schemas.contains_key(conn_id)
2048        {
2049            self.create_temporary_schema(conn_id, entry.owner_id)
2050                .expect("failed to create temporary schema");
2051        }
2052        let schema = self.get_schema_mut(
2053            &entry.name().qualifiers.database_spec,
2054            &entry.name().qualifiers.schema_spec,
2055            conn_id,
2056        );
2057
2058        let prev_id = match entry.item() {
2059            CatalogItem::Func(_) => schema
2060                .functions
2061                .insert(entry.name().item.clone(), entry.id()),
2062            CatalogItem::Type(_) => schema.types.insert(entry.name().item.clone(), entry.id()),
2063            _ => schema.items.insert(entry.name().item.clone(), entry.id()),
2064        };
2065
2066        assert!(
2067            prev_id.is_none(),
2068            "builtin name collision on {:?}",
2069            entry.name().item.clone()
2070        );
2071
2072        self.entry_by_id.insert(entry.id(), entry.clone());
2073    }
2074
2075    /// Associates a name, [`CatalogItemId`], and entry.
2076    fn insert_item(
2077        &mut self,
2078        id: CatalogItemId,
2079        oid: u32,
2080        name: QualifiedItemName,
2081        item: CatalogItem,
2082        owner_id: RoleId,
2083        privileges: PrivilegeMap,
2084    ) {
2085        let entry = CatalogEntry {
2086            item,
2087            name,
2088            id,
2089            oid,
2090            used_by: Vec::new(),
2091            referenced_by: Vec::new(),
2092            owner_id,
2093            privileges,
2094        };
2095
2096        self.insert_entry(entry);
2097    }
2098
2099    #[mz_ore::instrument(level = "trace")]
2100    fn drop_item(&mut self, id: CatalogItemId) -> CatalogEntry {
2101        let metadata = self.entry_by_id.remove(&id).expect("catalog out of sync");
2102        for u in metadata.references().items() {
2103            if let Some(dep_metadata) = self.entry_by_id.get_mut(u) {
2104                dep_metadata.referenced_by.retain(|u| *u != metadata.id())
2105            }
2106        }
2107        for u in metadata.uses() {
2108            if let Some(dep_metadata) = self.entry_by_id.get_mut(&u) {
2109                dep_metadata.used_by.retain(|u| *u != metadata.id())
2110            }
2111        }
2112        for gid in metadata.global_ids() {
2113            self.entry_by_global_id.remove(&gid);
2114        }
2115
2116        let conn_id = metadata.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
2117        let schema = self.get_schema_mut(
2118            &metadata.name().qualifiers.database_spec,
2119            &metadata.name().qualifiers.schema_spec,
2120            conn_id,
2121        );
2122        if metadata.item_type() == CatalogItemType::Type {
2123            schema
2124                .types
2125                .remove(&metadata.name().item)
2126                .expect("catalog out of sync");
2127        } else {
2128            // Functions would need special handling, but we don't yet support
2129            // dropping functions.
2130            assert_ne!(metadata.item_type(), CatalogItemType::Func);
2131
2132            schema
2133                .items
2134                .remove(&metadata.name().item)
2135                .expect("catalog out of sync");
2136        };
2137
2138        if !id.is_system() {
2139            if let Some(cluster_id) = metadata.item().cluster_id() {
2140                assert!(
2141                    self.clusters_by_id
2142                        .get_mut(&cluster_id)
2143                        .expect("catalog out of sync")
2144                        .bound_objects
2145                        .remove(&id),
2146                    "catalog out of sync"
2147                );
2148            }
2149        }
2150
2151        metadata
2152    }
2153
2154    fn insert_introspection_source_index(
2155        &mut self,
2156        cluster_id: ClusterId,
2157        log: &'static BuiltinLog,
2158        item_id: CatalogItemId,
2159        global_id: GlobalId,
2160        oid: u32,
2161    ) {
2162        let (index_name, index) =
2163            self.create_introspection_source_index(cluster_id, log, global_id);
2164        self.insert_item(
2165            item_id,
2166            oid,
2167            index_name,
2168            index,
2169            MZ_SYSTEM_ROLE_ID,
2170            PrivilegeMap::default(),
2171        );
2172    }
2173
2174    fn create_introspection_source_index(
2175        &self,
2176        cluster_id: ClusterId,
2177        log: &'static BuiltinLog,
2178        global_id: GlobalId,
2179    ) -> (QualifiedItemName, CatalogItem) {
2180        let source_name = FullItemName {
2181            database: RawDatabaseSpecifier::Ambient,
2182            schema: log.schema.into(),
2183            item: log.name.into(),
2184        };
2185        let index_name = format!("{}_{}_primary_idx", log.name, cluster_id);
2186        let mut index_name = QualifiedItemName {
2187            qualifiers: ItemQualifiers {
2188                database_spec: ResolvedDatabaseSpecifier::Ambient,
2189                schema_spec: SchemaSpecifier::Id(self.get_mz_introspection_schema_id()),
2190            },
2191            item: index_name.clone(),
2192        };
2193        index_name = self.find_available_name(index_name, &SYSTEM_CONN_ID);
2194        let index_item_name = index_name.item.clone();
2195        let (log_item_id, log_global_id) = self.resolve_builtin_log(log);
2196        let index = CatalogItem::Index(Index {
2197            global_id,
2198            on: log_global_id,
2199            keys: log
2200                .variant
2201                .index_by()
2202                .into_iter()
2203                .map(MirScalarExpr::column)
2204                .collect(),
2205            create_sql: index_sql(
2206                index_item_name,
2207                cluster_id,
2208                source_name,
2209                &log.variant.desc(),
2210                &log.variant.index_by(),
2211            ),
2212            conn_id: None,
2213            resolved_ids: [(log_item_id, log_global_id)].into_iter().collect(),
2214            cluster_id,
2215            is_retained_metrics_object: false,
2216            custom_logical_compaction_window: None,
2217            optimized_plan: None,
2218            physical_plan: None,
2219            dataflow_metainfo: None,
2220        });
2221        (index_name, index)
2222    }
2223
2224    /// Insert system configuration `name` with `value`.
2225    ///
2226    /// Return a `bool` value indicating whether the configuration was modified
2227    /// by the call.
2228    fn insert_system_configuration(&mut self, name: &str, value: VarInput) -> Result<bool, Error> {
2229        Ok(Arc::make_mut(&mut self.system_configuration).set(name, value)?)
2230    }
2231
2232    /// Reset system configuration `name`.
2233    ///
2234    /// Return a `bool` value indicating whether the configuration was modified
2235    /// by the call.
2236    fn remove_system_configuration(&mut self, name: &str) -> Result<bool, Error> {
2237        Ok(Arc::make_mut(&mut self.system_configuration).reset(name)?)
2238    }
2239}
2240
2241/// Sort [`StateUpdate`]s in dependency order.
2242///
2243/// # Panics
2244///
2245/// This function assumes that all provided `updates` have the same timestamp and will panic
2246/// otherwise. It also requires that the provided `updates` are consolidated, i.e. all contained
2247/// `StateUpdateKinds` are unique.
2248fn sort_updates(updates: Vec<StateUpdate>) -> Vec<StateUpdate> {
2249    fn push_update<T>(
2250        update: T,
2251        diff: StateDiff,
2252        retractions: &mut Vec<T>,
2253        additions: &mut Vec<T>,
2254    ) {
2255        match diff {
2256            StateDiff::Retraction => retractions.push(update),
2257            StateDiff::Addition => additions.push(update),
2258        }
2259    }
2260
2261    soft_assert_no_log!(
2262        updates.iter().map(|update| update.ts).all_equal(),
2263        "all timestamps should be equal: {updates:?}"
2264    );
2265    soft_assert_no_log!(
2266        {
2267            let mut dedup = BTreeSet::new();
2268            updates.iter().all(|update| dedup.insert(&update.kind))
2269        },
2270        "updates should be consolidated: {updates:?}"
2271    );
2272
2273    // Partition updates by type so that we can weave different update types into the right spots.
2274    let mut pre_cluster_retractions = Vec::new();
2275    let mut pre_cluster_additions = Vec::new();
2276    let mut cluster_retractions = Vec::new();
2277    let mut cluster_additions = Vec::new();
2278    let mut builtin_item_updates = Vec::new();
2279    let mut item_retractions = Vec::new();
2280    let mut item_additions = Vec::new();
2281    let mut temp_item_retractions = Vec::new();
2282    let mut temp_item_additions = Vec::new();
2283    let mut post_item_retractions = Vec::new();
2284    let mut post_item_additions = Vec::new();
2285    for update in updates {
2286        let diff = update.diff.clone();
2287        match update.kind {
2288            StateUpdateKind::Role(_)
2289            | StateUpdateKind::RoleAuth(_)
2290            | StateUpdateKind::Database(_)
2291            | StateUpdateKind::Schema(_)
2292            | StateUpdateKind::DefaultPrivilege(_)
2293            | StateUpdateKind::SystemPrivilege(_)
2294            | StateUpdateKind::SystemConfiguration(_)
2295            | StateUpdateKind::NetworkPolicy(_) => push_update(
2296                update,
2297                diff,
2298                &mut pre_cluster_retractions,
2299                &mut pre_cluster_additions,
2300            ),
2301            StateUpdateKind::Cluster(_)
2302            | StateUpdateKind::ClusterSystemConfiguration(_)
2303            | StateUpdateKind::IntrospectionSourceIndex(_)
2304            | StateUpdateKind::ClusterReplica(_)
2305            | StateUpdateKind::ReplicaSystemConfiguration(_) => push_update(
2306                update,
2307                diff,
2308                &mut cluster_retractions,
2309                &mut cluster_additions,
2310            ),
2311            StateUpdateKind::SystemObjectMapping(system_object_mapping) => {
2312                builtin_item_updates.push((system_object_mapping, update.ts, update.diff))
2313            }
2314            StateUpdateKind::TemporaryItem(item) => push_update(
2315                (item, update.ts, update.diff),
2316                diff,
2317                &mut temp_item_retractions,
2318                &mut temp_item_additions,
2319            ),
2320            StateUpdateKind::Item(item) => push_update(
2321                (item, update.ts, update.diff),
2322                diff,
2323                &mut item_retractions,
2324                &mut item_additions,
2325            ),
2326            StateUpdateKind::Comment(_)
2327            | StateUpdateKind::SourceReferences(_)
2328            | StateUpdateKind::AuditLog(_)
2329            | StateUpdateKind::StorageCollectionMetadata(_)
2330            | StateUpdateKind::UnfinalizedShard(_) => push_update(
2331                update,
2332                diff,
2333                &mut post_item_retractions,
2334                &mut post_item_additions,
2335            ),
2336        }
2337    }
2338
2339    // Sort builtin item updates by dependency. The builtin definition order must be a dependency
2340    // order, so we can use that to avoid a more expensive topo sorting.
2341    let builtin_item_updates = builtin_item_updates
2342        .into_iter()
2343        .map(|(system_object_mapping, ts, diff)| {
2344            let idx = BUILTIN_LOOKUP
2345                .get(&system_object_mapping.description)
2346                .expect("missing builtin")
2347                .0;
2348            (idx, system_object_mapping, ts, diff)
2349        })
2350        .sorted_by_key(|(idx, _, _, _)| *idx)
2351        .map(|(_, system_object_mapping, ts, diff)| (system_object_mapping, ts, diff));
2352
2353    // Partition builtins based on whether or not they should be applied before or after clusters.
2354    // Clusters depend on sources (introspection logs), so sources need to be applied first.
2355    // Everything else can be applied after clusters.
2356    let mut builtin_source_retractions = Vec::new();
2357    let mut builtin_source_additions = Vec::new();
2358    let mut other_builtin_retractions = Vec::new();
2359    let mut other_builtin_additions = Vec::new();
2360    for (builtin_item_update, ts, diff) in builtin_item_updates {
2361        let object_type = builtin_item_update.description.object_type;
2362        let update = StateUpdate {
2363            kind: StateUpdateKind::SystemObjectMapping(builtin_item_update),
2364            ts,
2365            diff,
2366        };
2367        if object_type == CatalogItemType::Source {
2368            push_update(
2369                update,
2370                diff,
2371                &mut builtin_source_retractions,
2372                &mut builtin_source_additions,
2373            );
2374        } else {
2375            push_update(
2376                update,
2377                diff,
2378                &mut other_builtin_retractions,
2379                &mut other_builtin_additions,
2380            );
2381        }
2382    }
2383
2384    /// Sort items by their dependencies using topological sort.
2385    ///
2386    /// # Panics
2387    ///
2388    /// This function requires that all provided items have unique item IDs.
2389    fn sort_items_topological(items: &mut Vec<(mz_catalog::durable::Item, Timestamp, StateDiff)>) {
2390        tracing::debug!(?items, "sorting items by dependencies");
2391
2392        let key_fn = |item: &(mz_catalog::durable::Item, _, _)| item.0.id;
2393        let dependencies_fn = |item: &(mz_catalog::durable::Item, _, _)| {
2394            let statement = mz_sql::parse::parse(&item.0.create_sql)
2395                .expect("valid create_sql")
2396                .into_element()
2397                .ast;
2398            mz_sql::names::dependencies(&statement).expect("failed to find dependencies of item")
2399        };
2400        sort_topological(items, key_fn, dependencies_fn);
2401    }
2402
2403    /// Sort item updates by dependency.
2404    ///
2405    /// First we group items into groups that are totally ordered by dependency. For example, when
2406    /// sorting all items by dependency we know that all tables can come after all sources, because
2407    /// a source can never depend on a table. Second, we sort the items in each group in
2408    /// topological order, or by ID, depending on the type.
2409    ///
2410    /// It used to be the case that the ID order of ALL items matched the dependency order. However,
2411    /// certain migrations shuffled item IDs around s.t. this was no longer true. A much better
2412    /// approach would be to investigate each item, discover their exact dependencies, and then
2413    /// perform a topological sort. This is non-trivial because we only have the CREATE SQL of each
2414    /// item here. Within the SQL the dependent items are sometimes referred to by ID and sometimes
2415    /// referred to by name.
2416    ///
2417    /// The logic of this function should match [`sort_temp_item_updates`].
2418    fn sort_item_updates(
2419        item_updates: Vec<(mz_catalog::durable::Item, Timestamp, StateDiff)>,
2420    ) -> VecDeque<(mz_catalog::durable::Item, Timestamp, StateDiff)> {
2421        // Partition items into groups s.t. each item in one group has a predefined order with all
2422        // items in other groups. For example, all sinks are ordered greater than all tables.
2423        let mut types = Vec::new();
2424        // N.B. Functions can depend on system tables, but not user tables.
2425        // TODO(udf): This will change when UDFs are supported.
2426        let mut funcs = Vec::new();
2427        let mut secrets = Vec::new();
2428        let mut connections = Vec::new();
2429        let mut sources = Vec::new();
2430        let mut tables = Vec::new();
2431        let mut derived_items = Vec::new();
2432        let mut sinks = Vec::new();
2433        for update in item_updates {
2434            match update.0.item_type() {
2435                CatalogItemType::Type => types.push(update),
2436                CatalogItemType::Func => funcs.push(update),
2437                CatalogItemType::Secret => secrets.push(update),
2438                CatalogItemType::Connection => connections.push(update),
2439                CatalogItemType::Source => sources.push(update),
2440                CatalogItemType::Table => tables.push(update),
2441                CatalogItemType::View
2442                | CatalogItemType::MaterializedView
2443                | CatalogItemType::Index => derived_items.push(update),
2444                CatalogItemType::Sink => sinks.push(update),
2445            }
2446        }
2447
2448        // For some groups, the items in them can depend on each other and can be `ALTER`ed so that
2449        // an item ends up depending on an item with a greater ID. Thus we need to perform
2450        // topological sort for these groups.
2451        sort_items_topological(&mut connections);
2452        sort_items_topological(&mut derived_items);
2453
2454        // Other groups we can simply sort by ID.
2455        for group in [
2456            &mut types,
2457            &mut funcs,
2458            &mut secrets,
2459            &mut sources,
2460            &mut tables,
2461            &mut sinks,
2462        ] {
2463            group.sort_by_key(|(item, _, _)| item.id);
2464        }
2465
2466        iter::empty()
2467            .chain(types)
2468            .chain(funcs)
2469            .chain(secrets)
2470            .chain(connections)
2471            .chain(sources)
2472            .chain(tables)
2473            .chain(derived_items)
2474            .chain(sinks)
2475            .collect()
2476    }
2477
2478    let item_retractions = sort_item_updates(item_retractions);
2479    let item_additions = sort_item_updates(item_additions);
2480
2481    /// Sort temporary item updates by dependency.
2482    ///
2483    /// The logic of this function should match [`sort_item_updates`].
2484    fn sort_temp_item_updates(
2485        temp_item_updates: Vec<(TemporaryItem, Timestamp, StateDiff)>,
2486    ) -> VecDeque<(TemporaryItem, Timestamp, StateDiff)> {
2487        // Partition items into groups s.t. each item in one group has a predefined order with all
2488        // items in other groups. For example, all sinks are ordered greater than all tables.
2489        let mut types = Vec::new();
2490        // N.B. Functions can depend on system tables, but not user tables.
2491        let mut funcs = Vec::new();
2492        let mut secrets = Vec::new();
2493        let mut connections = Vec::new();
2494        let mut sources = Vec::new();
2495        let mut tables = Vec::new();
2496        let mut derived_items = Vec::new();
2497        let mut sinks = Vec::new();
2498        for update in temp_item_updates {
2499            match update.0.item_type() {
2500                CatalogItemType::Type => types.push(update),
2501                CatalogItemType::Func => funcs.push(update),
2502                CatalogItemType::Secret => secrets.push(update),
2503                CatalogItemType::Connection => connections.push(update),
2504                CatalogItemType::Source => sources.push(update),
2505                CatalogItemType::Table => tables.push(update),
2506                CatalogItemType::View
2507                | CatalogItemType::MaterializedView
2508                | CatalogItemType::Index => derived_items.push(update),
2509                CatalogItemType::Sink => sinks.push(update),
2510            }
2511        }
2512
2513        // Within each group, sort by ID.
2514        for group in [
2515            &mut types,
2516            &mut funcs,
2517            &mut secrets,
2518            &mut connections,
2519            &mut sources,
2520            &mut tables,
2521            &mut derived_items,
2522            &mut sinks,
2523        ] {
2524            group.sort_by_key(|(item, _, _)| item.id);
2525        }
2526
2527        iter::empty()
2528            .chain(types)
2529            .chain(funcs)
2530            .chain(secrets)
2531            .chain(connections)
2532            .chain(sources)
2533            .chain(tables)
2534            .chain(derived_items)
2535            .chain(sinks)
2536            .collect()
2537    }
2538    let temp_item_retractions = sort_temp_item_updates(temp_item_retractions);
2539    let temp_item_additions = sort_temp_item_updates(temp_item_additions);
2540
2541    /// Concatenate sorted persistent and temporary item updates, persistent
2542    /// first.
2543    ///
2544    /// Both inputs are already in dependency (type-group) order internally. A
2545    /// non-temporary item can never depend on a temporary one (enforced at
2546    /// creation, see `ErrorKind::InvalidTemporaryDependency`), so emitting every
2547    /// persistent item before every temporary item keeps dependencies ahead of
2548    /// dependents for additions. (Retractions reuse this via a reversal by the
2549    /// caller, which puts temporary items first. Also, a retraction only drops
2550    /// the item and does not re-resolve `create_sql`.)
2551    ///
2552    /// NOTE: Do not interleave the two by raw id. The inputs are ordered by
2553    /// dependency group, which is not id order, so an id merge can place a
2554    /// temporary dependent ahead of the persistent item it references and panic
2555    /// apply with an unresolvable id.
2556    fn merge_item_updates(
2557        item_updates: VecDeque<(mz_catalog::durable::Item, Timestamp, StateDiff)>,
2558        temp_item_updates: VecDeque<(TemporaryItem, Timestamp, StateDiff)>,
2559    ) -> Vec<StateUpdate> {
2560        let mut state_updates = Vec::with_capacity(item_updates.len() + temp_item_updates.len());
2561        for (item, ts, diff) in item_updates {
2562            state_updates.push(StateUpdate {
2563                kind: StateUpdateKind::Item(item),
2564                ts,
2565                diff,
2566            });
2567        }
2568        for (temp_item, ts, diff) in temp_item_updates {
2569            state_updates.push(StateUpdate {
2570                kind: StateUpdateKind::TemporaryItem(temp_item),
2571                ts,
2572                diff,
2573            });
2574        }
2575        state_updates
2576    }
2577    let item_retractions = merge_item_updates(item_retractions, temp_item_retractions);
2578    let item_additions = merge_item_updates(item_additions, temp_item_additions);
2579
2580    // Put everything back together.
2581    iter::empty()
2582        // All retractions must be reversed.
2583        .chain(post_item_retractions.into_iter().rev())
2584        .chain(item_retractions.into_iter().rev())
2585        .chain(other_builtin_retractions.into_iter().rev())
2586        .chain(cluster_retractions.into_iter().rev())
2587        .chain(builtin_source_retractions.into_iter().rev())
2588        .chain(pre_cluster_retractions.into_iter().rev())
2589        .chain(pre_cluster_additions)
2590        .chain(builtin_source_additions)
2591        .chain(cluster_additions)
2592        .chain(other_builtin_additions)
2593        .chain(item_additions)
2594        .chain(post_item_additions)
2595        .collect()
2596}
2597
2598/// Groups of updates of certain types are applied in batches to improve
2599/// performance. A constraint is that updates must be applied in order. This
2600/// process is modeled as a state machine that batches then applies groups of
2601/// updates.
2602enum ApplyState {
2603    /// Additions of builtin views.
2604    BuiltinViewAdditions(Vec<(&'static BuiltinView, CatalogItemId, GlobalId)>),
2605    /// Item updates that aren't builtin view additions.
2606    ///
2607    /// This contains all updates whose application requires calling
2608    /// `parse_item` and thus toggling the `enable_for_item_parsing` feature
2609    /// flags.
2610    Items(Vec<StateUpdate>),
2611    /// All other updates.
2612    Updates(Vec<StateUpdate>),
2613}
2614
2615impl ApplyState {
2616    fn new(update: StateUpdate) -> Self {
2617        use StateUpdateKind::*;
2618        match &update.kind {
2619            SystemObjectMapping(som)
2620                if som.description.object_type == CatalogItemType::View
2621                    && update.diff == StateDiff::Addition =>
2622            {
2623                let view_addition = lookup_builtin_view_addition(som.clone());
2624                Self::BuiltinViewAdditions(vec![view_addition])
2625            }
2626
2627            IntrospectionSourceIndex(_) | SystemObjectMapping(_) | TemporaryItem(_) | Item(_) => {
2628                Self::Items(vec![update])
2629            }
2630
2631            Role(_)
2632            | RoleAuth(_)
2633            | Database(_)
2634            | Schema(_)
2635            | DefaultPrivilege(_)
2636            | SystemPrivilege(_)
2637            | SystemConfiguration(_)
2638            | ClusterSystemConfiguration(_)
2639            | ReplicaSystemConfiguration(_)
2640            | Cluster(_)
2641            | NetworkPolicy(_)
2642            | ClusterReplica(_)
2643            | SourceReferences(_)
2644            | Comment(_)
2645            | AuditLog(_)
2646            | StorageCollectionMetadata(_)
2647            | UnfinalizedShard(_) => Self::Updates(vec![update]),
2648        }
2649    }
2650
2651    /// Apply all updates that have been batched in `self`.
2652    ///
2653    /// We make sure to enable all "enable_for_item_parsing" feature flags when applying item
2654    /// updates during bootstrap. See [`CatalogState::with_enable_for_item_parsing`] for more
2655    /// details.
2656    async fn apply(
2657        self,
2658        state: &mut CatalogState,
2659        retractions: &mut InProgressRetractions,
2660        local_expression_cache: &mut LocalExpressionCache,
2661    ) -> (
2662        Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
2663        Vec<ParsedStateUpdate>,
2664    ) {
2665        match self {
2666            Self::BuiltinViewAdditions(builtin_view_additions) => {
2667                let restore = Arc::clone(&state.system_configuration);
2668                Arc::make_mut(&mut state.system_configuration).enable_for_item_parsing();
2669                let builtin_table_updates = CatalogState::parse_builtin_views(
2670                    state,
2671                    builtin_view_additions,
2672                    retractions,
2673                    local_expression_cache,
2674                )
2675                .await;
2676                state.system_configuration = restore;
2677                (builtin_table_updates, Vec::new())
2678            }
2679            Self::Items(updates) => state.with_enable_for_item_parsing(|state| {
2680                state
2681                    .apply_updates_inner(updates, retractions, local_expression_cache)
2682                    .expect("corrupt catalog")
2683            }),
2684            Self::Updates(updates) => state
2685                .apply_updates_inner(updates, retractions, local_expression_cache)
2686                .expect("corrupt catalog"),
2687        }
2688    }
2689
2690    async fn step(
2691        self,
2692        next: Self,
2693        state: &mut CatalogState,
2694        retractions: &mut InProgressRetractions,
2695        local_expression_cache: &mut LocalExpressionCache,
2696    ) -> (
2697        Self,
2698        (
2699            Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
2700            Vec<ParsedStateUpdate>,
2701        ),
2702    ) {
2703        match (self, next) {
2704            (
2705                Self::BuiltinViewAdditions(mut builtin_view_additions),
2706                Self::BuiltinViewAdditions(next_builtin_view_additions),
2707            ) => {
2708                // Continue batching builtin view additions.
2709                builtin_view_additions.extend(next_builtin_view_additions);
2710                (
2711                    Self::BuiltinViewAdditions(builtin_view_additions),
2712                    (Vec::new(), Vec::new()),
2713                )
2714            }
2715            (Self::Items(mut updates), Self::Items(next_updates)) => {
2716                // Continue batching item updates.
2717                updates.extend(next_updates);
2718                (Self::Items(updates), (Vec::new(), Vec::new()))
2719            }
2720            (Self::Updates(mut updates), Self::Updates(next_updates)) => {
2721                // Continue batching updates.
2722                updates.extend(next_updates);
2723                (Self::Updates(updates), (Vec::new(), Vec::new()))
2724            }
2725            (apply_state, next_apply_state) => {
2726                // Apply the current batch and start batching new apply state.
2727                let updates = apply_state
2728                    .apply(state, retractions, local_expression_cache)
2729                    .await;
2730                (next_apply_state, updates)
2731            }
2732        }
2733    }
2734}
2735
2736/// Trait abstracting over map operations needed by [`apply_inverted_lookup`] and
2737/// [`apply_with_update`]. Both [`BTreeMap`] and [`imbl::OrdMap`] implement this.
2738trait MutableMap<K, V> {
2739    fn insert(&mut self, key: K, value: V) -> Option<V>;
2740    fn remove(&mut self, key: &K) -> Option<V>;
2741}
2742
2743impl<K: Ord, V> MutableMap<K, V> for BTreeMap<K, V> {
2744    fn insert(&mut self, key: K, value: V) -> Option<V> {
2745        BTreeMap::insert(self, key, value)
2746    }
2747    fn remove(&mut self, key: &K) -> Option<V> {
2748        BTreeMap::remove(self, key)
2749    }
2750}
2751
2752impl<K: Ord + Clone, V: Clone> MutableMap<K, V> for imbl::OrdMap<K, V> {
2753    fn insert(&mut self, key: K, value: V) -> Option<V> {
2754        imbl::OrdMap::insert(self, key, value)
2755    }
2756    fn remove(&mut self, key: &K) -> Option<V> {
2757        imbl::OrdMap::remove(self, key)
2758    }
2759}
2760
2761/// Helper method to updated inverted lookup maps. The keys are generally names and the values are
2762/// generally IDs.
2763///
2764/// Importantly, when retracting it's expected that the existing value will match `value` exactly.
2765fn apply_inverted_lookup<K, V>(map: &mut impl MutableMap<K, V>, key: &K, value: V, diff: StateDiff)
2766where
2767    K: Ord + Clone + Debug,
2768    V: PartialEq + Debug,
2769{
2770    match diff {
2771        StateDiff::Retraction => {
2772            let prev = map.remove(key);
2773            assert_eq!(
2774                prev,
2775                Some(value),
2776                "retraction does not match existing value: {key:?}"
2777            );
2778        }
2779        StateDiff::Addition => {
2780            let prev = map.insert(key.clone(), value);
2781            assert_eq!(
2782                prev, None,
2783                "values must be explicitly retracted before inserting a new value: {key:?}"
2784            );
2785        }
2786    }
2787}
2788
2789/// Helper method to update catalog state, that may need to be updated from a previously retracted
2790/// object.
2791fn apply_with_update<K, V, D>(
2792    map: &mut impl MutableMap<K, V>,
2793    durable: D,
2794    key_fn: impl FnOnce(&D) -> K,
2795    diff: StateDiff,
2796    retractions: &mut BTreeMap<D::Key, V>,
2797) where
2798    K: Ord,
2799    V: UpdateFrom<D> + PartialEq + Debug,
2800    D: DurableType,
2801    D::Key: Ord,
2802{
2803    match diff {
2804        StateDiff::Retraction => {
2805            let mem_key = key_fn(&durable);
2806            let value = map
2807                .remove(&mem_key)
2808                .expect("retraction does not match existing value: {key:?}");
2809            let durable_key = durable.into_key_value().0;
2810            retractions.insert(durable_key, value);
2811        }
2812        StateDiff::Addition => {
2813            let mem_key = key_fn(&durable);
2814            let durable_key = durable.key();
2815            let value = match retractions.remove(&durable_key) {
2816                Some(mut retraction) => {
2817                    retraction.update_from(durable);
2818                    retraction
2819                }
2820                None => durable.into(),
2821            };
2822            let prev = map.insert(mem_key, value);
2823            assert_eq!(
2824                prev, None,
2825                "values must be explicitly retracted before inserting a new value"
2826            );
2827        }
2828    }
2829}
2830
2831/// Looks up a [`BuiltinView`] from a [`SystemObjectMapping`].
2832fn lookup_builtin_view_addition(
2833    mapping: SystemObjectMapping,
2834) -> (&'static BuiltinView, CatalogItemId, GlobalId) {
2835    let (_, builtin) = BUILTIN_LOOKUP
2836        .get(&mapping.description)
2837        .expect("missing builtin view");
2838    let Builtin::View(view) = builtin else {
2839        unreachable!("programming error, expected BuiltinView found {builtin:?}");
2840    };
2841
2842    (
2843        view,
2844        mapping.unique_identifier.catalog_id,
2845        mapping.unique_identifier.global_id,
2846    )
2847}