Skip to main content

mz_catalog/memory/
objects.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//! The current types used by the in-memory Catalog. Many of the objects in this module are
11//! extremely similar to the objects found in [`crate::durable::objects`] but in a format that is
12//! easier consumed by higher layers.
13
14use std::borrow::Cow;
15use std::collections::{BTreeMap, BTreeSet};
16use std::ops::{Deref, DerefMut};
17use std::sync::{Arc, LazyLock};
18use std::time::Duration;
19
20use chrono::{DateTime, Utc};
21use mz_adapter_types::compaction::CompactionWindow;
22use mz_adapter_types::connection::ConnectionId;
23use mz_compute_client::logging::LogVariant;
24use mz_compute_types::dataflows::DataflowDescription;
25use mz_compute_types::plan::LirRelationExpr as ComputePlan;
26use mz_controller::clusters::{ClusterRole, ClusterStatus, ReplicaConfig, ReplicaLogging};
27use mz_controller_types::{ClusterId, ReplicaId};
28use mz_expr::{MirScalarExpr, OptimizedMirRelationExpr};
29use mz_ore::collections::CollectionExt;
30use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
31use mz_repr::network_policy_id::NetworkPolicyId;
32use mz_repr::optimize::OptimizerFeatureOverrides;
33use mz_repr::refresh_schedule::RefreshSchedule;
34use mz_repr::role_id::RoleId;
35use mz_repr::{
36    CatalogItemId, ColumnName, Diff, GlobalId, RelationDesc, RelationVersion,
37    RelationVersionSelector, SqlColumnType, Timestamp, VersionedRelationDesc,
38};
39use mz_sql::ast::display::AstDisplay;
40use mz_sql::ast::{
41    ColumnDef, ColumnOption, ColumnOptionDef, ColumnVersioned, Expr, Raw, RawDataType, Statement,
42    UnresolvedItemName, Value, WithOptionValue,
43};
44use mz_sql::catalog::{
45    CatalogClusterReplica, CatalogError as SqlCatalogError, CatalogItem as SqlCatalogItem,
46    CatalogItemType as SqlCatalogItemType, CatalogItemType, CatalogSchema, CatalogType,
47    CatalogTypeDetails, DefaultPrivilegeAclItem, DefaultPrivilegeObject, IdReference,
48    RoleAttributes, RoleMembership, RoleVars, SystemObjectType,
49};
50use mz_sql::names::{
51    Aug, CommentObjectId, DatabaseId, DependencyIds, FullItemName, QualifiedItemName,
52    QualifiedSchemaName, ResolvedDatabaseSpecifier, ResolvedIds, SchemaId, SchemaSpecifier,
53};
54use mz_sql::plan::{
55    AutoScalingStrategy, ClusterSchedule, ComputeReplicaConfig, ComputeReplicaIntrospectionConfig,
56    ConnectionDetails, CreateClusterManagedPlan, CreateClusterPlan, CreateClusterVariant,
57    CreateSourcePlan, HirRelationExpr, NetworkPolicyRule, OnTimeoutAction, PlanError,
58    WebhookBodyFormat, WebhookHeaders, WebhookValidation,
59};
60use mz_sql::rbac;
61use mz_sql::session::vars::OwnedVarInput;
62use mz_storage_client::controller::IntrospectionType;
63use mz_storage_types::connections::inline::ReferencedConnection;
64use mz_storage_types::sinks::{SinkEnvelope, StorageSinkConnection};
65use mz_storage_types::sources::load_generator::LoadGenerator;
66use mz_storage_types::sources::{
67    GenericSourceConnection, SourceConnection, SourceDesc, SourceEnvelope, SourceExportDataConfig,
68    SourceExportDetails, Timeline,
69};
70use mz_transform::dataflow::DataflowMetainfo;
71use mz_transform::notice::OptimizerNotice;
72use serde::ser::SerializeSeq;
73use serde::{Deserialize, Serialize};
74use timely::progress::Antichain;
75use tracing::debug;
76
77use crate::builtin::{MZ_CATALOG_SERVER_CLUSTER, MZ_SYSTEM_CLUSTER};
78use crate::durable;
79use crate::durable::objects::item_type;
80
81/// Used to update `self` from the input value while consuming the input value.
82pub trait UpdateFrom<T>: From<T> {
83    fn update_from(&mut self, from: T);
84}
85
86#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
87pub struct Database {
88    pub name: String,
89    pub id: DatabaseId,
90    pub oid: u32,
91    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
92    pub schemas_by_id: BTreeMap<SchemaId, Schema>,
93    pub schemas_by_name: BTreeMap<String, SchemaId>,
94    pub owner_id: RoleId,
95    pub privileges: PrivilegeMap,
96}
97
98impl From<Database> for durable::Database {
99    fn from(database: Database) -> durable::Database {
100        durable::Database {
101            id: database.id,
102            oid: database.oid,
103            name: database.name,
104            owner_id: database.owner_id,
105            privileges: database.privileges.into_all_values().collect(),
106        }
107    }
108}
109
110impl From<durable::Database> for Database {
111    fn from(
112        durable::Database {
113            id,
114            oid,
115            name,
116            owner_id,
117            privileges,
118        }: durable::Database,
119    ) -> Database {
120        Database {
121            id,
122            oid,
123            schemas_by_id: BTreeMap::new(),
124            schemas_by_name: BTreeMap::new(),
125            name,
126            owner_id,
127            privileges: PrivilegeMap::from_mz_acl_items(privileges),
128        }
129    }
130}
131
132impl UpdateFrom<durable::Database> for Database {
133    fn update_from(
134        &mut self,
135        durable::Database {
136            id,
137            oid,
138            name,
139            owner_id,
140            privileges,
141        }: durable::Database,
142    ) {
143        self.id = id;
144        self.oid = oid;
145        self.name = name;
146        self.owner_id = owner_id;
147        self.privileges = PrivilegeMap::from_mz_acl_items(privileges);
148    }
149}
150
151#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
152pub struct Schema {
153    pub name: QualifiedSchemaName,
154    pub id: SchemaSpecifier,
155    pub oid: u32,
156    pub items: BTreeMap<String, CatalogItemId>,
157    pub functions: BTreeMap<String, CatalogItemId>,
158    pub types: BTreeMap<String, CatalogItemId>,
159    pub owner_id: RoleId,
160    pub privileges: PrivilegeMap,
161}
162
163impl From<Schema> for durable::Schema {
164    fn from(schema: Schema) -> durable::Schema {
165        durable::Schema {
166            id: schema.id.into(),
167            oid: schema.oid,
168            name: schema.name.schema,
169            database_id: schema.name.database.id(),
170            owner_id: schema.owner_id,
171            privileges: schema.privileges.into_all_values().collect(),
172        }
173    }
174}
175
176impl From<durable::Schema> for Schema {
177    fn from(
178        durable::Schema {
179            id,
180            oid,
181            name,
182            database_id,
183            owner_id,
184            privileges,
185        }: durable::Schema,
186    ) -> Schema {
187        Schema {
188            name: QualifiedSchemaName {
189                database: database_id.into(),
190                schema: name,
191            },
192            id: id.into(),
193            oid,
194            items: BTreeMap::new(),
195            functions: BTreeMap::new(),
196            types: BTreeMap::new(),
197            owner_id,
198            privileges: PrivilegeMap::from_mz_acl_items(privileges),
199        }
200    }
201}
202
203impl UpdateFrom<durable::Schema> for Schema {
204    fn update_from(
205        &mut self,
206        durable::Schema {
207            id,
208            oid,
209            name,
210            database_id,
211            owner_id,
212            privileges,
213        }: durable::Schema,
214    ) {
215        self.name = QualifiedSchemaName {
216            database: database_id.into(),
217            schema: name,
218        };
219        self.id = id.into();
220        self.oid = oid;
221        self.owner_id = owner_id;
222        self.privileges = PrivilegeMap::from_mz_acl_items(privileges);
223    }
224}
225
226#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
227pub struct Role {
228    pub name: String,
229    pub id: RoleId,
230    pub oid: u32,
231    pub attributes: RoleAttributes,
232    pub membership: RoleMembership,
233    pub vars: RoleVars,
234}
235
236impl Role {
237    pub fn is_user(&self) -> bool {
238        self.id.is_user()
239    }
240
241    pub fn vars<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a OwnedVarInput)> {
242        self.vars.map.iter().map(|(name, val)| (name.as_str(), val))
243    }
244}
245
246impl From<Role> for durable::Role {
247    fn from(role: Role) -> durable::Role {
248        durable::Role {
249            id: role.id,
250            oid: role.oid,
251            name: role.name,
252            attributes: role.attributes,
253            membership: role.membership,
254            vars: role.vars,
255        }
256    }
257}
258
259impl From<durable::Role> for Role {
260    fn from(
261        durable::Role {
262            id,
263            oid,
264            name,
265            attributes,
266            membership,
267            vars,
268        }: durable::Role,
269    ) -> Self {
270        Role {
271            name,
272            id,
273            oid,
274            attributes,
275            membership,
276            vars,
277        }
278    }
279}
280
281impl UpdateFrom<durable::Role> for Role {
282    fn update_from(
283        &mut self,
284        durable::Role {
285            id,
286            oid,
287            name,
288            attributes,
289            membership,
290            vars,
291        }: durable::Role,
292    ) {
293        self.id = id;
294        self.oid = oid;
295        self.name = name;
296        self.attributes = attributes;
297        self.membership = membership;
298        self.vars = vars;
299    }
300}
301
302#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
303pub struct RoleAuth {
304    pub role_id: RoleId,
305    pub password_hash: Option<String>,
306    pub updated_at: u64,
307}
308
309impl From<RoleAuth> for durable::RoleAuth {
310    fn from(role_auth: RoleAuth) -> durable::RoleAuth {
311        durable::RoleAuth {
312            role_id: role_auth.role_id,
313            password_hash: role_auth.password_hash,
314            updated_at: role_auth.updated_at,
315        }
316    }
317}
318
319impl From<durable::RoleAuth> for RoleAuth {
320    fn from(
321        durable::RoleAuth {
322            role_id,
323            password_hash,
324            updated_at,
325        }: durable::RoleAuth,
326    ) -> RoleAuth {
327        RoleAuth {
328            role_id,
329            password_hash,
330            updated_at,
331        }
332    }
333}
334
335impl UpdateFrom<durable::RoleAuth> for RoleAuth {
336    fn update_from(&mut self, from: durable::RoleAuth) {
337        self.role_id = from.role_id;
338        self.password_hash = from.password_hash;
339        self.updated_at = from.updated_at;
340    }
341}
342
343#[derive(Debug, Serialize, Clone, PartialEq)]
344pub struct Cluster {
345    pub name: String,
346    pub id: ClusterId,
347    pub config: ClusterConfig,
348    #[serde(skip)]
349    pub log_indexes: BTreeMap<LogVariant, GlobalId>,
350    /// Objects bound to this cluster. Does not include introspection source
351    /// indexes.
352    pub bound_objects: BTreeSet<CatalogItemId>,
353    pub replica_id_by_name_: BTreeMap<String, ReplicaId>,
354    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
355    pub replicas_by_id_: BTreeMap<ReplicaId, ClusterReplica>,
356    pub owner_id: RoleId,
357    pub privileges: PrivilegeMap,
358}
359
360impl Cluster {
361    /// The role of the cluster. Currently used to set alert severity.
362    pub fn role(&self) -> ClusterRole {
363        // NOTE - These roles power monitoring systems. Do not change
364        // them without talking to the cloud or observability groups.
365        if self.name == MZ_SYSTEM_CLUSTER.name {
366            ClusterRole::SystemCritical
367        } else if self.name == MZ_CATALOG_SERVER_CLUSTER.name {
368            ClusterRole::System
369        } else {
370            ClusterRole::User
371        }
372    }
373
374    /// Returns `true` if the cluster is a managed cluster.
375    pub fn is_managed(&self) -> bool {
376        matches!(self.config.variant, ClusterVariant::Managed { .. })
377    }
378
379    /// Lists the user replicas, which are those that do not have the internal flag set.
380    pub fn user_replicas(&self) -> impl Iterator<Item = &ClusterReplica> {
381        self.replicas().filter(|r| !r.config.location.internal())
382    }
383
384    /// Lists all replicas in the cluster
385    pub fn replicas(&self) -> impl Iterator<Item = &ClusterReplica> {
386        self.replicas_by_id_.values()
387    }
388
389    /// Lookup a replica by ID.
390    pub fn replica(&self, replica_id: ReplicaId) -> Option<&ClusterReplica> {
391        self.replicas_by_id_.get(&replica_id)
392    }
393
394    /// Lookup a replica ID by name.
395    pub fn replica_id(&self, name: &str) -> Option<ReplicaId> {
396        self.replica_id_by_name_.get(name).copied()
397    }
398
399    /// Returns the availability zones of this cluster, if they exist.
400    pub fn availability_zones(&self) -> Option<&[String]> {
401        match &self.config.variant {
402            ClusterVariant::Managed(managed) => Some(&managed.availability_zones),
403            ClusterVariant::Unmanaged => None,
404        }
405    }
406
407    pub fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError> {
408        let name = self.name.clone();
409        let variant = match &self.config.variant {
410            ClusterVariant::Managed(ClusterVariantManaged {
411                size,
412                availability_zones,
413                logging,
414                replication_factor,
415                optimizer_feature_overrides,
416                schedule,
417                // Not surfaced in the create plan: these are controller-managed
418                // runtime state and (for the policy) a separate SQL surface.
419                auto_scaling_strategy: _,
420                reconfiguration: _,
421                burst: _,
422            }) => {
423                let introspection = match logging {
424                    ReplicaLogging {
425                        log_logging,
426                        interval: Some(interval),
427                    } => Some(ComputeReplicaIntrospectionConfig {
428                        debugging: *log_logging,
429                        interval: interval.clone(),
430                    }),
431                    ReplicaLogging {
432                        log_logging: _,
433                        interval: None,
434                    } => None,
435                };
436                let compute = ComputeReplicaConfig { introspection };
437                CreateClusterVariant::Managed(CreateClusterManagedPlan {
438                    replication_factor: replication_factor.clone(),
439                    size: size.clone(),
440                    availability_zones: availability_zones.clone(),
441                    compute,
442                    optimizer_feature_overrides: optimizer_feature_overrides.clone(),
443                    schedule: schedule.clone(),
444                })
445            }
446            ClusterVariant::Unmanaged => {
447                // Unmanaged clusters are deprecated, so hopefully we can remove
448                // them before we have to implement this.
449                return Err(PlanError::Unsupported {
450                    feature: "SHOW CREATE for unmanaged clusters".to_string(),
451                    discussion_no: None,
452                });
453            }
454        };
455        let workload_class = self.config.workload_class.clone();
456        Ok(CreateClusterPlan {
457            name,
458            variant,
459            workload_class,
460        })
461    }
462}
463
464impl From<Cluster> for durable::Cluster {
465    fn from(cluster: Cluster) -> durable::Cluster {
466        durable::Cluster {
467            id: cluster.id,
468            name: cluster.name,
469            owner_id: cluster.owner_id,
470            privileges: cluster.privileges.into_all_values().collect(),
471            config: cluster.config.into(),
472        }
473    }
474}
475
476impl From<durable::Cluster> for Cluster {
477    fn from(
478        durable::Cluster {
479            id,
480            name,
481            owner_id,
482            privileges,
483            config,
484        }: durable::Cluster,
485    ) -> Self {
486        Cluster {
487            name: name.clone(),
488            id,
489            bound_objects: BTreeSet::new(),
490            log_indexes: BTreeMap::new(),
491            replica_id_by_name_: BTreeMap::new(),
492            replicas_by_id_: BTreeMap::new(),
493            owner_id,
494            privileges: PrivilegeMap::from_mz_acl_items(privileges),
495            config: config.into(),
496        }
497    }
498}
499
500impl UpdateFrom<durable::Cluster> for Cluster {
501    fn update_from(
502        &mut self,
503        durable::Cluster {
504            id,
505            name,
506            owner_id,
507            privileges,
508            config,
509        }: durable::Cluster,
510    ) {
511        self.id = id;
512        self.name = name;
513        self.owner_id = owner_id;
514        self.privileges = PrivilegeMap::from_mz_acl_items(privileges);
515        self.config = config.into();
516    }
517}
518
519#[derive(Debug, Serialize, Clone, PartialEq)]
520pub struct ClusterReplica {
521    pub name: String,
522    pub cluster_id: ClusterId,
523    pub replica_id: ReplicaId,
524    pub config: ReplicaConfig,
525    pub owner_id: RoleId,
526}
527
528impl From<ClusterReplica> for durable::ClusterReplica {
529    fn from(replica: ClusterReplica) -> durable::ClusterReplica {
530        durable::ClusterReplica {
531            cluster_id: replica.cluster_id,
532            replica_id: replica.replica_id,
533            name: replica.name,
534            config: replica.config.into(),
535            owner_id: replica.owner_id,
536        }
537    }
538}
539
540#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
541pub struct ClusterReplicaProcessStatus {
542    pub status: ClusterStatus,
543    /// Cumulative restart count of the process, mirrored from the orchestrator.
544    /// See [`mz_orchestrator::ServiceEvent::restart_count`].
545    pub restart_count: u64,
546    /// Time of the most recent change to `status` or `restart_count`.
547    pub time: DateTime<Utc>,
548}
549
550#[derive(Debug, Serialize, Clone, PartialEq)]
551pub struct SourceReferences {
552    pub updated_at: u64,
553    pub references: Vec<SourceReference>,
554}
555
556#[derive(Debug, Serialize, Clone, PartialEq)]
557pub struct SourceReference {
558    pub name: String,
559    pub namespace: Option<String>,
560    pub columns: Vec<String>,
561}
562
563impl From<SourceReference> for durable::SourceReference {
564    fn from(source_reference: SourceReference) -> durable::SourceReference {
565        durable::SourceReference {
566            name: source_reference.name,
567            namespace: source_reference.namespace,
568            columns: source_reference.columns,
569        }
570    }
571}
572
573impl SourceReferences {
574    pub fn to_durable(self, source_id: CatalogItemId) -> durable::SourceReferences {
575        durable::SourceReferences {
576            source_id,
577            updated_at: self.updated_at,
578            references: self.references.into_iter().map(Into::into).collect(),
579        }
580    }
581}
582
583impl From<durable::SourceReference> for SourceReference {
584    fn from(source_reference: durable::SourceReference) -> SourceReference {
585        SourceReference {
586            name: source_reference.name,
587            namespace: source_reference.namespace,
588            columns: source_reference.columns,
589        }
590    }
591}
592
593impl From<durable::SourceReferences> for SourceReferences {
594    fn from(source_references: durable::SourceReferences) -> SourceReferences {
595        SourceReferences {
596            updated_at: source_references.updated_at,
597            references: source_references
598                .references
599                .into_iter()
600                .map(|source_reference| source_reference.into())
601                .collect(),
602        }
603    }
604}
605
606impl From<mz_sql::plan::SourceReference> for SourceReference {
607    fn from(source_reference: mz_sql::plan::SourceReference) -> SourceReference {
608        SourceReference {
609            name: source_reference.name,
610            namespace: source_reference.namespace,
611            columns: source_reference.columns,
612        }
613    }
614}
615
616impl From<mz_sql::plan::SourceReferences> for SourceReferences {
617    fn from(source_references: mz_sql::plan::SourceReferences) -> SourceReferences {
618        SourceReferences {
619            updated_at: source_references.updated_at,
620            references: source_references
621                .references
622                .into_iter()
623                .map(|source_reference| source_reference.into())
624                .collect(),
625        }
626    }
627}
628
629impl From<SourceReferences> for mz_sql::plan::SourceReferences {
630    fn from(source_references: SourceReferences) -> mz_sql::plan::SourceReferences {
631        mz_sql::plan::SourceReferences {
632            updated_at: source_references.updated_at,
633            references: source_references
634                .references
635                .into_iter()
636                .map(|source_reference| source_reference.into())
637                .collect(),
638        }
639    }
640}
641
642impl From<SourceReference> for mz_sql::plan::SourceReference {
643    fn from(source_reference: SourceReference) -> mz_sql::plan::SourceReference {
644        mz_sql::plan::SourceReference {
645            name: source_reference.name,
646            namespace: source_reference.namespace,
647            columns: source_reference.columns,
648        }
649    }
650}
651
652#[derive(Clone, Debug, Serialize)]
653pub struct CatalogEntry {
654    pub item: CatalogItem,
655    #[serde(skip)]
656    pub referenced_by: Vec<CatalogItemId>,
657    // TODO(database-issues#7922)––this should have an invariant tied to it that all
658    // dependents (i.e. entries in this field) have IDs greater than this
659    // entry's ID.
660    #[serde(skip)]
661    pub used_by: Vec<CatalogItemId>,
662    pub id: CatalogItemId,
663    pub oid: u32,
664    pub name: QualifiedItemName,
665    pub owner_id: RoleId,
666    pub privileges: PrivilegeMap,
667}
668
669/// A [`CatalogEntry`] that is associated with a specific "collection" of data.
670/// A single item in the catalog may be associated with multiple "collections".
671///
672/// Here "collection" generally means a pTVC, e.g. a Persist Shard, an Index, a
673/// currently running dataflow, etc.
674///
675/// Items in the Catalog have a stable name -> ID mapping, in other words for
676/// the entire lifetime of an object its [`CatalogItemId`] will _never_ change.
677/// Similarly, we need to maintain a stable mapping from [`GlobalId`] to pTVC.
678/// This presents a challenge when `ALTER`-ing an object, e.g. adding columns
679/// to a table. We can't just change the schema of the underlying Persist Shard
680/// because that would be rebinding the [`GlobalId`] of the pTVC. Instead we
681/// allocate a new [`GlobalId`] to refer to the new version of the table, and
682/// then the [`CatalogEntry`] tracks the [`GlobalId`] for each version.
683#[derive(Clone, Debug)]
684pub struct CatalogCollectionEntry {
685    pub entry: CatalogEntry,
686    pub version: RelationVersionSelector,
687}
688
689impl CatalogCollectionEntry {
690    pub fn relation_desc(&self) -> Option<Cow<'_, RelationDesc>> {
691        self.item().relation_desc(self.version)
692    }
693}
694
695impl mz_sql::catalog::CatalogCollectionItem for CatalogCollectionEntry {
696    fn relation_desc(&self) -> Option<Cow<'_, RelationDesc>> {
697        self.item().relation_desc(self.version)
698    }
699
700    fn global_id(&self) -> GlobalId {
701        self.entry
702            .item()
703            .global_id_for_version(self.version)
704            .expect("catalog corruption, missing version!")
705    }
706}
707
708impl Deref for CatalogCollectionEntry {
709    type Target = CatalogEntry;
710
711    fn deref(&self) -> &CatalogEntry {
712        &self.entry
713    }
714}
715
716impl mz_sql::catalog::CatalogItem for CatalogCollectionEntry {
717    fn name(&self) -> &QualifiedItemName {
718        self.entry.name()
719    }
720
721    fn id(&self) -> CatalogItemId {
722        self.entry.id()
723    }
724
725    fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_> {
726        Box::new(self.entry.global_ids())
727    }
728
729    fn oid(&self) -> u32 {
730        self.entry.oid()
731    }
732
733    fn func(&self) -> Result<&'static mz_sql::func::Func, SqlCatalogError> {
734        self.entry.func()
735    }
736
737    fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, SqlCatalogError> {
738        self.entry.source_desc()
739    }
740
741    fn connection(
742        &self,
743    ) -> Result<mz_storage_types::connections::Connection<ReferencedConnection>, SqlCatalogError>
744    {
745        mz_sql::catalog::CatalogItem::connection(&self.entry)
746    }
747
748    fn create_sql(&self) -> &str {
749        self.entry.create_sql()
750    }
751
752    fn item_type(&self) -> SqlCatalogItemType {
753        self.entry.item_type()
754    }
755
756    fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)> {
757        self.entry.index_details()
758    }
759
760    fn writable_table_details(&self) -> Option<&[Expr<Aug>]> {
761        self.entry.writable_table_details()
762    }
763
764    fn replacement_target(&self) -> Option<CatalogItemId> {
765        self.entry.replacement_target()
766    }
767
768    fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>> {
769        self.entry.type_details()
770    }
771
772    fn references(&self) -> &ResolvedIds {
773        self.entry.references()
774    }
775
776    fn uses(&self) -> BTreeSet<CatalogItemId> {
777        self.entry.uses()
778    }
779
780    fn referenced_by(&self) -> &[CatalogItemId] {
781        self.entry.referenced_by()
782    }
783
784    fn used_by(&self) -> &[CatalogItemId] {
785        self.entry.used_by()
786    }
787
788    fn subsource_details(
789        &self,
790    ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)> {
791        self.entry.subsource_details()
792    }
793
794    fn source_export_details(
795        &self,
796    ) -> Option<(
797        CatalogItemId,
798        &UnresolvedItemName,
799        &SourceExportDetails,
800        &SourceExportDataConfig<ReferencedConnection>,
801    )> {
802        self.entry.source_export_details()
803    }
804
805    fn is_progress_source(&self) -> bool {
806        self.entry.is_progress_source()
807    }
808
809    fn progress_id(&self) -> Option<CatalogItemId> {
810        self.entry.progress_id()
811    }
812
813    fn owner_id(&self) -> RoleId {
814        *self.entry.owner_id()
815    }
816
817    fn privileges(&self) -> &PrivilegeMap {
818        self.entry.privileges()
819    }
820
821    fn cluster_id(&self) -> Option<ClusterId> {
822        self.entry.item().cluster_id()
823    }
824
825    fn at_version(
826        &self,
827        version: RelationVersionSelector,
828    ) -> Box<dyn mz_sql::catalog::CatalogCollectionItem> {
829        Box::new(CatalogCollectionEntry {
830            entry: self.entry.clone(),
831            version,
832        })
833    }
834
835    fn latest_version(&self) -> Option<RelationVersion> {
836        self.entry.latest_version()
837    }
838}
839
840#[derive(Debug, Clone, Serialize)]
841pub enum CatalogItem {
842    Table(Table),
843    Source(Source),
844    Log(Log),
845    View(View),
846    MaterializedView(MaterializedView),
847    Sink(Sink),
848    Index(Index),
849    Type(Type),
850    Func(Func),
851    Secret(Secret),
852    Connection(Connection),
853}
854
855impl From<CatalogEntry> for durable::Item {
856    fn from(entry: CatalogEntry) -> durable::Item {
857        let (create_sql, global_id, extra_versions) = entry.item.into_serialized();
858        durable::Item {
859            id: entry.id,
860            oid: entry.oid,
861            global_id,
862            schema_id: entry.name.qualifiers.schema_spec.into(),
863            name: entry.name.item,
864            create_sql,
865            owner_id: entry.owner_id,
866            privileges: entry.privileges.into_all_values().collect(),
867            extra_versions,
868        }
869    }
870}
871
872#[derive(Debug, Clone, Serialize)]
873pub struct Table {
874    /// Parse-able SQL that defines this table.
875    pub create_sql: Option<String>,
876    /// [`VersionedRelationDesc`] of this table, derived from the `create_sql`.
877    pub desc: VersionedRelationDesc,
878    /// Versions of this table, and the [`GlobalId`]s that refer to them.
879    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
880    pub collections: BTreeMap<RelationVersion, GlobalId>,
881    /// If created in the `TEMPORARY` schema, the [`ConnectionId`] for that session.
882    #[serde(skip)]
883    pub conn_id: Option<ConnectionId>,
884    /// Other catalog objects referenced by this table, e.g. custom types.
885    pub resolved_ids: ResolvedIds,
886    /// Custom compaction window, e.g. set via `ALTER RETAIN HISTORY`.
887    pub custom_logical_compaction_window: Option<CompactionWindow>,
888    /// Whether the table's logical compaction window is controlled by the ['metrics_retention']
889    /// session variable.
890    ///
891    /// ['metrics_retention']: mz_sql::session::vars::METRICS_RETENTION
892    pub is_retained_metrics_object: bool,
893    /// Where data for this table comes from, e.g. `INSERT` statements or an upstream source.
894    pub data_source: TableDataSource,
895}
896
897impl Table {
898    pub fn timeline(&self) -> Timeline {
899        match &self.data_source {
900            // The Coordinator controls insertions for writable tables
901            // (including system tables), so they are realtime.
902            TableDataSource::TableWrites { .. } => Timeline::EpochMilliseconds,
903            TableDataSource::DataSource { timeline, .. } => timeline.clone(),
904        }
905    }
906
907    /// Returns all of the [`GlobalId`]s that this [`Table`] can be referenced by.
908    pub fn global_ids(&self) -> impl Iterator<Item = GlobalId> + '_ {
909        self.collections.values().copied()
910    }
911
912    /// Returns the latest [`GlobalId`] for this [`Table`] which should be used for writes.
913    pub fn global_id_writes(&self) -> GlobalId {
914        *self
915            .collections
916            .last_key_value()
917            .expect("at least one version of a table")
918            .1
919    }
920
921    /// Returns all of the collections and their [`RelationDesc`]s associated with this [`Table`].
922    pub fn collection_descs(
923        &self,
924    ) -> impl Iterator<Item = (GlobalId, RelationVersion, RelationDesc)> + '_ {
925        self.collections.iter().map(|(version, gid)| {
926            let desc = self
927                .desc
928                .at_version(RelationVersionSelector::Specific(*version));
929            (*gid, *version, desc)
930        })
931    }
932
933    /// Returns the [`RelationDesc`] for a specific [`GlobalId`].
934    pub fn desc_for(&self, id: &GlobalId) -> RelationDesc {
935        let (version, _gid) = self
936            .collections
937            .iter()
938            .find(|(_version, gid)| *gid == id)
939            .expect("GlobalId to exist");
940        self.desc
941            .at_version(RelationVersionSelector::Specific(*version))
942    }
943}
944
945#[derive(Clone, Debug, Serialize)]
946pub enum TableDataSource {
947    /// The table owns data created via INSERT/UPDATE/DELETE statements.
948    TableWrites {
949        #[serde(skip)]
950        defaults: Vec<Expr<Aug>>,
951    },
952
953    /// The table receives its data from the identified `DataSourceDesc`.
954    /// This table type does not support INSERT/UPDATE/DELETE statements.
955    DataSource {
956        desc: DataSourceDesc,
957        timeline: Timeline,
958    },
959}
960
961#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
962pub enum DataSourceDesc {
963    /// Receives data from an external system
964    Ingestion {
965        desc: SourceDesc<ReferencedConnection>,
966        cluster_id: ClusterId,
967    },
968    /// Receives data from an external system
969    OldSyntaxIngestion {
970        desc: SourceDesc<ReferencedConnection>,
971        cluster_id: ClusterId,
972        // If we're dealing with an old syntax ingestion the progress id will be some other collection
973        // and the ingestion itself will have the data from an external reference
974        progress_subsource: CatalogItemId,
975        data_config: SourceExportDataConfig<ReferencedConnection>,
976        details: SourceExportDetails,
977    },
978    /// This source receives its data from the identified ingestion,
979    /// specifically the output identified by `external_reference`.
980    /// N.B. that `external_reference` should not be used to identify
981    /// anything downstream of purification, as the purification process
982    /// encodes source-specific identifiers into the `details` struct.
983    /// The `external_reference` field is only used here for displaying
984    /// human-readable names in system tables.
985    IngestionExport {
986        ingestion_id: CatalogItemId,
987        external_reference: UnresolvedItemName,
988        details: SourceExportDetails,
989        data_config: SourceExportDataConfig<ReferencedConnection>,
990    },
991    /// Receives introspection data from an internal system
992    Introspection(IntrospectionType),
993    /// Receives data from the source's reclocking/remapping operations.
994    Progress,
995    /// Receives data from HTTP requests.
996    Webhook {
997        /// Optional components used to validation a webhook request.
998        validate_using: Option<WebhookValidation>,
999        /// Describes how we deserialize the body of a webhook request.
1000        body_format: WebhookBodyFormat,
1001        /// Describes whether or not to include headers and how to map them.
1002        headers: WebhookHeaders,
1003        /// The cluster which this source is associated with.
1004        cluster_id: ClusterId,
1005    },
1006    /// Exposes the contents of the catalog shard.
1007    Catalog,
1008}
1009
1010impl From<IntrospectionType> for DataSourceDesc {
1011    fn from(typ: IntrospectionType) -> Self {
1012        Self::Introspection(typ)
1013    }
1014}
1015
1016impl DataSourceDesc {
1017    /// The key and value formats of the data source.
1018    pub fn formats(&self) -> (Option<&str>, Option<&str>) {
1019        match &self {
1020            DataSourceDesc::Ingestion { .. } => (None, None),
1021            DataSourceDesc::OldSyntaxIngestion { data_config, .. } => {
1022                match &data_config.encoding.as_ref() {
1023                    Some(encoding) => match &encoding.key {
1024                        Some(key) => (Some(key.type_()), Some(encoding.value.type_())),
1025                        None => (None, Some(encoding.value.type_())),
1026                    },
1027                    None => (None, None),
1028                }
1029            }
1030            DataSourceDesc::IngestionExport { data_config, .. } => match &data_config.encoding {
1031                Some(encoding) => match &encoding.key {
1032                    Some(key) => (Some(key.type_()), Some(encoding.value.type_())),
1033                    None => (None, Some(encoding.value.type_())),
1034                },
1035                None => (None, None),
1036            },
1037            DataSourceDesc::Introspection(_)
1038            | DataSourceDesc::Webhook { .. }
1039            | DataSourceDesc::Progress
1040            | DataSourceDesc::Catalog => (None, None),
1041        }
1042    }
1043
1044    /// Envelope of the data source.
1045    pub fn envelope(&self) -> Option<&str> {
1046        // Note how "none"/"append-only" is different from `None`. Source
1047        // sources don't have an envelope (internal logs, for example), while
1048        // other sources have an envelope that we call the "NONE"-envelope.
1049
1050        fn envelope_string(envelope: &SourceEnvelope) -> &str {
1051            match envelope {
1052                SourceEnvelope::None(_) => "none",
1053                SourceEnvelope::Upsert(upsert_envelope) => match upsert_envelope.style {
1054                    mz_storage_types::sources::envelope::UpsertStyle::Default(_) => "upsert",
1055                    mz_storage_types::sources::envelope::UpsertStyle::Debezium { .. } => {
1056                        // NOTE(aljoscha): Should we somehow mark that this is
1057                        // using upsert internally? See note above about
1058                        // DEBEZIUM.
1059                        "debezium"
1060                    }
1061                    mz_storage_types::sources::envelope::UpsertStyle::ValueErrInline { .. } => {
1062                        "upsert-value-err-inline"
1063                    }
1064                },
1065                SourceEnvelope::CdcV2 => {
1066                    // TODO(aljoscha): Should we even report this? It's
1067                    // currently not exposed.
1068                    "materialize"
1069                }
1070            }
1071        }
1072
1073        match self {
1074            // NOTE(aljoscha): We could move the block for ingestions into
1075            // `SourceEnvelope` itself, but that one feels more like an internal
1076            // thing and adapter should own how we represent envelopes as a
1077            // string? It would not be hard to convince me otherwise, though.
1078            DataSourceDesc::Ingestion { .. } => None,
1079            DataSourceDesc::OldSyntaxIngestion { data_config, .. } => {
1080                Some(envelope_string(&data_config.envelope))
1081            }
1082            DataSourceDesc::IngestionExport { data_config, .. } => {
1083                Some(envelope_string(&data_config.envelope))
1084            }
1085            DataSourceDesc::Introspection(_)
1086            | DataSourceDesc::Webhook { .. }
1087            | DataSourceDesc::Progress
1088            | DataSourceDesc::Catalog => None,
1089        }
1090    }
1091}
1092
1093#[derive(Debug, Clone, Serialize)]
1094pub struct Source {
1095    /// Parse-able SQL that defines this table.
1096    pub create_sql: Option<String>,
1097    /// [`GlobalId`] used to reference this source from outside the catalog.
1098    pub global_id: GlobalId,
1099    // TODO: Unskip: currently blocked on some inner BTreeMap<X, _> problems.
1100    #[serde(skip)]
1101    pub data_source: DataSourceDesc,
1102    /// [`RelationDesc`] of this source, derived from the `create_sql`.
1103    pub desc: RelationDesc,
1104    /// The timeline this source exists on.
1105    pub timeline: Timeline,
1106    /// Other catalog objects referenced by this table, e.g. custom types.
1107    pub resolved_ids: ResolvedIds,
1108    /// This value is ignored for subsources, i.e. for
1109    /// [`DataSourceDesc::IngestionExport`]. Instead, it uses the primary
1110    /// sources logical compaction window.
1111    pub custom_logical_compaction_window: Option<CompactionWindow>,
1112    /// Whether the source's logical compaction window is controlled by
1113    /// METRICS_RETENTION
1114    pub is_retained_metrics_object: bool,
1115}
1116
1117impl Source {
1118    /// Creates a new `Source`.
1119    ///
1120    /// # Panics
1121    /// - If an ingestion-based plan is not given a cluster_id.
1122    /// - If a non-ingestion-based source has a defined cluster config in its plan.
1123    /// - If a non-ingestion-based source is given a cluster_id.
1124    pub fn new(
1125        plan: CreateSourcePlan,
1126        global_id: GlobalId,
1127        resolved_ids: ResolvedIds,
1128        custom_logical_compaction_window: Option<CompactionWindow>,
1129        is_retained_metrics_object: bool,
1130    ) -> Source {
1131        Source {
1132            create_sql: Some(plan.source.create_sql),
1133            data_source: match plan.source.data_source {
1134                mz_sql::plan::DataSourceDesc::Ingestion(desc) => DataSourceDesc::Ingestion {
1135                    desc,
1136                    cluster_id: plan
1137                        .in_cluster
1138                        .expect("ingestion-based sources must be given a cluster ID"),
1139                },
1140                mz_sql::plan::DataSourceDesc::OldSyntaxIngestion {
1141                    desc,
1142                    progress_subsource,
1143                    data_config,
1144                    details,
1145                } => DataSourceDesc::OldSyntaxIngestion {
1146                    desc,
1147                    cluster_id: plan
1148                        .in_cluster
1149                        .expect("ingestion-based sources must be given a cluster ID"),
1150                    progress_subsource,
1151                    data_config,
1152                    details,
1153                },
1154                mz_sql::plan::DataSourceDesc::Progress => {
1155                    assert!(
1156                        plan.in_cluster.is_none(),
1157                        "subsources must not have a host config or cluster_id defined"
1158                    );
1159                    DataSourceDesc::Progress
1160                }
1161                mz_sql::plan::DataSourceDesc::IngestionExport {
1162                    ingestion_id,
1163                    external_reference,
1164                    details,
1165                    data_config,
1166                } => {
1167                    assert!(
1168                        plan.in_cluster.is_none(),
1169                        "subsources must not have a host config or cluster_id defined"
1170                    );
1171                    DataSourceDesc::IngestionExport {
1172                        ingestion_id,
1173                        external_reference,
1174                        details,
1175                        data_config,
1176                    }
1177                }
1178                mz_sql::plan::DataSourceDesc::Webhook {
1179                    validate_using,
1180                    body_format,
1181                    headers,
1182                    cluster_id,
1183                } => {
1184                    mz_ore::soft_assert_or_log!(
1185                        cluster_id.is_none(),
1186                        "cluster_id set at Source level for Webhooks"
1187                    );
1188                    DataSourceDesc::Webhook {
1189                        validate_using,
1190                        body_format,
1191                        headers,
1192                        cluster_id: plan
1193                            .in_cluster
1194                            .expect("webhook sources must be given a cluster ID"),
1195                    }
1196                }
1197            },
1198            desc: plan.source.desc,
1199            global_id,
1200            timeline: plan.timeline,
1201            resolved_ids,
1202            custom_logical_compaction_window: plan
1203                .source
1204                .compaction_window
1205                .or(custom_logical_compaction_window),
1206            is_retained_metrics_object,
1207        }
1208    }
1209
1210    /// Type of the source.
1211    pub fn source_type(&self) -> &str {
1212        match &self.data_source {
1213            DataSourceDesc::Ingestion { desc, .. }
1214            | DataSourceDesc::OldSyntaxIngestion { desc, .. } => desc.connection.name(),
1215            DataSourceDesc::Progress => "progress",
1216            DataSourceDesc::IngestionExport { .. } => "subsource",
1217            DataSourceDesc::Introspection(_) | DataSourceDesc::Catalog => "source",
1218            DataSourceDesc::Webhook { .. } => "webhook",
1219        }
1220    }
1221
1222    /// Connection ID of the source, if one exists.
1223    pub fn connection_id(&self) -> Option<CatalogItemId> {
1224        match &self.data_source {
1225            DataSourceDesc::Ingestion { desc, .. }
1226            | DataSourceDesc::OldSyntaxIngestion { desc, .. } => desc.connection.connection_id(),
1227            DataSourceDesc::IngestionExport { .. }
1228            | DataSourceDesc::Introspection(_)
1229            | DataSourceDesc::Webhook { .. }
1230            | DataSourceDesc::Progress
1231            | DataSourceDesc::Catalog => None,
1232        }
1233    }
1234
1235    /// The single [`GlobalId`] that refers to this Source.
1236    pub fn global_id(&self) -> GlobalId {
1237        self.global_id
1238    }
1239
1240    /// The expensive resource that each source consumes is persist shards. To
1241    /// prevent abuse, we want to prevent users from creating sources that use an
1242    /// unbounded number of persist shards. But we also don't want to count
1243    /// persist shards that are mandated by the system (e.g., the progress
1244    /// shard) so that future versions of Materialize can introduce additional
1245    /// per-source shards (e.g., a per-source status shard) without impacting
1246    /// the limit calculation.
1247    pub fn user_controllable_persist_shard_count(&self) -> i64 {
1248        match &self.data_source {
1249            DataSourceDesc::Ingestion { .. } => 0,
1250            DataSourceDesc::OldSyntaxIngestion { desc, .. } => {
1251                match &desc.connection {
1252                    // These multi-output sources do not use their primary
1253                    // source's data shard, so we don't include it in accounting
1254                    // for users.
1255                    GenericSourceConnection::Postgres(_)
1256                    | GenericSourceConnection::MySql(_)
1257                    | GenericSourceConnection::SqlServer(_) => 0,
1258                    GenericSourceConnection::LoadGenerator(lg) => match lg.load_generator {
1259                        // Load generators that output data in their primary shard
1260                        LoadGenerator::Clock
1261                        | LoadGenerator::Counter { .. }
1262                        | LoadGenerator::Datums
1263                        | LoadGenerator::KeyValue(_) => 1,
1264                        LoadGenerator::Auction
1265                        | LoadGenerator::Marketing
1266                        | LoadGenerator::Tpch { .. } => 0,
1267                    },
1268                    GenericSourceConnection::Kafka(_) => 1,
1269                }
1270            }
1271            //  DataSourceDesc::IngestionExport represents a subsource, which
1272            //  use a data shard.
1273            DataSourceDesc::IngestionExport { .. } => 1,
1274            DataSourceDesc::Webhook { .. } => 1,
1275            // Introspection, catalog, and progress subsources are not under the user's control, so
1276            // shouldn't count toward their quota.
1277            DataSourceDesc::Introspection(_)
1278            | DataSourceDesc::Progress
1279            | DataSourceDesc::Catalog => 0,
1280        }
1281    }
1282}
1283
1284#[derive(Debug, Clone, Serialize)]
1285pub struct Log {
1286    /// The category of data this log stores.
1287    pub variant: LogVariant,
1288    /// [`GlobalId`] used to reference this log from outside the catalog.
1289    pub global_id: GlobalId,
1290}
1291
1292impl Log {
1293    /// The single [`GlobalId`] that refers to this Log.
1294    pub fn global_id(&self) -> GlobalId {
1295        self.global_id
1296    }
1297}
1298
1299#[derive(Debug, Clone, Serialize)]
1300pub struct Sink {
1301    /// Parse-able SQL that defines this sink.
1302    pub create_sql: String,
1303    /// [`GlobalId`] used to reference this sink from outside the catalog, e.g storage.
1304    pub global_id: GlobalId,
1305    /// Collection we read into this sink.
1306    pub from: GlobalId,
1307    /// Connection to the external service we're sinking into, e.g. Kafka.
1308    pub connection: StorageSinkConnection<ReferencedConnection>,
1309    /// Envelope we use to sink into the external system.
1310    ///
1311    /// TODO(guswynn): this probably should just be in the `connection`.
1312    pub envelope: SinkEnvelope,
1313    /// Emit an initial snapshot into the sink.
1314    pub with_snapshot: bool,
1315    /// Used to fence other writes into this sink as we evolve the upstream materialized view.
1316    pub version: u64,
1317    /// Other catalog objects this sink references.
1318    pub resolved_ids: ResolvedIds,
1319    /// Cluster this sink runs on.
1320    pub cluster_id: ClusterId,
1321    /// Commit interval for the sink.
1322    pub commit_interval: Option<Duration>,
1323}
1324
1325impl Sink {
1326    pub fn sink_type(&self) -> &str {
1327        self.connection.name()
1328    }
1329
1330    /// Envelope of the sink.
1331    pub fn envelope(&self) -> Option<&str> {
1332        match &self.envelope {
1333            SinkEnvelope::Debezium => Some("debezium"),
1334            SinkEnvelope::Upsert => Some("upsert"),
1335            SinkEnvelope::Append => Some("append"),
1336        }
1337    }
1338
1339    /// Output a combined format string of the sink. For legacy reasons
1340    /// if the key-format is none or the key & value formats are
1341    /// both the same (either avro or json), we return the value format name,
1342    /// otherwise we return a composite name.
1343    pub fn combined_format(&self) -> Option<Cow<'_, str>> {
1344        match &self.connection {
1345            StorageSinkConnection::Kafka(connection) => Some(connection.format.get_format_name()),
1346            StorageSinkConnection::Iceberg(_) => None,
1347        }
1348    }
1349
1350    /// Output distinct key_format and value_format of the sink.
1351    pub fn formats(&self) -> Option<(Option<&str>, &str)> {
1352        match &self.connection {
1353            StorageSinkConnection::Kafka(connection) => {
1354                let key_format = connection
1355                    .format
1356                    .key_format
1357                    .as_ref()
1358                    .map(|f| f.get_format_name());
1359                let value_format = connection.format.value_format.get_format_name();
1360                Some((key_format, value_format))
1361            }
1362            StorageSinkConnection::Iceberg(_) => None,
1363        }
1364    }
1365
1366    pub fn connection_id(&self) -> Option<CatalogItemId> {
1367        self.connection.connection_id()
1368    }
1369
1370    /// The single [`GlobalId`] that this Sink can be referenced by.
1371    pub fn global_id(&self) -> GlobalId {
1372        self.global_id
1373    }
1374}
1375
1376#[derive(Debug, Clone, Serialize)]
1377pub struct View {
1378    /// Parse-able SQL that defines this view.
1379    pub create_sql: String,
1380    /// [`GlobalId`] used to reference this view from outside the catalog, e.g. compute.
1381    pub global_id: GlobalId,
1382    /// Unoptimized high-level expression from parsing the `create_sql`.
1383    pub raw_expr: Arc<HirRelationExpr>,
1384    /// Optimized mid-level expression from (locally) optimizing the `raw_expr`.
1385    pub locally_optimized_expr: Arc<OptimizedMirRelationExpr>,
1386    /// Columns of this view.
1387    pub desc: RelationDesc,
1388    /// If created in the `TEMPORARY` schema, the [`ConnectionId`] for that session.
1389    pub conn_id: Option<ConnectionId>,
1390    /// Other catalog objects that are referenced by this view, determined at name resolution.
1391    pub resolved_ids: ResolvedIds,
1392    /// All of the catalog objects that are referenced by this view.
1393    pub dependencies: DependencyIds,
1394}
1395
1396impl View {
1397    /// The single [`GlobalId`] this [`View`] can be referenced by.
1398    pub fn global_id(&self) -> GlobalId {
1399        self.global_id
1400    }
1401}
1402
1403#[derive(Debug, Clone, Serialize)]
1404pub struct MaterializedView {
1405    /// Parse-able SQL that defines this materialized view.
1406    pub create_sql: String,
1407    /// Versions of this materialized view, and the [`GlobalId`]s that refer to them.
1408    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
1409    pub collections: BTreeMap<RelationVersion, GlobalId>,
1410    /// Raw high-level expression from planning, derived from the `create_sql`.
1411    pub raw_expr: Arc<HirRelationExpr>,
1412    /// Optimized mid-level expression, derived from the `raw_expr`.
1413    pub locally_optimized_expr: Arc<OptimizedMirRelationExpr>,
1414    /// [`VersionedRelationDesc`] of this materialized view, derived from the `create_sql`.
1415    pub desc: VersionedRelationDesc,
1416    /// Other catalog items that this materialized view references, determined at name resolution.
1417    pub resolved_ids: ResolvedIds,
1418    /// All of the catalog objects that are referenced by this view.
1419    pub dependencies: DependencyIds,
1420    /// ID of the materialized view this materialized view is intended to replace.
1421    pub replacement_target: Option<CatalogItemId>,
1422    /// Cluster that this materialized view runs on.
1423    pub cluster_id: ClusterId,
1424    /// If set, only install this materialized view's dataflow on the specified replica.
1425    pub target_replica: Option<ReplicaId>,
1426    /// Column indexes that we assert are not `NULL`.
1427    ///
1428    /// TODO(parkmycar): Switch this to use the `ColumnIdx` type.
1429    pub non_null_assertions: Vec<usize>,
1430    /// Custom compaction window, e.g. set via `ALTER RETAIN HISTORY`.
1431    pub custom_logical_compaction_window: Option<CompactionWindow>,
1432    /// Schedule to refresh this materialized view, e.g. set via `REFRESH EVERY` option.
1433    pub refresh_schedule: Option<RefreshSchedule>,
1434    /// The initial `as_of` of the storage collection associated with the materialized view.
1435    ///
1436    /// Note: This doesn't change upon restarts.
1437    /// (The dataflow's initial `as_of` can be different.)
1438    pub initial_as_of: Option<Antichain<mz_repr::Timestamp>>,
1439    // The catalog `dump` method uses serde to serialize catalog state, e.g., Testdrive catalog
1440    // consistency checks do two dumps and compare them. One of these states comes from the durable
1441    // catalog, but the following fields are not restored when the consistency check loads the
1442    // durable catalog, hence we need `#[serde(skip)]`.
1443    /// Optimized global MIR plan, set after global optimization.
1444    #[serde(skip)]
1445    pub optimized_plan: Option<Arc<DataflowDescription<OptimizedMirRelationExpr>>>,
1446    /// Physical (LIR) plan, set after physical optimization.
1447    #[serde(skip)]
1448    pub physical_plan: Option<Arc<DataflowDescription<ComputePlan>>>,
1449    /// Dataflow metainfo (optimizer notices, etc.), set after optimization.
1450    #[serde(skip)]
1451    pub dataflow_metainfo: Option<DataflowMetainfo<Arc<OptimizerNotice>>>,
1452}
1453
1454impl MaterializedView {
1455    /// Returns all [`GlobalId`]s that this [`MaterializedView`] can be referenced by.
1456    pub fn global_ids(&self) -> impl Iterator<Item = GlobalId> + '_ {
1457        self.collections.values().copied()
1458    }
1459
1460    /// The latest [`GlobalId`] for this [`MaterializedView`] which represents the writing
1461    /// version.
1462    pub fn global_id_writes(&self) -> GlobalId {
1463        *self
1464            .collections
1465            .last_key_value()
1466            .expect("at least one version of a materialized view")
1467            .1
1468    }
1469
1470    /// Returns all collections and their [`RelationDesc`]s associated with this [`MaterializedView`].
1471    pub fn collection_descs(
1472        &self,
1473    ) -> impl Iterator<Item = (GlobalId, RelationVersion, RelationDesc)> + '_ {
1474        self.collections.iter().map(|(version, gid)| {
1475            let desc = self
1476                .desc
1477                .at_version(RelationVersionSelector::Specific(*version));
1478            (*gid, *version, desc)
1479        })
1480    }
1481
1482    /// Returns the [`RelationDesc`] for a specific [`GlobalId`].
1483    pub fn desc_for(&self, id: &GlobalId) -> RelationDesc {
1484        let (version, _gid) = self
1485            .collections
1486            .iter()
1487            .find(|(_version, gid)| *gid == id)
1488            .expect("GlobalId to exist");
1489        self.desc
1490            .at_version(RelationVersionSelector::Specific(*version))
1491    }
1492
1493    /// Apply the given replacement materialized view to this [`MaterializedView`].
1494    pub fn apply_replacement(&mut self, replacement: Self) {
1495        let target_id = replacement
1496            .replacement_target
1497            .expect("replacement has target");
1498
1499        fn parse(create_sql: &str) -> mz_sql::ast::CreateMaterializedViewStatement<Raw> {
1500            let res = mz_sql::parse::parse(create_sql).unwrap_or_else(|e| {
1501                panic!("invalid create_sql persisted in catalog: {e}\n{create_sql}");
1502            });
1503            if let Statement::CreateMaterializedView(cmvs) = res.into_element().ast {
1504                cmvs
1505            } else {
1506                panic!("invalid MV create_sql persisted in catalog\n{create_sql}");
1507            }
1508        }
1509
1510        let old_stmt = parse(&self.create_sql);
1511        let rpl_stmt = parse(&replacement.create_sql);
1512        let new_stmt = mz_sql::ast::CreateMaterializedViewStatement {
1513            if_exists: old_stmt.if_exists,
1514            name: old_stmt.name,
1515            columns: rpl_stmt.columns,
1516            replacement_for: None,
1517            in_cluster: rpl_stmt.in_cluster,
1518            in_cluster_replica: rpl_stmt.in_cluster_replica,
1519            query: rpl_stmt.query,
1520            as_of: rpl_stmt.as_of,
1521            with_options: rpl_stmt.with_options,
1522        };
1523        let create_sql = new_stmt.to_ast_string_stable();
1524
1525        let mut collections = std::mem::take(&mut self.collections);
1526        // Note: We can't use `self.desc.latest_version` here because a replacement doesn't
1527        // necessary evolve the relation schema, so that version might be lower than the actual
1528        // latest version.
1529        let latest_version = collections.keys().max().expect("at least one version");
1530        let new_version = latest_version.bump();
1531        collections.insert(new_version, replacement.global_id_writes());
1532
1533        let mut resolved_ids = replacement.resolved_ids;
1534        resolved_ids.remove_item(&target_id);
1535        let mut dependencies = replacement.dependencies;
1536        dependencies.0.remove(&target_id);
1537
1538        *self = Self {
1539            create_sql,
1540            collections,
1541            raw_expr: replacement.raw_expr,
1542            locally_optimized_expr: replacement.locally_optimized_expr,
1543            desc: replacement.desc,
1544            resolved_ids,
1545            dependencies,
1546            replacement_target: None,
1547            cluster_id: replacement.cluster_id,
1548            target_replica: replacement.target_replica,
1549            non_null_assertions: replacement.non_null_assertions,
1550            custom_logical_compaction_window: replacement.custom_logical_compaction_window,
1551            refresh_schedule: replacement.refresh_schedule,
1552            initial_as_of: replacement.initial_as_of,
1553            optimized_plan: replacement.optimized_plan,
1554            physical_plan: replacement.physical_plan,
1555            dataflow_metainfo: replacement.dataflow_metainfo,
1556        };
1557    }
1558}
1559
1560#[derive(Debug, Clone, Serialize)]
1561pub struct Index {
1562    /// Parse-able SQL that defines this table.
1563    pub create_sql: String,
1564    /// [`GlobalId`] used to reference this index from outside the catalog, e.g. compute.
1565    pub global_id: GlobalId,
1566    /// The [`GlobalId`] this Index is on.
1567    pub on: GlobalId,
1568    /// Keys of the index.
1569    pub keys: Arc<[MirScalarExpr]>,
1570    /// If created in the `TEMPORARY` schema, the [`ConnectionId`] for that session.
1571    pub conn_id: Option<ConnectionId>,
1572    /// Other catalog objects referenced by this index, e.g. the object we're indexing.
1573    pub resolved_ids: ResolvedIds,
1574    /// Cluster this index is installed on.
1575    pub cluster_id: ClusterId,
1576    /// Custom compaction window, e.g. set via `ALTER RETAIN HISTORY`.
1577    pub custom_logical_compaction_window: Option<CompactionWindow>,
1578    /// Whether the table's logical compaction window is controlled by the ['metrics_retention']
1579    /// session variable.
1580    ///
1581    /// ['metrics_retention']: mz_sql::session::vars::METRICS_RETENTION
1582    pub is_retained_metrics_object: bool,
1583    // The catalog `dump` method uses serde to serialize catalog state, e.g., Testdrive catalog
1584    // consistency checks do two dumps and compare them. One of these states comes from the durable
1585    // catalog, but the following fields are not restored when the consistency check loads the
1586    // durable catalog, hence we need `#[serde(skip)]`.
1587    /// Optimized global MIR plan, set after global optimization.
1588    #[serde(skip)]
1589    pub optimized_plan: Option<Arc<DataflowDescription<OptimizedMirRelationExpr>>>,
1590    /// Physical (LIR) plan, set after physical optimization.
1591    #[serde(skip)]
1592    pub physical_plan: Option<Arc<DataflowDescription<ComputePlan>>>,
1593    /// Dataflow metainfo (optimizer notices, etc.), set after optimization.
1594    #[serde(skip)]
1595    pub dataflow_metainfo: Option<DataflowMetainfo<Arc<OptimizerNotice>>>,
1596}
1597
1598impl Index {
1599    /// The [`GlobalId`] that refers to this Index.
1600    pub fn global_id(&self) -> GlobalId {
1601        self.global_id
1602    }
1603}
1604
1605#[derive(Debug, Clone, Serialize)]
1606pub struct Type {
1607    /// Parse-able SQL that defines this type.
1608    pub create_sql: Option<String>,
1609    /// [`GlobalId`] used to reference this type from outside the catalog.
1610    pub global_id: GlobalId,
1611    #[serde(skip)]
1612    pub details: CatalogTypeDetails<IdReference>,
1613    /// Other catalog objects referenced by this type.
1614    pub resolved_ids: ResolvedIds,
1615}
1616
1617#[derive(Debug, Clone, Serialize)]
1618pub struct Func {
1619    /// Static definition of the function.
1620    #[serde(skip)]
1621    pub inner: &'static mz_sql::func::Func,
1622    /// [`GlobalId`] used to reference this function from outside the catalog.
1623    pub global_id: GlobalId,
1624}
1625
1626#[derive(Debug, Clone, Serialize)]
1627pub struct Secret {
1628    /// Parse-able SQL that defines this secret.
1629    pub create_sql: String,
1630    /// [`GlobalId`] used to reference this secret from outside the catalog.
1631    pub global_id: GlobalId,
1632}
1633
1634#[derive(Debug, Clone, Serialize)]
1635pub struct Connection {
1636    /// Parse-able SQL that defines this connection.
1637    pub create_sql: String,
1638    /// [`GlobalId`] used to reference this connection from the storage layer.
1639    pub global_id: GlobalId,
1640    /// The kind of connection.
1641    pub details: ConnectionDetails,
1642    /// Other objects this connection depends on.
1643    pub resolved_ids: ResolvedIds,
1644}
1645
1646impl Connection {
1647    /// The single [`GlobalId`] used to reference this connection.
1648    pub fn global_id(&self) -> GlobalId {
1649        self.global_id
1650    }
1651}
1652
1653#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1654pub struct NetworkPolicy {
1655    pub name: String,
1656    pub id: NetworkPolicyId,
1657    pub oid: u32,
1658    pub rules: Vec<NetworkPolicyRule>,
1659    pub owner_id: RoleId,
1660    pub privileges: PrivilegeMap,
1661}
1662
1663impl From<NetworkPolicy> for durable::NetworkPolicy {
1664    fn from(policy: NetworkPolicy) -> durable::NetworkPolicy {
1665        durable::NetworkPolicy {
1666            id: policy.id,
1667            oid: policy.oid,
1668            name: policy.name,
1669            rules: policy.rules,
1670            owner_id: policy.owner_id,
1671            privileges: policy.privileges.into_all_values().collect(),
1672        }
1673    }
1674}
1675
1676impl From<durable::NetworkPolicy> for NetworkPolicy {
1677    fn from(
1678        durable::NetworkPolicy {
1679            id,
1680            oid,
1681            name,
1682            rules,
1683            owner_id,
1684            privileges,
1685        }: durable::NetworkPolicy,
1686    ) -> Self {
1687        NetworkPolicy {
1688            id,
1689            oid,
1690            name,
1691            rules,
1692            owner_id,
1693            privileges: PrivilegeMap::from_mz_acl_items(privileges),
1694        }
1695    }
1696}
1697
1698impl UpdateFrom<durable::NetworkPolicy> for NetworkPolicy {
1699    fn update_from(
1700        &mut self,
1701        durable::NetworkPolicy {
1702            id,
1703            oid,
1704            name,
1705            rules,
1706            owner_id,
1707            privileges,
1708        }: durable::NetworkPolicy,
1709    ) {
1710        self.id = id;
1711        self.oid = oid;
1712        self.name = name;
1713        self.rules = rules;
1714        self.owner_id = owner_id;
1715        self.privileges = PrivilegeMap::from_mz_acl_items(privileges);
1716    }
1717}
1718
1719impl CatalogItem {
1720    /// Returns a string indicating the type of this catalog entry.
1721    pub fn typ(&self) -> mz_sql::catalog::CatalogItemType {
1722        match self {
1723            CatalogItem::Table(_) => CatalogItemType::Table,
1724            CatalogItem::Source(_) => CatalogItemType::Source,
1725            CatalogItem::Log(_) => CatalogItemType::Source,
1726            CatalogItem::Sink(_) => CatalogItemType::Sink,
1727            CatalogItem::View(_) => CatalogItemType::View,
1728            CatalogItem::MaterializedView(_) => CatalogItemType::MaterializedView,
1729            CatalogItem::Index(_) => CatalogItemType::Index,
1730            CatalogItem::Type(_) => CatalogItemType::Type,
1731            CatalogItem::Func(_) => CatalogItemType::Func,
1732            CatalogItem::Secret(_) => CatalogItemType::Secret,
1733            CatalogItem::Connection(_) => CatalogItemType::Connection,
1734        }
1735    }
1736
1737    /// Returns the [`GlobalId`]s that reference this item, if any.
1738    pub fn global_ids(&self) -> impl Iterator<Item = GlobalId> + '_ {
1739        let gid = match self {
1740            CatalogItem::Source(source) => source.global_id,
1741            CatalogItem::Log(log) => log.global_id,
1742            CatalogItem::Sink(sink) => sink.global_id,
1743            CatalogItem::View(view) => view.global_id,
1744            CatalogItem::MaterializedView(mv) => {
1745                return itertools::Either::Left(mv.collections.values().copied());
1746            }
1747            CatalogItem::Index(index) => index.global_id,
1748            CatalogItem::Func(func) => func.global_id,
1749            CatalogItem::Type(ty) => ty.global_id,
1750            CatalogItem::Secret(secret) => secret.global_id,
1751            CatalogItem::Connection(conn) => conn.global_id,
1752            CatalogItem::Table(table) => {
1753                return itertools::Either::Left(table.collections.values().copied());
1754            }
1755        };
1756        itertools::Either::Right(std::iter::once(gid))
1757    }
1758
1759    /// Returns the most up-to-date [`GlobalId`] for this item.
1760    ///
1761    /// Note: The only type of object that can have multiple [`GlobalId`]s are tables.
1762    pub fn latest_global_id(&self) -> GlobalId {
1763        match self {
1764            CatalogItem::Source(source) => source.global_id,
1765            CatalogItem::Log(log) => log.global_id,
1766            CatalogItem::Sink(sink) => sink.global_id,
1767            CatalogItem::View(view) => view.global_id,
1768            CatalogItem::MaterializedView(mv) => mv.global_id_writes(),
1769            CatalogItem::Index(index) => index.global_id,
1770            CatalogItem::Func(func) => func.global_id,
1771            CatalogItem::Type(ty) => ty.global_id,
1772            CatalogItem::Secret(secret) => secret.global_id,
1773            CatalogItem::Connection(conn) => conn.global_id,
1774            CatalogItem::Table(table) => table.global_id_writes(),
1775        }
1776    }
1777
1778    /// Returns the optimized global MIR plan, if this item has one.
1779    pub fn optimized_plan(&self) -> Option<&Arc<DataflowDescription<OptimizedMirRelationExpr>>> {
1780        match self {
1781            CatalogItem::Index(idx) => idx.optimized_plan.as_ref(),
1782            CatalogItem::MaterializedView(mv) => mv.optimized_plan.as_ref(),
1783            _ => None,
1784        }
1785    }
1786
1787    /// Returns the physical (LIR) plan, if this item has one.
1788    pub fn physical_plan(&self) -> Option<&Arc<DataflowDescription<ComputePlan>>> {
1789        match self {
1790            CatalogItem::Index(idx) => idx.physical_plan.as_ref(),
1791            CatalogItem::MaterializedView(mv) => mv.physical_plan.as_ref(),
1792            _ => None,
1793        }
1794    }
1795
1796    /// Returns the dataflow metainfo, if this item has one.
1797    pub fn dataflow_metainfo(&self) -> Option<&DataflowMetainfo<Arc<OptimizerNotice>>> {
1798        match self {
1799            CatalogItem::Index(idx) => idx.dataflow_metainfo.as_ref(),
1800            CatalogItem::MaterializedView(mv) => mv.dataflow_metainfo.as_ref(),
1801            _ => None,
1802        }
1803    }
1804
1805    /// Returns mutable references to the plan fields (`optimized_plan`,
1806    /// `physical_plan`, `dataflow_metainfo`) on plan-bearing items
1807    /// (`Index`, `MaterializedView`), or `None` for
1808    /// other item kinds.
1809    pub fn plan_fields_mut(
1810        &mut self,
1811    ) -> Option<(
1812        &mut Option<Arc<DataflowDescription<OptimizedMirRelationExpr>>>,
1813        &mut Option<Arc<DataflowDescription<ComputePlan>>>,
1814        &mut Option<DataflowMetainfo<Arc<OptimizerNotice>>>,
1815    )> {
1816        match self {
1817            CatalogItem::Index(idx) => Some((
1818                &mut idx.optimized_plan,
1819                &mut idx.physical_plan,
1820                &mut idx.dataflow_metainfo,
1821            )),
1822            CatalogItem::MaterializedView(mv) => Some((
1823                &mut mv.optimized_plan,
1824                &mut mv.physical_plan,
1825                &mut mv.dataflow_metainfo,
1826            )),
1827            _ => None,
1828        }
1829    }
1830
1831    /// Whether this item represents a storage collection.
1832    pub fn is_storage_collection(&self) -> bool {
1833        match self {
1834            CatalogItem::Table(_)
1835            | CatalogItem::Source(_)
1836            | CatalogItem::MaterializedView(_)
1837            | CatalogItem::Sink(_) => true,
1838            CatalogItem::Log(_)
1839            | CatalogItem::View(_)
1840            | CatalogItem::Index(_)
1841            | CatalogItem::Type(_)
1842            | CatalogItem::Func(_)
1843            | CatalogItem::Secret(_)
1844            | CatalogItem::Connection(_) => false,
1845        }
1846    }
1847
1848    /// Returns the [`RelationDesc`] for items that yield rows, at the requested
1849    /// version.
1850    ///
1851    /// Some item types honor `version` so callers can ask for the schema that
1852    /// matches a specific [`GlobalId`] or historical definition. Other relation
1853    /// types ignore `version` because they have a single shape. Non-relational
1854    /// items ( for example functions, indexes, sinks, secrets, and connections)
1855    /// return `None`.
1856    pub fn relation_desc(&self, version: RelationVersionSelector) -> Option<Cow<'_, RelationDesc>> {
1857        match &self {
1858            CatalogItem::Source(src) => Some(Cow::Borrowed(&src.desc)),
1859            CatalogItem::Log(log) => Some(Cow::Owned(log.variant.desc())),
1860            CatalogItem::Table(tbl) => Some(Cow::Owned(tbl.desc.at_version(version))),
1861            CatalogItem::View(view) => Some(Cow::Borrowed(&view.desc)),
1862            CatalogItem::MaterializedView(mview) => {
1863                Some(Cow::Owned(mview.desc.at_version(version)))
1864            }
1865            CatalogItem::Func(_)
1866            | CatalogItem::Index(_)
1867            | CatalogItem::Sink(_)
1868            | CatalogItem::Secret(_)
1869            | CatalogItem::Connection(_)
1870            | CatalogItem::Type(_) => None,
1871        }
1872    }
1873
1874    pub fn func(
1875        &self,
1876        entry: &CatalogEntry,
1877    ) -> Result<&'static mz_sql::func::Func, SqlCatalogError> {
1878        match &self {
1879            CatalogItem::Func(func) => Ok(func.inner),
1880            _ => Err(SqlCatalogError::UnexpectedType {
1881                name: entry.name().item.to_string(),
1882                actual_type: entry.item_type(),
1883                expected_type: CatalogItemType::Func,
1884            }),
1885        }
1886    }
1887
1888    pub fn source_desc(
1889        &self,
1890        entry: &CatalogEntry,
1891    ) -> Result<Option<&SourceDesc<ReferencedConnection>>, SqlCatalogError> {
1892        match &self {
1893            CatalogItem::Source(source) => match &source.data_source {
1894                DataSourceDesc::Ingestion { desc, .. }
1895                | DataSourceDesc::OldSyntaxIngestion { desc, .. } => Ok(Some(desc)),
1896                DataSourceDesc::IngestionExport { .. }
1897                | DataSourceDesc::Introspection(_)
1898                | DataSourceDesc::Webhook { .. }
1899                | DataSourceDesc::Progress
1900                | DataSourceDesc::Catalog => Ok(None),
1901            },
1902            _ => Err(SqlCatalogError::UnexpectedType {
1903                name: entry.name().item.to_string(),
1904                actual_type: entry.item_type(),
1905                expected_type: CatalogItemType::Source,
1906            }),
1907        }
1908    }
1909
1910    /// Reports whether this catalog entry is a progress source.
1911    pub fn is_progress_source(&self) -> bool {
1912        matches!(
1913            self,
1914            CatalogItem::Source(Source {
1915                data_source: DataSourceDesc::Progress,
1916                ..
1917            })
1918        )
1919    }
1920
1921    /// Collects the identifiers of the objects that were encountered when resolving names in the
1922    /// item's DDL statement.
1923    pub fn references(&self) -> &ResolvedIds {
1924        static EMPTY: LazyLock<ResolvedIds> = LazyLock::new(ResolvedIds::empty);
1925        match self {
1926            CatalogItem::Func(_) => &*EMPTY,
1927            CatalogItem::Index(idx) => &idx.resolved_ids,
1928            CatalogItem::Sink(sink) => &sink.resolved_ids,
1929            CatalogItem::Source(source) => &source.resolved_ids,
1930            CatalogItem::Log(_) => &*EMPTY,
1931            CatalogItem::Table(table) => &table.resolved_ids,
1932            CatalogItem::Type(typ) => &typ.resolved_ids,
1933            CatalogItem::View(view) => &view.resolved_ids,
1934            CatalogItem::MaterializedView(mview) => &mview.resolved_ids,
1935            CatalogItem::Secret(_) => &*EMPTY,
1936            CatalogItem::Connection(connection) => &connection.resolved_ids,
1937        }
1938    }
1939
1940    /// Collects the identifiers of the objects used by this [`CatalogItem`].
1941    ///
1942    /// Like [`CatalogItem::references()`] but also includes objects that are not directly
1943    /// referenced. For example this will include any catalog objects used to implement functions
1944    /// and casts in the item.
1945    pub fn uses(&self) -> BTreeSet<CatalogItemId> {
1946        let mut uses: BTreeSet<_> = self.references().items().copied().collect();
1947        match self {
1948            // TODO(jkosh44) This isn't really correct for functions. They may use other objects in
1949            // their implementation. However, currently there's no way to get that information.
1950            CatalogItem::Func(_) => {}
1951            CatalogItem::Index(_) => {}
1952            CatalogItem::Sink(_) => {}
1953            CatalogItem::Source(_) => {}
1954            CatalogItem::Log(_) => {}
1955            CatalogItem::Table(_) => {}
1956            CatalogItem::Type(_) => {}
1957            CatalogItem::View(view) => uses.extend(view.dependencies.0.iter().copied()),
1958            CatalogItem::MaterializedView(mview) => {
1959                uses.extend(mview.dependencies.0.iter().copied())
1960            }
1961            CatalogItem::Secret(_) => {}
1962            CatalogItem::Connection(_) => {}
1963        }
1964        uses
1965    }
1966
1967    /// Returns the connection ID that this item belongs to, if this item is
1968    /// temporary.
1969    pub fn conn_id(&self) -> Option<&ConnectionId> {
1970        match self {
1971            CatalogItem::View(view) => view.conn_id.as_ref(),
1972            CatalogItem::Index(index) => index.conn_id.as_ref(),
1973            CatalogItem::Table(table) => table.conn_id.as_ref(),
1974            CatalogItem::Log(_)
1975            | CatalogItem::Source(_)
1976            | CatalogItem::Sink(_)
1977            | CatalogItem::MaterializedView(_)
1978            | CatalogItem::Secret(_)
1979            | CatalogItem::Type(_)
1980            | CatalogItem::Func(_)
1981            | CatalogItem::Connection(_) => None,
1982        }
1983    }
1984
1985    /// Sets the connection ID that this item belongs to, which makes it a
1986    /// temporary item.
1987    pub fn set_conn_id(&mut self, conn_id: Option<ConnectionId>) {
1988        match self {
1989            CatalogItem::View(view) => view.conn_id = conn_id,
1990            CatalogItem::Index(index) => index.conn_id = conn_id,
1991            CatalogItem::Table(table) => table.conn_id = conn_id,
1992            CatalogItem::Log(_)
1993            | CatalogItem::Source(_)
1994            | CatalogItem::Sink(_)
1995            | CatalogItem::MaterializedView(_)
1996            | CatalogItem::Secret(_)
1997            | CatalogItem::Type(_)
1998            | CatalogItem::Func(_)
1999            | CatalogItem::Connection(_) => (),
2000        }
2001    }
2002
2003    /// Overwrites the `create_sql` of this item, without replanning.
2004    ///
2005    /// Only used when applying temporary item updates. A temporary item's
2006    /// in-memory `create_sql` must stay byte-identical to the update that
2007    /// created it, so that re-serializing the item, for example when a later
2008    /// op in the same transaction retracts it, yields a value that
2009    /// consolidates away against that update. Persistent items get this
2010    /// guarantee from the durable catalog, which stores the exact bytes.
2011    pub fn set_create_sql(&mut self, create_sql: String) {
2012        match self {
2013            CatalogItem::View(view) => view.create_sql = create_sql,
2014            CatalogItem::Index(index) => index.create_sql = create_sql,
2015            CatalogItem::Table(table) => table.create_sql = Some(create_sql),
2016            CatalogItem::Log(_)
2017            | CatalogItem::Source(_)
2018            | CatalogItem::Sink(_)
2019            | CatalogItem::MaterializedView(_)
2020            | CatalogItem::Secret(_)
2021            | CatalogItem::Type(_)
2022            | CatalogItem::Func(_)
2023            | CatalogItem::Connection(_) => {
2024                unreachable!("only views, indexes, and tables can be temporary")
2025            }
2026        }
2027    }
2028
2029    /// Indicates whether this item is temporary or not.
2030    pub fn is_temporary(&self) -> bool {
2031        self.conn_id().is_some()
2032    }
2033
2034    pub fn rename_schema_refs(
2035        &self,
2036        database_name: &str,
2037        cur_schema_name: &str,
2038        new_schema_name: &str,
2039    ) -> Result<CatalogItem, (String, String)> {
2040        let do_rewrite = |create_sql: String| -> Result<String, (String, String)> {
2041            let mut create_stmt = mz_sql::parse::parse(&create_sql)
2042                .expect("invalid create sql persisted to catalog")
2043                .into_element()
2044                .ast;
2045
2046            // Rename all references to cur_schema_name.
2047            mz_sql::ast::transform::create_stmt_rename_schema_refs(
2048                &mut create_stmt,
2049                database_name,
2050                cur_schema_name,
2051                new_schema_name,
2052            )?;
2053
2054            Ok(create_stmt.to_ast_string_stable())
2055        };
2056
2057        match self {
2058            CatalogItem::Table(i) => {
2059                let mut i = i.clone();
2060                i.create_sql = i.create_sql.map(do_rewrite).transpose()?;
2061                Ok(CatalogItem::Table(i))
2062            }
2063            CatalogItem::Log(i) => Ok(CatalogItem::Log(i.clone())),
2064            CatalogItem::Source(i) => {
2065                let mut i = i.clone();
2066                i.create_sql = i.create_sql.map(do_rewrite).transpose()?;
2067                Ok(CatalogItem::Source(i))
2068            }
2069            CatalogItem::Sink(i) => {
2070                let mut i = i.clone();
2071                i.create_sql = do_rewrite(i.create_sql)?;
2072                Ok(CatalogItem::Sink(i))
2073            }
2074            CatalogItem::View(i) => {
2075                let mut i = i.clone();
2076                i.create_sql = do_rewrite(i.create_sql)?;
2077                Ok(CatalogItem::View(i))
2078            }
2079            CatalogItem::MaterializedView(i) => {
2080                let mut i = i.clone();
2081                i.create_sql = do_rewrite(i.create_sql)?;
2082                Ok(CatalogItem::MaterializedView(i))
2083            }
2084            CatalogItem::Index(i) => {
2085                let mut i = i.clone();
2086                i.create_sql = do_rewrite(i.create_sql)?;
2087                Ok(CatalogItem::Index(i))
2088            }
2089            CatalogItem::Secret(i) => {
2090                let mut i = i.clone();
2091                i.create_sql = do_rewrite(i.create_sql)?;
2092                Ok(CatalogItem::Secret(i))
2093            }
2094            CatalogItem::Connection(i) => {
2095                let mut i = i.clone();
2096                i.create_sql = do_rewrite(i.create_sql)?;
2097                Ok(CatalogItem::Connection(i))
2098            }
2099            CatalogItem::Type(i) => {
2100                let mut i = i.clone();
2101                i.create_sql = i.create_sql.map(do_rewrite).transpose()?;
2102                Ok(CatalogItem::Type(i))
2103            }
2104            CatalogItem::Func(i) => Ok(CatalogItem::Func(i.clone())),
2105        }
2106    }
2107
2108    /// Returns a clone of `self` with all instances of `from` renamed to `to`
2109    /// (with the option of including the item's own name) or errors if request
2110    /// is ambiguous.
2111    pub fn rename_item_refs(
2112        &self,
2113        from: FullItemName,
2114        to_item_name: String,
2115        rename_self: bool,
2116    ) -> Result<CatalogItem, String> {
2117        let do_rewrite = |create_sql: String| -> Result<String, String> {
2118            let mut create_stmt = mz_sql::parse::parse(&create_sql)
2119                .expect("invalid create sql persisted to catalog")
2120                .into_element()
2121                .ast;
2122            if rename_self {
2123                mz_sql::ast::transform::create_stmt_rename(&mut create_stmt, to_item_name.clone());
2124            }
2125            // Determination of what constitutes an ambiguous request is done here.
2126            mz_sql::ast::transform::create_stmt_rename_refs(&mut create_stmt, from, to_item_name)?;
2127            Ok(create_stmt.to_ast_string_stable())
2128        };
2129
2130        match self {
2131            CatalogItem::Table(i) => {
2132                let mut i = i.clone();
2133                i.create_sql = i.create_sql.map(do_rewrite).transpose()?;
2134                Ok(CatalogItem::Table(i))
2135            }
2136            CatalogItem::Log(i) => Ok(CatalogItem::Log(i.clone())),
2137            CatalogItem::Source(i) => {
2138                let mut i = i.clone();
2139                i.create_sql = i.create_sql.map(do_rewrite).transpose()?;
2140                Ok(CatalogItem::Source(i))
2141            }
2142            CatalogItem::Sink(i) => {
2143                let mut i = i.clone();
2144                i.create_sql = do_rewrite(i.create_sql)?;
2145                Ok(CatalogItem::Sink(i))
2146            }
2147            CatalogItem::View(i) => {
2148                let mut i = i.clone();
2149                i.create_sql = do_rewrite(i.create_sql)?;
2150                Ok(CatalogItem::View(i))
2151            }
2152            CatalogItem::MaterializedView(i) => {
2153                let mut i = i.clone();
2154                i.create_sql = do_rewrite(i.create_sql)?;
2155                Ok(CatalogItem::MaterializedView(i))
2156            }
2157            CatalogItem::Index(i) => {
2158                let mut i = i.clone();
2159                i.create_sql = do_rewrite(i.create_sql)?;
2160                Ok(CatalogItem::Index(i))
2161            }
2162            CatalogItem::Secret(i) => {
2163                let mut i = i.clone();
2164                i.create_sql = do_rewrite(i.create_sql)?;
2165                Ok(CatalogItem::Secret(i))
2166            }
2167            CatalogItem::Func(_) | CatalogItem::Type(_) => {
2168                unreachable!("{}s cannot be renamed", self.typ())
2169            }
2170            CatalogItem::Connection(i) => {
2171                let mut i = i.clone();
2172                i.create_sql = do_rewrite(i.create_sql)?;
2173                Ok(CatalogItem::Connection(i))
2174            }
2175        }
2176    }
2177
2178    /// Returns a clone of `self` with all instances of `old_id` replaced with `new_id`.
2179    pub fn replace_item_refs(&self, old_id: CatalogItemId, new_id: CatalogItemId) -> CatalogItem {
2180        let do_rewrite = |create_sql: String| -> String {
2181            let mut create_stmt = mz_sql::parse::parse(&create_sql)
2182                .expect("invalid create sql persisted to catalog")
2183                .into_element()
2184                .ast;
2185            mz_sql::ast::transform::create_stmt_replace_ids(
2186                &mut create_stmt,
2187                &[(old_id, new_id)].into(),
2188            );
2189            create_stmt.to_ast_string_stable()
2190        };
2191
2192        match self {
2193            CatalogItem::Table(i) => {
2194                let mut i = i.clone();
2195                i.create_sql = i.create_sql.map(do_rewrite);
2196                CatalogItem::Table(i)
2197            }
2198            CatalogItem::Log(i) => CatalogItem::Log(i.clone()),
2199            CatalogItem::Source(i) => {
2200                let mut i = i.clone();
2201                i.create_sql = i.create_sql.map(do_rewrite);
2202                CatalogItem::Source(i)
2203            }
2204            CatalogItem::Sink(i) => {
2205                let mut i = i.clone();
2206                i.create_sql = do_rewrite(i.create_sql);
2207                CatalogItem::Sink(i)
2208            }
2209            CatalogItem::View(i) => {
2210                let mut i = i.clone();
2211                i.create_sql = do_rewrite(i.create_sql);
2212                CatalogItem::View(i)
2213            }
2214            CatalogItem::MaterializedView(i) => {
2215                let mut i = i.clone();
2216                i.create_sql = do_rewrite(i.create_sql);
2217                CatalogItem::MaterializedView(i)
2218            }
2219            CatalogItem::Index(i) => {
2220                let mut i = i.clone();
2221                i.create_sql = do_rewrite(i.create_sql);
2222                CatalogItem::Index(i)
2223            }
2224            CatalogItem::Secret(i) => {
2225                let mut i = i.clone();
2226                i.create_sql = do_rewrite(i.create_sql);
2227                CatalogItem::Secret(i)
2228            }
2229            CatalogItem::Func(_) | CatalogItem::Type(_) => {
2230                unreachable!("references of {}s cannot be replaced", self.typ())
2231            }
2232            CatalogItem::Connection(i) => {
2233                let mut i = i.clone();
2234                i.create_sql = do_rewrite(i.create_sql);
2235                CatalogItem::Connection(i)
2236            }
2237        }
2238    }
2239    /// Updates the retain history for an item. Returns the previous retain history value. Returns
2240    /// an error if this item does not support retain history.
2241    pub fn update_retain_history(
2242        &mut self,
2243        value: Option<Value>,
2244        window: CompactionWindow,
2245    ) -> Result<Option<WithOptionValue<Raw>>, ()> {
2246        let update = |mut ast: &mut Statement<Raw>| {
2247            // Each statement type has unique option types. This macro handles them commonly.
2248            macro_rules! update_retain_history {
2249                ( $stmt:ident, $opt:ident, $name:ident ) => {{
2250                    // Replace or add the option.
2251                    let pos = $stmt
2252                        .with_options
2253                        .iter()
2254                        // In case there are ever multiple, look for the last one.
2255                        .rposition(|o| o.name == mz_sql_parser::ast::$name::RetainHistory);
2256                    if let Some(value) = value {
2257                        let next = mz_sql_parser::ast::$opt {
2258                            name: mz_sql_parser::ast::$name::RetainHistory,
2259                            value: Some(WithOptionValue::RetainHistoryFor(value)),
2260                        };
2261                        if let Some(idx) = pos {
2262                            let previous = $stmt.with_options[idx].clone();
2263                            $stmt.with_options[idx] = next;
2264                            previous.value
2265                        } else {
2266                            $stmt.with_options.push(next);
2267                            None
2268                        }
2269                    } else {
2270                        if let Some(idx) = pos {
2271                            $stmt.with_options.swap_remove(idx).value
2272                        } else {
2273                            None
2274                        }
2275                    }
2276                }};
2277            }
2278            let previous = match &mut ast {
2279                Statement::CreateTable(stmt) => {
2280                    update_retain_history!(stmt, TableOption, TableOptionName)
2281                }
2282                Statement::CreateIndex(stmt) => {
2283                    update_retain_history!(stmt, IndexOption, IndexOptionName)
2284                }
2285                Statement::CreateSource(stmt) => {
2286                    update_retain_history!(stmt, CreateSourceOption, CreateSourceOptionName)
2287                }
2288                Statement::CreateMaterializedView(stmt) => {
2289                    update_retain_history!(stmt, MaterializedViewOption, MaterializedViewOptionName)
2290                }
2291                _ => {
2292                    return Err(());
2293                }
2294            };
2295            Ok(previous)
2296        };
2297
2298        let res = self.update_sql(update)?;
2299        let cw = self
2300            .custom_logical_compaction_window_mut()
2301            .expect("item must have compaction window");
2302        *cw = Some(window);
2303        Ok(res)
2304    }
2305
2306    /// Updates the timestamp interval for a source. Returns the previous timestamp interval
2307    /// value, if any. Returns an error if this item is not a source.
2308    pub fn update_timestamp_interval(
2309        &mut self,
2310        value: Option<Value>,
2311        interval: Duration,
2312    ) -> Result<Option<WithOptionValue<Raw>>, ()> {
2313        let update = |ast: &mut Statement<Raw>| match ast {
2314            Statement::CreateSource(stmt) => {
2315                let pos = stmt.with_options.iter().rposition(|o| {
2316                    o.name == mz_sql_parser::ast::CreateSourceOptionName::TimestampInterval
2317                });
2318                let previous = if let Some(value) = value {
2319                    let next = mz_sql_parser::ast::CreateSourceOption {
2320                        name: mz_sql_parser::ast::CreateSourceOptionName::TimestampInterval,
2321                        value: Some(WithOptionValue::Value(value)),
2322                    };
2323                    if let Some(idx) = pos {
2324                        let previous = stmt.with_options[idx].clone();
2325                        stmt.with_options[idx] = next;
2326                        previous.value
2327                    } else {
2328                        stmt.with_options.push(next);
2329                        None
2330                    }
2331                } else if let Some(idx) = pos {
2332                    stmt.with_options.swap_remove(idx).value
2333                } else {
2334                    None
2335                };
2336                Ok(previous)
2337            }
2338            _ => Err(()),
2339        };
2340
2341        let previous = self.update_sql(update)?;
2342
2343        // Update the in-memory SourceDesc timestamp_interval.
2344        match self {
2345            CatalogItem::Source(source) => {
2346                match &mut source.data_source {
2347                    DataSourceDesc::Ingestion { desc, .. }
2348                    | DataSourceDesc::OldSyntaxIngestion { desc, .. } => {
2349                        desc.timestamp_interval = interval;
2350                    }
2351                    _ => return Err(()),
2352                }
2353                Ok(previous)
2354            }
2355            _ => Err(()),
2356        }
2357    }
2358
2359    pub fn add_column(
2360        &mut self,
2361        name: ColumnName,
2362        typ: SqlColumnType,
2363        sql: RawDataType,
2364    ) -> Result<RelationVersion, PlanError> {
2365        let CatalogItem::Table(table) = self else {
2366            return Err(PlanError::Unsupported {
2367                feature: "adding columns to a non-Table".to_string(),
2368                discussion_no: None,
2369            });
2370        };
2371        let next_version = table.desc.add_column(name.clone(), typ);
2372
2373        let update = |mut ast: &mut Statement<Raw>| match &mut ast {
2374            Statement::CreateTable(stmt) => {
2375                let version = ColumnOptionDef {
2376                    name: None,
2377                    option: ColumnOption::Versioned {
2378                        action: ColumnVersioned::Added,
2379                        version: next_version.into(),
2380                    },
2381                };
2382                let column = ColumnDef {
2383                    name: name.into(),
2384                    data_type: sql,
2385                    collation: None,
2386                    options: vec![version],
2387                };
2388                stmt.columns.push(column);
2389                Ok(())
2390            }
2391            _ => Err(()),
2392        };
2393
2394        self.update_sql(update)
2395            .map_err(|()| PlanError::Unstructured("expected CREATE TABLE statement".to_string()))?;
2396        Ok(next_version)
2397    }
2398
2399    /// Updates the create_sql field of this item. Returns an error if this is a builtin item,
2400    /// otherwise returns f's result.
2401    pub fn update_sql<F, T>(&mut self, f: F) -> Result<T, ()>
2402    where
2403        F: FnOnce(&mut Statement<Raw>) -> Result<T, ()>,
2404    {
2405        let create_sql = match self {
2406            CatalogItem::Table(Table { create_sql, .. })
2407            | CatalogItem::Type(Type { create_sql, .. })
2408            | CatalogItem::Source(Source { create_sql, .. }) => create_sql.as_mut(),
2409            CatalogItem::Sink(Sink { create_sql, .. })
2410            | CatalogItem::View(View { create_sql, .. })
2411            | CatalogItem::MaterializedView(MaterializedView { create_sql, .. })
2412            | CatalogItem::Index(Index { create_sql, .. })
2413            | CatalogItem::Secret(Secret { create_sql, .. })
2414            | CatalogItem::Connection(Connection { create_sql, .. }) => Some(create_sql),
2415            CatalogItem::Func(_) | CatalogItem::Log(_) => None,
2416        };
2417        let Some(create_sql) = create_sql else {
2418            return Err(());
2419        };
2420        let mut ast = mz_sql_parser::parser::parse_statements(create_sql)
2421            .expect("non-system items must be parseable")
2422            .into_element()
2423            .ast;
2424        debug!("rewrite: {}", ast.to_ast_string_redacted());
2425        let t = f(&mut ast)?;
2426        *create_sql = ast.to_ast_string_stable();
2427        debug!("rewrote: {}", ast.to_ast_string_redacted());
2428        Ok(t)
2429    }
2430
2431    /// If the object is considered a "compute object"
2432    /// (i.e., it is managed by the compute controller),
2433    /// this function returns its cluster ID. Otherwise, it returns nothing.
2434    ///
2435    /// This function differs from `cluster_id` because while all
2436    /// compute objects run on a cluster, the converse is not true.
2437    pub fn is_compute_object_on_cluster(&self) -> Option<ClusterId> {
2438        match self {
2439            CatalogItem::Index(index) => Some(index.cluster_id),
2440            CatalogItem::Table(_)
2441            | CatalogItem::Source(_)
2442            | CatalogItem::Log(_)
2443            | CatalogItem::View(_)
2444            | CatalogItem::MaterializedView(_)
2445            | CatalogItem::Sink(_)
2446            | CatalogItem::Type(_)
2447            | CatalogItem::Func(_)
2448            | CatalogItem::Secret(_)
2449            | CatalogItem::Connection(_) => None,
2450        }
2451    }
2452
2453    pub fn cluster_id(&self) -> Option<ClusterId> {
2454        match self {
2455            CatalogItem::MaterializedView(mv) => Some(mv.cluster_id),
2456            CatalogItem::Index(index) => Some(index.cluster_id),
2457            CatalogItem::Source(source) => match &source.data_source {
2458                DataSourceDesc::Ingestion { cluster_id, .. }
2459                | DataSourceDesc::OldSyntaxIngestion { cluster_id, .. } => Some(*cluster_id),
2460                // This is somewhat of a lie because the export runs on the same
2461                // cluster as its ingestion but we don't yet have a way of
2462                // cross-referencing the items
2463                DataSourceDesc::IngestionExport { .. } => None,
2464                DataSourceDesc::Webhook { cluster_id, .. } => Some(*cluster_id),
2465                DataSourceDesc::Introspection(_)
2466                | DataSourceDesc::Progress
2467                | DataSourceDesc::Catalog => None,
2468            },
2469            CatalogItem::Sink(sink) => Some(sink.cluster_id),
2470            CatalogItem::Table(_)
2471            | CatalogItem::Log(_)
2472            | CatalogItem::View(_)
2473            | CatalogItem::Type(_)
2474            | CatalogItem::Func(_)
2475            | CatalogItem::Secret(_)
2476            | CatalogItem::Connection(_) => None,
2477        }
2478    }
2479
2480    /// The custom compaction window, if any has been set. This does not reflect any propagated
2481    /// compaction window (i.e., source -> subsource).
2482    pub fn custom_logical_compaction_window(&self) -> Option<CompactionWindow> {
2483        match self {
2484            CatalogItem::Table(table) => table.custom_logical_compaction_window,
2485            CatalogItem::Source(source) => source.custom_logical_compaction_window,
2486            CatalogItem::Index(index) => index.custom_logical_compaction_window,
2487            CatalogItem::MaterializedView(mview) => mview.custom_logical_compaction_window,
2488            CatalogItem::Log(_)
2489            | CatalogItem::View(_)
2490            | CatalogItem::Sink(_)
2491            | CatalogItem::Type(_)
2492            | CatalogItem::Func(_)
2493            | CatalogItem::Secret(_)
2494            | CatalogItem::Connection(_) => None,
2495        }
2496    }
2497
2498    /// Mutable access to the custom compaction window, or None if this type does not support custom
2499    /// compaction windows. This does not reflect any propagated compaction window (i.e., source ->
2500    /// subsource).
2501    pub fn custom_logical_compaction_window_mut(
2502        &mut self,
2503    ) -> Option<&mut Option<CompactionWindow>> {
2504        let cw = match self {
2505            CatalogItem::Table(table) => &mut table.custom_logical_compaction_window,
2506            CatalogItem::Source(source) => &mut source.custom_logical_compaction_window,
2507            CatalogItem::Index(index) => &mut index.custom_logical_compaction_window,
2508            CatalogItem::MaterializedView(mview) => &mut mview.custom_logical_compaction_window,
2509            CatalogItem::Log(_)
2510            | CatalogItem::View(_)
2511            | CatalogItem::Sink(_)
2512            | CatalogItem::Type(_)
2513            | CatalogItem::Func(_)
2514            | CatalogItem::Secret(_)
2515            | CatalogItem::Connection(_) => return None,
2516        };
2517        Some(cw)
2518    }
2519
2520    /// The initial compaction window, for objects that have one; that is, tables, sources, indexes,
2521    /// and MVs. This does not reflect any propagated compaction window (i.e., source -> subsource).
2522    ///
2523    /// If `custom_logical_compaction_window()` returns something, use that.  Otherwise, use a
2524    /// sensible default (currently 1s).
2525    ///
2526    /// For objects that do not have the concept of compaction window, return None.
2527    pub fn initial_logical_compaction_window(&self) -> Option<CompactionWindow> {
2528        let custom_logical_compaction_window = match self {
2529            CatalogItem::Table(_)
2530            | CatalogItem::Source(_)
2531            | CatalogItem::Index(_)
2532            | CatalogItem::MaterializedView(_) => self.custom_logical_compaction_window(),
2533            CatalogItem::Log(_)
2534            | CatalogItem::View(_)
2535            | CatalogItem::Sink(_)
2536            | CatalogItem::Type(_)
2537            | CatalogItem::Func(_)
2538            | CatalogItem::Secret(_)
2539            | CatalogItem::Connection(_) => return None,
2540        };
2541        Some(custom_logical_compaction_window.unwrap_or(CompactionWindow::Default))
2542    }
2543
2544    /// Whether the item's logical compaction window
2545    /// is controlled by the METRICS_RETENTION
2546    /// system var.
2547    pub fn is_retained_metrics_object(&self) -> bool {
2548        match self {
2549            CatalogItem::Table(table) => table.is_retained_metrics_object,
2550            CatalogItem::Source(source) => source.is_retained_metrics_object,
2551            CatalogItem::Index(index) => index.is_retained_metrics_object,
2552            CatalogItem::Log(_)
2553            | CatalogItem::View(_)
2554            | CatalogItem::MaterializedView(_)
2555            | CatalogItem::Sink(_)
2556            | CatalogItem::Type(_)
2557            | CatalogItem::Func(_)
2558            | CatalogItem::Secret(_)
2559            | CatalogItem::Connection(_) => false,
2560        }
2561    }
2562
2563    pub fn to_serialized(&self) -> (String, GlobalId, BTreeMap<RelationVersion, GlobalId>) {
2564        match self {
2565            CatalogItem::Table(table) => {
2566                let create_sql = table
2567                    .create_sql
2568                    .clone()
2569                    .expect("builtin tables cannot be serialized");
2570                let mut collections = table.collections.clone();
2571                let global_id = collections
2572                    .remove(&RelationVersion::root())
2573                    .expect("at least one version");
2574                (create_sql, global_id, collections)
2575            }
2576            CatalogItem::Log(_) => unreachable!("builtin logs cannot be serialized"),
2577            CatalogItem::Source(source) => {
2578                assert!(
2579                    !matches!(source.data_source, DataSourceDesc::Introspection(_)),
2580                    "cannot serialize introspection/builtin sources",
2581                );
2582                let create_sql = source
2583                    .create_sql
2584                    .clone()
2585                    .expect("builtin sources cannot be serialized");
2586                (create_sql, source.global_id, BTreeMap::new())
2587            }
2588            CatalogItem::View(view) => (view.create_sql.clone(), view.global_id, BTreeMap::new()),
2589            CatalogItem::MaterializedView(mview) => {
2590                let mut collections = mview.collections.clone();
2591                let global_id = collections
2592                    .remove(&RelationVersion::root())
2593                    .expect("at least one version");
2594                (mview.create_sql.clone(), global_id, collections)
2595            }
2596            CatalogItem::Index(index) => {
2597                (index.create_sql.clone(), index.global_id, BTreeMap::new())
2598            }
2599            CatalogItem::Sink(sink) => (sink.create_sql.clone(), sink.global_id, BTreeMap::new()),
2600            CatalogItem::Type(typ) => {
2601                let create_sql = typ
2602                    .create_sql
2603                    .clone()
2604                    .expect("builtin types cannot be serialized");
2605                (create_sql, typ.global_id, BTreeMap::new())
2606            }
2607            CatalogItem::Secret(secret) => {
2608                (secret.create_sql.clone(), secret.global_id, BTreeMap::new())
2609            }
2610            CatalogItem::Connection(connection) => (
2611                connection.create_sql.clone(),
2612                connection.global_id,
2613                BTreeMap::new(),
2614            ),
2615            CatalogItem::Func(_) => unreachable!("cannot serialize functions yet"),
2616        }
2617    }
2618
2619    pub fn into_serialized(self) -> (String, GlobalId, BTreeMap<RelationVersion, GlobalId>) {
2620        match self {
2621            CatalogItem::Table(mut table) => {
2622                let create_sql = table
2623                    .create_sql
2624                    .expect("builtin tables cannot be serialized");
2625                let global_id = table
2626                    .collections
2627                    .remove(&RelationVersion::root())
2628                    .expect("at least one version");
2629                (create_sql, global_id, table.collections)
2630            }
2631            CatalogItem::Log(_) => unreachable!("builtin logs cannot be serialized"),
2632            CatalogItem::Source(source) => {
2633                assert!(
2634                    !matches!(source.data_source, DataSourceDesc::Introspection(_)),
2635                    "cannot serialize introspection/builtin sources",
2636                );
2637                let create_sql = source
2638                    .create_sql
2639                    .expect("builtin sources cannot be serialized");
2640                (create_sql, source.global_id, BTreeMap::new())
2641            }
2642            CatalogItem::View(view) => (view.create_sql, view.global_id, BTreeMap::new()),
2643            CatalogItem::MaterializedView(mut mview) => {
2644                let global_id = mview
2645                    .collections
2646                    .remove(&RelationVersion::root())
2647                    .expect("at least one version");
2648                (mview.create_sql, global_id, mview.collections)
2649            }
2650            CatalogItem::Index(index) => (index.create_sql, index.global_id, BTreeMap::new()),
2651            CatalogItem::Sink(sink) => (sink.create_sql, sink.global_id, BTreeMap::new()),
2652            CatalogItem::Type(typ) => {
2653                let create_sql = typ.create_sql.expect("builtin types cannot be serialized");
2654                (create_sql, typ.global_id, BTreeMap::new())
2655            }
2656            CatalogItem::Secret(secret) => (secret.create_sql, secret.global_id, BTreeMap::new()),
2657            CatalogItem::Connection(connection) => {
2658                (connection.create_sql, connection.global_id, BTreeMap::new())
2659            }
2660            CatalogItem::Func(_) => unreachable!("cannot serialize functions yet"),
2661        }
2662    }
2663
2664    /// Returns a global ID for a specific version selector. Returns `None` if the item does
2665    /// not have versions or if the version does not exist.
2666    pub fn global_id_for_version(&self, version: RelationVersionSelector) -> Option<GlobalId> {
2667        let collections = match self {
2668            CatalogItem::MaterializedView(mv) => &mv.collections,
2669            CatalogItem::Table(table) => &table.collections,
2670            CatalogItem::Source(source) => return Some(source.global_id),
2671            CatalogItem::Log(log) => return Some(log.global_id),
2672            CatalogItem::View(view) => return Some(view.global_id),
2673            CatalogItem::Sink(sink) => return Some(sink.global_id),
2674            CatalogItem::Index(index) => return Some(index.global_id),
2675            CatalogItem::Type(ty) => return Some(ty.global_id),
2676            CatalogItem::Func(func) => return Some(func.global_id),
2677            CatalogItem::Secret(secret) => return Some(secret.global_id),
2678            CatalogItem::Connection(conn) => return Some(conn.global_id),
2679        };
2680        match version {
2681            RelationVersionSelector::Latest => collections.values().last().copied(),
2682            RelationVersionSelector::Specific(version) => collections.get(&version).copied(),
2683        }
2684    }
2685}
2686
2687impl CatalogEntry {
2688    /// Reports the latest [`RelationDesc`] of the rows produced by this [`CatalogEntry`], if it
2689    /// produces rows.
2690    pub fn relation_desc_latest(&self) -> Option<Cow<'_, RelationDesc>> {
2691        self.item.relation_desc(RelationVersionSelector::Latest)
2692    }
2693
2694    /// Reports if the item has columns.
2695    pub fn has_columns(&self) -> bool {
2696        match self.item() {
2697            CatalogItem::Type(Type { details, .. }) => {
2698                matches!(details.typ, CatalogType::Record { .. })
2699            }
2700            _ => self.relation_desc_latest().is_some(),
2701        }
2702    }
2703
2704    /// Returns the [`mz_sql::func::Func`] associated with this `CatalogEntry`.
2705    pub fn func(&self) -> Result<&'static mz_sql::func::Func, SqlCatalogError> {
2706        self.item.func(self)
2707    }
2708
2709    /// Returns the inner [`Index`] if this entry is an index, else `None`.
2710    pub fn index(&self) -> Option<&Index> {
2711        match self.item() {
2712            CatalogItem::Index(idx) => Some(idx),
2713            _ => None,
2714        }
2715    }
2716
2717    /// Returns the inner [`MaterializedView`] if this entry is a materialized view, else `None`.
2718    pub fn materialized_view(&self) -> Option<&MaterializedView> {
2719        match self.item() {
2720            CatalogItem::MaterializedView(mv) => Some(mv),
2721            _ => None,
2722        }
2723    }
2724
2725    /// Returns the inner [`Table`] if this entry is a table, else `None`.
2726    pub fn table(&self) -> Option<&Table> {
2727        match self.item() {
2728            CatalogItem::Table(tbl) => Some(tbl),
2729            _ => None,
2730        }
2731    }
2732
2733    /// Returns the inner [`Source`] if this entry is a source, else `None`.
2734    pub fn source(&self) -> Option<&Source> {
2735        match self.item() {
2736            CatalogItem::Source(src) => Some(src),
2737            _ => None,
2738        }
2739    }
2740
2741    /// Returns the inner [`Sink`] if this entry is a sink, else `None`.
2742    pub fn sink(&self) -> Option<&Sink> {
2743        match self.item() {
2744            CatalogItem::Sink(sink) => Some(sink),
2745            _ => None,
2746        }
2747    }
2748
2749    /// Returns the inner [`Secret`] if this entry is a secret, else `None`.
2750    pub fn secret(&self) -> Option<&Secret> {
2751        match self.item() {
2752            CatalogItem::Secret(secret) => Some(secret),
2753            _ => None,
2754        }
2755    }
2756
2757    pub fn connection(&self) -> Result<&Connection, SqlCatalogError> {
2758        match self.item() {
2759            CatalogItem::Connection(connection) => Ok(connection),
2760            _ => {
2761                let db_name = match self.name().qualifiers.database_spec {
2762                    ResolvedDatabaseSpecifier::Ambient => "".to_string(),
2763                    ResolvedDatabaseSpecifier::Id(id) => format!("{id}."),
2764                };
2765                Err(SqlCatalogError::UnknownConnection(format!(
2766                    "{}{}.{}",
2767                    db_name,
2768                    self.name().qualifiers.schema_spec,
2769                    self.name().item
2770                )))
2771            }
2772        }
2773    }
2774
2775    /// Returns the [`mz_storage_types::sources::SourceDesc`] associated with
2776    /// this `CatalogEntry`, if any.
2777    pub fn source_desc(
2778        &self,
2779    ) -> Result<Option<&SourceDesc<ReferencedConnection>>, SqlCatalogError> {
2780        self.item.source_desc(self)
2781    }
2782
2783    /// Reports whether this catalog entry is a connection.
2784    pub fn is_connection(&self) -> bool {
2785        matches!(self.item(), CatalogItem::Connection(_))
2786    }
2787
2788    /// Reports whether this catalog entry is a table.
2789    pub fn is_table(&self) -> bool {
2790        matches!(self.item(), CatalogItem::Table(_))
2791    }
2792
2793    /// Reports whether this catalog entry is a source. Note that this includes
2794    /// subsources.
2795    pub fn is_source(&self) -> bool {
2796        matches!(self.item(), CatalogItem::Source(_))
2797    }
2798
2799    /// Reports whether this catalog entry is a subsource and, if it is, the
2800    /// ingestion it is an export of, as well as the item it exports.
2801    pub fn subsource_details(
2802        &self,
2803    ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)> {
2804        match &self.item() {
2805            CatalogItem::Source(source) => match &source.data_source {
2806                DataSourceDesc::IngestionExport {
2807                    ingestion_id,
2808                    external_reference,
2809                    details,
2810                    data_config: _,
2811                } => Some((*ingestion_id, external_reference, details)),
2812                _ => None,
2813            },
2814            _ => None,
2815        }
2816    }
2817
2818    /// Reports whether this catalog entry is a source export and, if it is, the
2819    /// ingestion it is an export of, as well as the item it exports.
2820    pub fn source_export_details(
2821        &self,
2822    ) -> Option<(
2823        CatalogItemId,
2824        &UnresolvedItemName,
2825        &SourceExportDetails,
2826        &SourceExportDataConfig<ReferencedConnection>,
2827    )> {
2828        match &self.item() {
2829            CatalogItem::Source(source) => match &source.data_source {
2830                DataSourceDesc::IngestionExport {
2831                    ingestion_id,
2832                    external_reference,
2833                    details,
2834                    data_config,
2835                } => Some((*ingestion_id, external_reference, details, data_config)),
2836                _ => None,
2837            },
2838            CatalogItem::Table(table) => match &table.data_source {
2839                TableDataSource::DataSource {
2840                    desc:
2841                        DataSourceDesc::IngestionExport {
2842                            ingestion_id,
2843                            external_reference,
2844                            details,
2845                            data_config,
2846                        },
2847                    timeline: _,
2848                } => Some((*ingestion_id, external_reference, details, data_config)),
2849                _ => None,
2850            },
2851            _ => None,
2852        }
2853    }
2854
2855    /// Reports whether this catalog entry is a progress source.
2856    pub fn is_progress_source(&self) -> bool {
2857        self.item().is_progress_source()
2858    }
2859
2860    /// Returns the `GlobalId` of all of this entry's progress ID.
2861    pub fn progress_id(&self) -> Option<CatalogItemId> {
2862        match &self.item() {
2863            CatalogItem::Source(source) => match &source.data_source {
2864                DataSourceDesc::Ingestion { .. } => Some(self.id),
2865                DataSourceDesc::OldSyntaxIngestion {
2866                    progress_subsource, ..
2867                } => Some(*progress_subsource),
2868                DataSourceDesc::IngestionExport { .. }
2869                | DataSourceDesc::Introspection(_)
2870                | DataSourceDesc::Progress
2871                | DataSourceDesc::Webhook { .. }
2872                | DataSourceDesc::Catalog => None,
2873            },
2874            CatalogItem::Table(_)
2875            | CatalogItem::Log(_)
2876            | CatalogItem::View(_)
2877            | CatalogItem::MaterializedView(_)
2878            | CatalogItem::Sink(_)
2879            | CatalogItem::Index(_)
2880            | CatalogItem::Type(_)
2881            | CatalogItem::Func(_)
2882            | CatalogItem::Secret(_)
2883            | CatalogItem::Connection(_) => None,
2884        }
2885    }
2886
2887    /// Reports whether this catalog entry is a sink.
2888    pub fn is_sink(&self) -> bool {
2889        matches!(self.item(), CatalogItem::Sink(_))
2890    }
2891
2892    /// Reports whether this catalog entry is a materialized view.
2893    pub fn is_materialized_view(&self) -> bool {
2894        matches!(self.item(), CatalogItem::MaterializedView(_))
2895    }
2896
2897    /// Reports whether this catalog entry is a view.
2898    pub fn is_view(&self) -> bool {
2899        matches!(self.item(), CatalogItem::View(_))
2900    }
2901
2902    /// Reports whether this catalog entry is a secret.
2903    pub fn is_secret(&self) -> bool {
2904        matches!(self.item(), CatalogItem::Secret(_))
2905    }
2906
2907    /// Reports whether this catalog entry is an introspection source.
2908    pub fn is_introspection_source(&self) -> bool {
2909        matches!(self.item(), CatalogItem::Log(_))
2910    }
2911
2912    /// Reports whether this catalog entry is an index.
2913    pub fn is_index(&self) -> bool {
2914        matches!(self.item(), CatalogItem::Index(_))
2915    }
2916
2917    /// Reports whether this catalog entry can be treated as a relation, it can produce rows.
2918    pub fn is_relation(&self) -> bool {
2919        mz_sql::catalog::ObjectType::from(self.item_type()).is_relation()
2920    }
2921
2922    /// Collects the identifiers of the objects that were encountered when
2923    /// resolving names in the item's DDL statement.
2924    pub fn references(&self) -> &ResolvedIds {
2925        self.item.references()
2926    }
2927
2928    /// Collects the identifiers of the objects used by this [`CatalogEntry`].
2929    ///
2930    /// Like [`CatalogEntry::references()`] but also includes objects that are not directly
2931    /// referenced. For example this will include any catalog objects used to implement functions
2932    /// and casts in the item.
2933    pub fn uses(&self) -> BTreeSet<CatalogItemId> {
2934        self.item.uses()
2935    }
2936
2937    /// Returns the `CatalogItem` associated with this catalog entry.
2938    pub fn item(&self) -> &CatalogItem {
2939        &self.item
2940    }
2941
2942    /// Returns a mutable reference to the `CatalogItem` associated with this
2943    /// catalog entry.
2944    pub fn item_mut(&mut self) -> &mut CatalogItem {
2945        &mut self.item
2946    }
2947
2948    /// Returns the [`CatalogItemId`] of this catalog entry.
2949    pub fn id(&self) -> CatalogItemId {
2950        self.id
2951    }
2952
2953    /// Returns all of the [`GlobalId`]s associated with this item.
2954    pub fn global_ids(&self) -> impl Iterator<Item = GlobalId> + '_ {
2955        self.item().global_ids()
2956    }
2957
2958    pub fn latest_global_id(&self) -> GlobalId {
2959        self.item().latest_global_id()
2960    }
2961
2962    /// Returns the OID of this catalog entry.
2963    pub fn oid(&self) -> u32 {
2964        self.oid
2965    }
2966
2967    /// Returns the fully qualified name of this catalog entry.
2968    pub fn name(&self) -> &QualifiedItemName {
2969        &self.name
2970    }
2971
2972    /// Returns the identifiers of the dataflows that are directly referenced by this dataflow.
2973    pub fn referenced_by(&self) -> &[CatalogItemId] {
2974        &self.referenced_by
2975    }
2976
2977    /// Returns the identifiers of the dataflows that depend upon this dataflow.
2978    pub fn used_by(&self) -> &[CatalogItemId] {
2979        &self.used_by
2980    }
2981
2982    /// Returns the connection ID that this item belongs to, if this item is
2983    /// temporary.
2984    pub fn conn_id(&self) -> Option<&ConnectionId> {
2985        self.item.conn_id()
2986    }
2987
2988    /// Returns the role ID of the entry owner.
2989    pub fn owner_id(&self) -> &RoleId {
2990        &self.owner_id
2991    }
2992
2993    /// Returns the privileges of the entry.
2994    pub fn privileges(&self) -> &PrivilegeMap {
2995        &self.privileges
2996    }
2997
2998    /// Returns the comment object ID for this entry.
2999    pub fn comment_object_id(&self) -> CommentObjectId {
3000        use CatalogItemType::*;
3001        match self.item_type() {
3002            Table => CommentObjectId::Table(self.id),
3003            Source => CommentObjectId::Source(self.id),
3004            Sink => CommentObjectId::Sink(self.id),
3005            View => CommentObjectId::View(self.id),
3006            MaterializedView => CommentObjectId::MaterializedView(self.id),
3007            Index => CommentObjectId::Index(self.id),
3008            Func => CommentObjectId::Func(self.id),
3009            Connection => CommentObjectId::Connection(self.id),
3010            Type => CommentObjectId::Type(self.id),
3011            Secret => CommentObjectId::Secret(self.id),
3012        }
3013    }
3014}
3015
3016#[derive(Debug, Clone, Default)]
3017pub struct CommentsMap {
3018    map: BTreeMap<CommentObjectId, BTreeMap<Option<usize>, String>>,
3019}
3020
3021impl CommentsMap {
3022    pub fn update_comment(
3023        &mut self,
3024        object_id: CommentObjectId,
3025        sub_component: Option<usize>,
3026        comment: Option<String>,
3027    ) -> Option<String> {
3028        let object_comments = self.map.entry(object_id).or_default();
3029
3030        // Either replace the existing comment, or remove it if comment is None/NULL.
3031        let (empty, prev) = if let Some(comment) = comment {
3032            let prev = object_comments.insert(sub_component, comment);
3033            (false, prev)
3034        } else {
3035            let prev = object_comments.remove(&sub_component);
3036            (object_comments.is_empty(), prev)
3037        };
3038
3039        // Cleanup entries that are now empty.
3040        if empty {
3041            self.map.remove(&object_id);
3042        }
3043
3044        // Return the previous comment, if there was one, for easy removal.
3045        prev
3046    }
3047
3048    /// Remove all comments for `object_id` from the map.
3049    ///
3050    /// Generally there is one comment for a given [`CommentObjectId`], but in the case of
3051    /// relations you can also have comments on the individual columns. Dropping the comments for a
3052    /// relation will also drop all of the comments on any columns.
3053    pub fn drop_comments(
3054        &mut self,
3055        object_ids: &BTreeSet<CommentObjectId>,
3056    ) -> Vec<(CommentObjectId, Option<usize>, String)> {
3057        let mut removed_comments = Vec::new();
3058
3059        for object_id in object_ids {
3060            if let Some(comments) = self.map.remove(object_id) {
3061                let removed = comments
3062                    .into_iter()
3063                    .map(|(sub_comp, comment)| (object_id.clone(), sub_comp, comment));
3064                removed_comments.extend(removed);
3065            }
3066        }
3067
3068        removed_comments
3069    }
3070
3071    pub fn iter(&self) -> impl Iterator<Item = (CommentObjectId, Option<usize>, &str)> {
3072        self.map
3073            .iter()
3074            .map(|(id, comments)| {
3075                comments
3076                    .iter()
3077                    .map(|(pos, comment)| (*id, *pos, comment.as_str()))
3078            })
3079            .flatten()
3080    }
3081
3082    pub fn get_object_comments(
3083        &self,
3084        object_id: CommentObjectId,
3085    ) -> Option<&BTreeMap<Option<usize>, String>> {
3086        self.map.get(&object_id)
3087    }
3088}
3089
3090impl Serialize for CommentsMap {
3091    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3092    where
3093        S: serde::Serializer,
3094    {
3095        let comment_count = self
3096            .map
3097            .iter()
3098            .map(|(_object_id, comments)| comments.len())
3099            .sum();
3100
3101        let mut seq = serializer.serialize_seq(Some(comment_count))?;
3102        for (object_id, sub) in &self.map {
3103            for (sub_component, comment) in sub {
3104                seq.serialize_element(&(
3105                    format!("{object_id:?}"),
3106                    format!("{sub_component:?}"),
3107                    comment,
3108                ))?;
3109            }
3110        }
3111        seq.end()
3112    }
3113}
3114
3115#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Default)]
3116pub struct DefaultPrivileges {
3117    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
3118    privileges: BTreeMap<DefaultPrivilegeObject, RoleDefaultPrivileges>,
3119}
3120
3121// Use a new type here because otherwise we have two levels of BTreeMap, both needing
3122// map_key_to_string.
3123#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Default)]
3124struct RoleDefaultPrivileges(
3125    /// Denormalized, the key is the grantee Role.
3126    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
3127    BTreeMap<RoleId, DefaultPrivilegeAclItem>,
3128);
3129
3130impl Deref for RoleDefaultPrivileges {
3131    type Target = BTreeMap<RoleId, DefaultPrivilegeAclItem>;
3132
3133    fn deref(&self) -> &Self::Target {
3134        &self.0
3135    }
3136}
3137
3138impl DerefMut for RoleDefaultPrivileges {
3139    fn deref_mut(&mut self) -> &mut Self::Target {
3140        &mut self.0
3141    }
3142}
3143
3144impl DefaultPrivileges {
3145    /// Add a new default privilege into the set of all default privileges.
3146    pub fn grant(&mut self, object: DefaultPrivilegeObject, privilege: DefaultPrivilegeAclItem) {
3147        if privilege.acl_mode.is_empty() {
3148            return;
3149        }
3150
3151        let privileges = self.privileges.entry(object).or_default();
3152        if let Some(default_privilege) = privileges.get_mut(&privilege.grantee) {
3153            default_privilege.acl_mode |= privilege.acl_mode;
3154        } else {
3155            privileges.insert(privilege.grantee, privilege);
3156        }
3157    }
3158
3159    /// Revoke a default privilege from the set of all default privileges.
3160    pub fn revoke(&mut self, object: &DefaultPrivilegeObject, privilege: &DefaultPrivilegeAclItem) {
3161        if let Some(privileges) = self.privileges.get_mut(object) {
3162            if let Some(default_privilege) = privileges.get_mut(&privilege.grantee) {
3163                default_privilege.acl_mode =
3164                    default_privilege.acl_mode.difference(privilege.acl_mode);
3165                if default_privilege.acl_mode.is_empty() {
3166                    privileges.remove(&privilege.grantee);
3167                }
3168            }
3169            if privileges.is_empty() {
3170                self.privileges.remove(object);
3171            }
3172        }
3173    }
3174
3175    /// Get the privileges that will be granted on all objects matching `object` to `grantee`, if
3176    /// any exist.
3177    pub fn get_privileges_for_grantee(
3178        &self,
3179        object: &DefaultPrivilegeObject,
3180        grantee: &RoleId,
3181    ) -> Option<&AclMode> {
3182        self.privileges
3183            .get(object)
3184            .and_then(|privileges| privileges.get(grantee))
3185            .map(|privilege| &privilege.acl_mode)
3186    }
3187
3188    /// Get all default privileges that apply to the provided object details.
3189    pub fn get_applicable_privileges(
3190        &self,
3191        role_id: RoleId,
3192        database_id: Option<DatabaseId>,
3193        schema_id: Option<SchemaId>,
3194        object_type: mz_sql::catalog::ObjectType,
3195    ) -> impl Iterator<Item = DefaultPrivilegeAclItem> + '_ {
3196        // Privileges consider all relations to be of type table due to PostgreSQL compatibility. We
3197        // don't require the caller to worry about that and we will map their `object_type` to the
3198        // correct type for privileges.
3199        let privilege_object_type = if object_type.is_relation() {
3200            mz_sql::catalog::ObjectType::Table
3201        } else {
3202            object_type
3203        };
3204        let valid_acl_mode = rbac::all_object_privileges(SystemObjectType::Object(object_type));
3205
3206        // Collect all entries that apply to the provided object details.
3207        // If either `database_id` or `schema_id` are `None`, then we might end up with duplicate
3208        // entries in the vec below. That's OK because we consolidate the results after.
3209        [
3210            DefaultPrivilegeObject {
3211                role_id,
3212                database_id,
3213                schema_id,
3214                object_type: privilege_object_type,
3215            },
3216            DefaultPrivilegeObject {
3217                role_id,
3218                database_id,
3219                schema_id: None,
3220                object_type: privilege_object_type,
3221            },
3222            DefaultPrivilegeObject {
3223                role_id,
3224                database_id: None,
3225                schema_id: None,
3226                object_type: privilege_object_type,
3227            },
3228            DefaultPrivilegeObject {
3229                role_id: RoleId::Public,
3230                database_id,
3231                schema_id,
3232                object_type: privilege_object_type,
3233            },
3234            DefaultPrivilegeObject {
3235                role_id: RoleId::Public,
3236                database_id,
3237                schema_id: None,
3238                object_type: privilege_object_type,
3239            },
3240            DefaultPrivilegeObject {
3241                role_id: RoleId::Public,
3242                database_id: None,
3243                schema_id: None,
3244                object_type: privilege_object_type,
3245            },
3246        ]
3247        .into_iter()
3248        .filter_map(|object| self.privileges.get(&object))
3249        .flat_map(|acl_map| acl_map.values())
3250        // Consolidate privileges with a common grantee.
3251        .fold(
3252            BTreeMap::new(),
3253            |mut accum, DefaultPrivilegeAclItem { grantee, acl_mode }| {
3254                let accum_acl_mode = accum.entry(grantee).or_insert_with(AclMode::empty);
3255                *accum_acl_mode |= *acl_mode;
3256                accum
3257            },
3258        )
3259        .into_iter()
3260        // Restrict the acl_mode to only privileges valid for the provided object type. If the
3261        // default privilege has an object type of Table, then it may contain privileges valid for
3262        // tables but not other relations. If the passed in object type is another relation, then
3263        // we need to remove any privilege that is not valid for the specified relation.
3264        .map(move |(grantee, acl_mode)| (grantee, acl_mode & valid_acl_mode))
3265        // Filter out empty privileges.
3266        .filter(|(_, acl_mode)| !acl_mode.is_empty())
3267        .map(|(grantee, acl_mode)| DefaultPrivilegeAclItem {
3268            grantee: *grantee,
3269            acl_mode,
3270        })
3271    }
3272
3273    pub fn iter(
3274        &self,
3275    ) -> impl Iterator<
3276        Item = (
3277            &DefaultPrivilegeObject,
3278            impl Iterator<Item = &DefaultPrivilegeAclItem>,
3279        ),
3280    > {
3281        self.privileges
3282            .iter()
3283            .map(|(object, acl_map)| (object, acl_map.values()))
3284    }
3285}
3286
3287#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3288pub struct ClusterConfig {
3289    pub variant: ClusterVariant,
3290    pub workload_class: Option<String>,
3291}
3292
3293impl ClusterConfig {
3294    pub fn features(&self) -> Option<&OptimizerFeatureOverrides> {
3295        match &self.variant {
3296            ClusterVariant::Managed(managed) => Some(&managed.optimizer_feature_overrides),
3297            ClusterVariant::Unmanaged => None,
3298        }
3299    }
3300}
3301
3302impl From<ClusterConfig> for durable::ClusterConfig {
3303    fn from(config: ClusterConfig) -> Self {
3304        Self {
3305            variant: config.variant.into(),
3306            workload_class: config.workload_class,
3307        }
3308    }
3309}
3310
3311impl From<durable::ClusterConfig> for ClusterConfig {
3312    fn from(config: durable::ClusterConfig) -> Self {
3313        Self {
3314            variant: config.variant.into(),
3315            workload_class: config.workload_class,
3316        }
3317    }
3318}
3319
3320#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3321pub struct ClusterVariantManaged {
3322    pub size: String,
3323    pub availability_zones: Vec<String>,
3324    pub logging: ReplicaLogging,
3325    pub replication_factor: u32,
3326    pub optimizer_feature_overrides: OptimizerFeatureOverrides,
3327    pub schedule: ClusterSchedule,
3328    /// User-configured autoscaling policy, distinct from the in-flight runtime
3329    /// records below. Shared with the durable layer, like [`ClusterSchedule`].
3330    pub auto_scaling_strategy: Option<AutoScalingStrategy>,
3331    /// In-flight graceful reconfiguration the controller is converging on.
3332    pub reconfiguration: Option<ReconfigurationState>,
3333    /// In-flight hydration burst the controller is running.
3334    pub burst: Option<BurstState>,
3335}
3336
3337impl From<ClusterVariantManaged> for durable::ClusterVariantManaged {
3338    fn from(managed: ClusterVariantManaged) -> Self {
3339        // Destructure the source (no `..`): a field added to either side is a
3340        // compile error here until it's carried across the boundary.
3341        let ClusterVariantManaged {
3342            size,
3343            availability_zones,
3344            logging,
3345            replication_factor,
3346            optimizer_feature_overrides,
3347            schedule,
3348            auto_scaling_strategy,
3349            reconfiguration,
3350            burst,
3351        } = managed;
3352        Self {
3353            size,
3354            availability_zones,
3355            logging,
3356            replication_factor,
3357            optimizer_feature_overrides: optimizer_feature_overrides.into(),
3358            schedule,
3359            auto_scaling_strategy,
3360            reconfiguration: reconfiguration.map(Into::into),
3361            burst: burst.map(Into::into),
3362        }
3363    }
3364}
3365
3366impl From<durable::ClusterVariantManaged> for ClusterVariantManaged {
3367    fn from(managed: durable::ClusterVariantManaged) -> Self {
3368        // Destructure the source (no `..`): a field added to either side is a
3369        // compile error here until it's carried across the boundary.
3370        let durable::ClusterVariantManaged {
3371            size,
3372            availability_zones,
3373            logging,
3374            replication_factor,
3375            optimizer_feature_overrides,
3376            schedule,
3377            auto_scaling_strategy,
3378            reconfiguration,
3379            burst,
3380        } = managed;
3381        Self {
3382            size,
3383            availability_zones,
3384            logging,
3385            replication_factor,
3386            optimizer_feature_overrides: optimizer_feature_overrides.into(),
3387            schedule,
3388            auto_scaling_strategy,
3389            reconfiguration: reconfiguration.map(Into::into),
3390            burst: burst.map(Into::into),
3391        }
3392    }
3393}
3394
3395/// In-memory mirror of [`durable::ReconfigurationState`].
3396///
3397/// This runtime state lives only in the durable layer, so the memory layer
3398/// carries its own `Serialize`/`Deserialize` mirror (to back the catalog
3399/// `dump()`) and converts across the boundary, rather than embedding the
3400/// durable-only type. The semantic contract lives on the durable type.
3401#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3402pub struct ReconfigurationState {
3403    pub target: ReconfigurationTarget,
3404    pub deadline: Timestamp,
3405    pub on_timeout: OnTimeoutAction,
3406}
3407
3408impl From<ReconfigurationState> for durable::ReconfigurationState {
3409    fn from(state: ReconfigurationState) -> Self {
3410        // Destructure the source (no `..`): a field added to either side is a
3411        // compile error here until it's carried across the boundary.
3412        let ReconfigurationState {
3413            target,
3414            deadline,
3415            on_timeout,
3416        } = state;
3417        Self {
3418            target: target.into(),
3419            deadline,
3420            on_timeout,
3421        }
3422    }
3423}
3424
3425impl From<durable::ReconfigurationState> for ReconfigurationState {
3426    fn from(state: durable::ReconfigurationState) -> Self {
3427        // Destructure the source (no `..`): a field added to either side is a
3428        // compile error here until it's carried across the boundary.
3429        let durable::ReconfigurationState {
3430            target,
3431            deadline,
3432            on_timeout,
3433        } = state;
3434        Self {
3435            target: target.into(),
3436            deadline,
3437            on_timeout,
3438        }
3439    }
3440}
3441
3442/// In-memory mirror of [`durable::ReconfigurationTarget`].
3443#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3444pub struct ReconfigurationTarget {
3445    pub size: String,
3446    pub replication_factor: u32,
3447    pub availability_zones: Vec<String>,
3448    pub logging: ReplicaLogging,
3449}
3450
3451impl From<ReconfigurationTarget> for durable::ReconfigurationTarget {
3452    fn from(target: ReconfigurationTarget) -> Self {
3453        // Destructure the source (no `..`): a field added to either side is a
3454        // compile error here until it's carried across the boundary.
3455        let ReconfigurationTarget {
3456            size,
3457            replication_factor,
3458            availability_zones,
3459            logging,
3460        } = target;
3461        Self {
3462            size,
3463            replication_factor,
3464            availability_zones,
3465            logging,
3466        }
3467    }
3468}
3469
3470impl From<durable::ReconfigurationTarget> for ReconfigurationTarget {
3471    fn from(target: durable::ReconfigurationTarget) -> Self {
3472        // Destructure the source (no `..`): a field added to either side is a
3473        // compile error here until it's carried across the boundary.
3474        let durable::ReconfigurationTarget {
3475            size,
3476            replication_factor,
3477            availability_zones,
3478            logging,
3479        } = target;
3480        Self {
3481            size,
3482            replication_factor,
3483            availability_zones,
3484            logging,
3485        }
3486    }
3487}
3488
3489/// In-memory mirror of [`durable::BurstState`].
3490#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3491pub struct BurstState {
3492    pub burst_size: String,
3493    pub linger_duration: Duration,
3494    pub steady_hydrated_at: Option<Timestamp>,
3495}
3496
3497impl From<BurstState> for durable::BurstState {
3498    fn from(burst: BurstState) -> Self {
3499        // Destructure the source (no `..`): a field added to either side is a
3500        // compile error here until it's carried across the boundary.
3501        let BurstState {
3502            burst_size,
3503            linger_duration,
3504            steady_hydrated_at,
3505        } = burst;
3506        Self {
3507            burst_size,
3508            linger_duration,
3509            steady_hydrated_at,
3510        }
3511    }
3512}
3513
3514impl From<durable::BurstState> for BurstState {
3515    fn from(burst: durable::BurstState) -> Self {
3516        // Destructure the source (no `..`): a field added to either side is a
3517        // compile error here until it's carried across the boundary.
3518        let durable::BurstState {
3519            burst_size,
3520            linger_duration,
3521            steady_hydrated_at,
3522        } = burst;
3523        Self {
3524            burst_size,
3525            linger_duration,
3526            steady_hydrated_at,
3527        }
3528    }
3529}
3530
3531#[derive(Clone, Debug, Deserialize, Serialize, PartialOrd, PartialEq, Eq, Ord)]
3532pub enum ClusterVariant {
3533    Managed(ClusterVariantManaged),
3534    Unmanaged,
3535}
3536
3537impl From<ClusterVariant> for durable::ClusterVariant {
3538    fn from(variant: ClusterVariant) -> Self {
3539        match variant {
3540            ClusterVariant::Managed(managed) => Self::Managed(managed.into()),
3541            ClusterVariant::Unmanaged => Self::Unmanaged,
3542        }
3543    }
3544}
3545
3546impl From<durable::ClusterVariant> for ClusterVariant {
3547    fn from(variant: durable::ClusterVariant) -> Self {
3548        match variant {
3549            durable::ClusterVariant::Managed(managed) => Self::Managed(managed.into()),
3550            durable::ClusterVariant::Unmanaged => Self::Unmanaged,
3551        }
3552    }
3553}
3554
3555impl mz_sql::catalog::CatalogDatabase for Database {
3556    fn name(&self) -> &str {
3557        &self.name
3558    }
3559
3560    fn id(&self) -> DatabaseId {
3561        self.id
3562    }
3563
3564    fn has_schemas(&self) -> bool {
3565        !self.schemas_by_name.is_empty()
3566    }
3567
3568    fn schema_ids(&self) -> &BTreeMap<String, SchemaId> {
3569        &self.schemas_by_name
3570    }
3571
3572    // `as` is ok to use to cast to a trait object.
3573    #[allow(clippy::as_conversions)]
3574    fn schemas(&self) -> Vec<&dyn CatalogSchema> {
3575        self.schemas_by_id
3576            .values()
3577            .map(|schema| schema as &dyn CatalogSchema)
3578            .collect()
3579    }
3580
3581    fn owner_id(&self) -> RoleId {
3582        self.owner_id
3583    }
3584
3585    fn privileges(&self) -> &PrivilegeMap {
3586        &self.privileges
3587    }
3588}
3589
3590impl mz_sql::catalog::CatalogSchema for Schema {
3591    fn database(&self) -> &ResolvedDatabaseSpecifier {
3592        &self.name.database
3593    }
3594
3595    fn name(&self) -> &QualifiedSchemaName {
3596        &self.name
3597    }
3598
3599    fn id(&self) -> &SchemaSpecifier {
3600        &self.id
3601    }
3602
3603    fn has_items(&self) -> bool {
3604        // A schema holds items, types, and functions in separate maps (see
3605        // `item_ids`). All three keep the schema non-empty, so e.g. DROP SCHEMA
3606        // without CASCADE must be rejected when only a type or function remains.
3607        !self.items.is_empty() || !self.types.is_empty() || !self.functions.is_empty()
3608    }
3609
3610    fn item_ids(&self) -> Box<dyn Iterator<Item = CatalogItemId> + '_> {
3611        Box::new(
3612            self.items
3613                .values()
3614                .chain(self.functions.values())
3615                .chain(self.types.values())
3616                .copied(),
3617        )
3618    }
3619
3620    fn owner_id(&self) -> RoleId {
3621        self.owner_id
3622    }
3623
3624    fn privileges(&self) -> &PrivilegeMap {
3625        &self.privileges
3626    }
3627}
3628
3629impl mz_sql::catalog::CatalogRole for Role {
3630    fn name(&self) -> &str {
3631        &self.name
3632    }
3633
3634    fn id(&self) -> RoleId {
3635        self.id
3636    }
3637
3638    fn membership(&self) -> &BTreeMap<RoleId, RoleId> {
3639        &self.membership.map
3640    }
3641
3642    fn attributes(&self) -> &RoleAttributes {
3643        &self.attributes
3644    }
3645
3646    fn vars(&self) -> &BTreeMap<String, OwnedVarInput> {
3647        &self.vars.map
3648    }
3649}
3650
3651impl mz_sql::catalog::CatalogNetworkPolicy for NetworkPolicy {
3652    fn name(&self) -> &str {
3653        &self.name
3654    }
3655
3656    fn id(&self) -> NetworkPolicyId {
3657        self.id
3658    }
3659
3660    fn owner_id(&self) -> RoleId {
3661        self.owner_id
3662    }
3663
3664    fn privileges(&self) -> &PrivilegeMap {
3665        &self.privileges
3666    }
3667}
3668
3669impl mz_sql::catalog::CatalogCluster<'_> for Cluster {
3670    fn name(&self) -> &str {
3671        &self.name
3672    }
3673
3674    fn id(&self) -> ClusterId {
3675        self.id
3676    }
3677
3678    fn bound_objects(&self) -> &BTreeSet<CatalogItemId> {
3679        &self.bound_objects
3680    }
3681
3682    fn replica_ids(&self) -> &BTreeMap<String, ReplicaId> {
3683        &self.replica_id_by_name_
3684    }
3685
3686    // `as` is ok to use to cast to a trait object.
3687    #[allow(clippy::as_conversions)]
3688    fn replicas(&self) -> Vec<&dyn CatalogClusterReplica<'_>> {
3689        self.replicas()
3690            .map(|replica| replica as &dyn CatalogClusterReplica)
3691            .collect()
3692    }
3693
3694    fn replica(&self, id: ReplicaId) -> &dyn CatalogClusterReplica<'_> {
3695        self.replica(id).expect("catalog out of sync")
3696    }
3697
3698    fn owner_id(&self) -> RoleId {
3699        self.owner_id
3700    }
3701
3702    fn privileges(&self) -> &PrivilegeMap {
3703        &self.privileges
3704    }
3705
3706    fn is_managed(&self) -> bool {
3707        self.is_managed()
3708    }
3709
3710    fn managed_size(&self) -> Option<&str> {
3711        match &self.config.variant {
3712            ClusterVariant::Managed(ClusterVariantManaged { size, .. }) => Some(size),
3713            ClusterVariant::Unmanaged => None,
3714        }
3715    }
3716
3717    fn schedule(&self) -> Option<&ClusterSchedule> {
3718        match &self.config.variant {
3719            ClusterVariant::Managed(ClusterVariantManaged { schedule, .. }) => Some(schedule),
3720            ClusterVariant::Unmanaged => None,
3721        }
3722    }
3723
3724    fn replication_factor(&self) -> Option<u32> {
3725        match &self.config.variant {
3726            ClusterVariant::Managed(ClusterVariantManaged {
3727                replication_factor, ..
3728            }) => Some(*replication_factor),
3729            ClusterVariant::Unmanaged => None,
3730        }
3731    }
3732
3733    fn try_to_plan(&self) -> Result<CreateClusterPlan, PlanError> {
3734        self.try_to_plan()
3735    }
3736}
3737
3738impl mz_sql::catalog::CatalogClusterReplica<'_> for ClusterReplica {
3739    fn name(&self) -> &str {
3740        &self.name
3741    }
3742
3743    fn cluster_id(&self) -> ClusterId {
3744        self.cluster_id
3745    }
3746
3747    fn replica_id(&self) -> ReplicaId {
3748        self.replica_id
3749    }
3750
3751    fn owner_id(&self) -> RoleId {
3752        self.owner_id
3753    }
3754
3755    fn internal(&self) -> bool {
3756        self.config.location.internal()
3757    }
3758}
3759
3760impl mz_sql::catalog::CatalogItem for CatalogEntry {
3761    fn name(&self) -> &QualifiedItemName {
3762        self.name()
3763    }
3764
3765    fn id(&self) -> CatalogItemId {
3766        self.id()
3767    }
3768
3769    fn global_ids(&self) -> Box<dyn Iterator<Item = GlobalId> + '_> {
3770        Box::new(self.global_ids())
3771    }
3772
3773    fn oid(&self) -> u32 {
3774        self.oid()
3775    }
3776
3777    fn func(&self) -> Result<&'static mz_sql::func::Func, SqlCatalogError> {
3778        self.func()
3779    }
3780
3781    fn source_desc(&self) -> Result<Option<&SourceDesc<ReferencedConnection>>, SqlCatalogError> {
3782        self.source_desc()
3783    }
3784
3785    fn connection(
3786        &self,
3787    ) -> Result<mz_storage_types::connections::Connection<ReferencedConnection>, SqlCatalogError>
3788    {
3789        Ok(self.connection()?.details.to_connection())
3790    }
3791
3792    fn create_sql(&self) -> &str {
3793        match self.item() {
3794            CatalogItem::Table(Table { create_sql, .. }) => {
3795                create_sql.as_deref().unwrap_or("<builtin>")
3796            }
3797            CatalogItem::Source(Source { create_sql, .. }) => {
3798                create_sql.as_deref().unwrap_or("<builtin>")
3799            }
3800            CatalogItem::Sink(Sink { create_sql, .. }) => create_sql,
3801            CatalogItem::View(View { create_sql, .. }) => create_sql,
3802            CatalogItem::MaterializedView(MaterializedView { create_sql, .. }) => create_sql,
3803            CatalogItem::Index(Index { create_sql, .. }) => create_sql,
3804            CatalogItem::Type(Type { create_sql, .. }) => {
3805                create_sql.as_deref().unwrap_or("<builtin>")
3806            }
3807            CatalogItem::Secret(Secret { create_sql, .. }) => create_sql,
3808            CatalogItem::Connection(Connection { create_sql, .. }) => create_sql,
3809            CatalogItem::Func(_) => "<builtin>",
3810            CatalogItem::Log(_) => "<builtin>",
3811        }
3812    }
3813
3814    fn item_type(&self) -> SqlCatalogItemType {
3815        self.item().typ()
3816    }
3817
3818    fn index_details(&self) -> Option<(&[MirScalarExpr], GlobalId)> {
3819        if let CatalogItem::Index(Index { keys, on, .. }) = self.item() {
3820            Some((keys, *on))
3821        } else {
3822            None
3823        }
3824    }
3825
3826    fn writable_table_details(&self) -> Option<&[Expr<Aug>]> {
3827        if let CatalogItem::Table(Table {
3828            data_source: TableDataSource::TableWrites { defaults },
3829            ..
3830        }) = self.item()
3831        {
3832            Some(defaults.as_slice())
3833        } else {
3834            None
3835        }
3836    }
3837
3838    fn replacement_target(&self) -> Option<CatalogItemId> {
3839        if let CatalogItem::MaterializedView(mv) = self.item() {
3840            mv.replacement_target
3841        } else {
3842            None
3843        }
3844    }
3845
3846    fn type_details(&self) -> Option<&CatalogTypeDetails<IdReference>> {
3847        if let CatalogItem::Type(Type { details, .. }) = self.item() {
3848            Some(details)
3849        } else {
3850            None
3851        }
3852    }
3853
3854    fn references(&self) -> &ResolvedIds {
3855        self.references()
3856    }
3857
3858    fn uses(&self) -> BTreeSet<CatalogItemId> {
3859        self.uses()
3860    }
3861
3862    fn referenced_by(&self) -> &[CatalogItemId] {
3863        self.referenced_by()
3864    }
3865
3866    fn used_by(&self) -> &[CatalogItemId] {
3867        self.used_by()
3868    }
3869
3870    fn subsource_details(
3871        &self,
3872    ) -> Option<(CatalogItemId, &UnresolvedItemName, &SourceExportDetails)> {
3873        self.subsource_details()
3874    }
3875
3876    fn source_export_details(
3877        &self,
3878    ) -> Option<(
3879        CatalogItemId,
3880        &UnresolvedItemName,
3881        &SourceExportDetails,
3882        &SourceExportDataConfig<ReferencedConnection>,
3883    )> {
3884        self.source_export_details()
3885    }
3886
3887    fn is_progress_source(&self) -> bool {
3888        self.is_progress_source()
3889    }
3890
3891    fn progress_id(&self) -> Option<CatalogItemId> {
3892        self.progress_id()
3893    }
3894
3895    fn owner_id(&self) -> RoleId {
3896        self.owner_id
3897    }
3898
3899    fn privileges(&self) -> &PrivilegeMap {
3900        &self.privileges
3901    }
3902
3903    fn cluster_id(&self) -> Option<ClusterId> {
3904        self.item().cluster_id()
3905    }
3906
3907    fn at_version(
3908        &self,
3909        version: RelationVersionSelector,
3910    ) -> Box<dyn mz_sql::catalog::CatalogCollectionItem> {
3911        Box::new(CatalogCollectionEntry {
3912            entry: self.clone(),
3913            version,
3914        })
3915    }
3916
3917    fn latest_version(&self) -> Option<RelationVersion> {
3918        self.table().map(|t| t.desc.latest_version())
3919    }
3920}
3921
3922/// A single update to the catalog state.
3923#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
3924pub struct StateUpdate {
3925    pub kind: StateUpdateKind,
3926    pub ts: Timestamp,
3927    pub diff: StateDiff,
3928}
3929
3930/// The contents of a single state update.
3931///
3932/// Variants are listed in dependency order.
3933#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
3934pub enum StateUpdateKind {
3935    Role(durable::objects::Role),
3936    RoleAuth(durable::objects::RoleAuth),
3937    Database(durable::objects::Database),
3938    Schema(durable::objects::Schema),
3939    DefaultPrivilege(durable::objects::DefaultPrivilege),
3940    SystemPrivilege(MzAclItem),
3941    SystemConfiguration(durable::objects::SystemConfiguration),
3942    Cluster(durable::objects::Cluster),
3943    ClusterSystemConfiguration(durable::objects::ClusterSystemConfiguration),
3944    NetworkPolicy(durable::objects::NetworkPolicy),
3945    IntrospectionSourceIndex(durable::objects::IntrospectionSourceIndex),
3946    ClusterReplica(durable::objects::ClusterReplica),
3947    ReplicaSystemConfiguration(durable::objects::ReplicaSystemConfiguration),
3948    SourceReferences(durable::objects::SourceReferences),
3949    SystemObjectMapping(durable::objects::SystemObjectMapping),
3950    // Temporary items are not actually updated via the durable catalog, but
3951    // this allows us to model them the same way as all other items in parts of
3952    // the pipeline.
3953    TemporaryItem(TemporaryItem),
3954    Item(durable::objects::Item),
3955    Comment(durable::objects::Comment),
3956    AuditLog(durable::objects::AuditLog),
3957    // Storage updates.
3958    StorageCollectionMetadata(durable::objects::StorageCollectionMetadata),
3959    UnfinalizedShard(durable::objects::UnfinalizedShard),
3960}
3961
3962/// Valid diffs for catalog state updates.
3963#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
3964pub enum StateDiff {
3965    Retraction,
3966    Addition,
3967}
3968
3969impl From<StateDiff> for Diff {
3970    fn from(diff: StateDiff) -> Self {
3971        match diff {
3972            StateDiff::Retraction => Diff::MINUS_ONE,
3973            StateDiff::Addition => Diff::ONE,
3974        }
3975    }
3976}
3977impl TryFrom<Diff> for StateDiff {
3978    type Error = String;
3979
3980    fn try_from(diff: Diff) -> Result<Self, Self::Error> {
3981        match diff {
3982            Diff::MINUS_ONE => Ok(Self::Retraction),
3983            Diff::ONE => Ok(Self::Addition),
3984            diff => Err(format!("invalid diff {diff}")),
3985        }
3986    }
3987}
3988
3989/// Information needed to process an update to a temporary item.
3990#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
3991pub struct TemporaryItem {
3992    pub id: CatalogItemId,
3993    pub oid: u32,
3994    pub global_id: GlobalId,
3995    pub schema_id: SchemaId,
3996    pub name: String,
3997    pub conn_id: Option<ConnectionId>,
3998    pub create_sql: String,
3999    pub owner_id: RoleId,
4000    pub privileges: Vec<MzAclItem>,
4001    pub extra_versions: BTreeMap<RelationVersion, GlobalId>,
4002}
4003
4004impl From<CatalogEntry> for TemporaryItem {
4005    fn from(entry: CatalogEntry) -> Self {
4006        let conn_id = entry.conn_id().cloned();
4007        let (create_sql, global_id, extra_versions) = entry.item.to_serialized();
4008
4009        TemporaryItem {
4010            id: entry.id,
4011            oid: entry.oid,
4012            global_id,
4013            schema_id: entry.name.qualifiers.schema_spec.into(),
4014            name: entry.name.item,
4015            conn_id,
4016            create_sql,
4017            owner_id: entry.owner_id,
4018            privileges: entry.privileges.into_all_values().collect(),
4019            extra_versions,
4020        }
4021    }
4022}
4023
4024impl TemporaryItem {
4025    pub fn item_type(&self) -> CatalogItemType {
4026        item_type(&self.create_sql)
4027    }
4028}
4029
4030/// The same as [`StateUpdateKind`], but without `TemporaryItem` so we can derive [`Ord`].
4031#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
4032pub enum BootstrapStateUpdateKind {
4033    Role(durable::objects::Role),
4034    RoleAuth(durable::objects::RoleAuth),
4035    Database(durable::objects::Database),
4036    Schema(durable::objects::Schema),
4037    DefaultPrivilege(durable::objects::DefaultPrivilege),
4038    SystemPrivilege(MzAclItem),
4039    SystemConfiguration(durable::objects::SystemConfiguration),
4040    Cluster(durable::objects::Cluster),
4041    ClusterSystemConfiguration(durable::objects::ClusterSystemConfiguration),
4042    NetworkPolicy(durable::objects::NetworkPolicy),
4043    IntrospectionSourceIndex(durable::objects::IntrospectionSourceIndex),
4044    ClusterReplica(durable::objects::ClusterReplica),
4045    ReplicaSystemConfiguration(durable::objects::ReplicaSystemConfiguration),
4046    SourceReferences(durable::objects::SourceReferences),
4047    SystemObjectMapping(durable::objects::SystemObjectMapping),
4048    Item(durable::objects::Item),
4049    Comment(durable::objects::Comment),
4050    AuditLog(durable::objects::AuditLog),
4051    // Storage updates.
4052    StorageCollectionMetadata(durable::objects::StorageCollectionMetadata),
4053    UnfinalizedShard(durable::objects::UnfinalizedShard),
4054}
4055
4056impl From<BootstrapStateUpdateKind> for StateUpdateKind {
4057    fn from(value: BootstrapStateUpdateKind) -> Self {
4058        match value {
4059            BootstrapStateUpdateKind::Role(kind) => StateUpdateKind::Role(kind),
4060            BootstrapStateUpdateKind::RoleAuth(kind) => StateUpdateKind::RoleAuth(kind),
4061            BootstrapStateUpdateKind::Database(kind) => StateUpdateKind::Database(kind),
4062            BootstrapStateUpdateKind::Schema(kind) => StateUpdateKind::Schema(kind),
4063            BootstrapStateUpdateKind::DefaultPrivilege(kind) => {
4064                StateUpdateKind::DefaultPrivilege(kind)
4065            }
4066            BootstrapStateUpdateKind::SystemPrivilege(kind) => {
4067                StateUpdateKind::SystemPrivilege(kind)
4068            }
4069            BootstrapStateUpdateKind::SystemConfiguration(kind) => {
4070                StateUpdateKind::SystemConfiguration(kind)
4071            }
4072            BootstrapStateUpdateKind::ClusterSystemConfiguration(kind) => {
4073                StateUpdateKind::ClusterSystemConfiguration(kind)
4074            }
4075            BootstrapStateUpdateKind::ReplicaSystemConfiguration(kind) => {
4076                StateUpdateKind::ReplicaSystemConfiguration(kind)
4077            }
4078            BootstrapStateUpdateKind::SourceReferences(kind) => {
4079                StateUpdateKind::SourceReferences(kind)
4080            }
4081            BootstrapStateUpdateKind::Cluster(kind) => StateUpdateKind::Cluster(kind),
4082            BootstrapStateUpdateKind::NetworkPolicy(kind) => StateUpdateKind::NetworkPolicy(kind),
4083            BootstrapStateUpdateKind::IntrospectionSourceIndex(kind) => {
4084                StateUpdateKind::IntrospectionSourceIndex(kind)
4085            }
4086            BootstrapStateUpdateKind::ClusterReplica(kind) => StateUpdateKind::ClusterReplica(kind),
4087            BootstrapStateUpdateKind::SystemObjectMapping(kind) => {
4088                StateUpdateKind::SystemObjectMapping(kind)
4089            }
4090            BootstrapStateUpdateKind::Item(kind) => StateUpdateKind::Item(kind),
4091            BootstrapStateUpdateKind::Comment(kind) => StateUpdateKind::Comment(kind),
4092            BootstrapStateUpdateKind::AuditLog(kind) => StateUpdateKind::AuditLog(kind),
4093            BootstrapStateUpdateKind::StorageCollectionMetadata(kind) => {
4094                StateUpdateKind::StorageCollectionMetadata(kind)
4095            }
4096            BootstrapStateUpdateKind::UnfinalizedShard(kind) => {
4097                StateUpdateKind::UnfinalizedShard(kind)
4098            }
4099        }
4100    }
4101}
4102
4103impl TryFrom<StateUpdateKind> for BootstrapStateUpdateKind {
4104    type Error = TemporaryItem;
4105
4106    fn try_from(value: StateUpdateKind) -> Result<Self, Self::Error> {
4107        match value {
4108            StateUpdateKind::Role(kind) => Ok(BootstrapStateUpdateKind::Role(kind)),
4109            StateUpdateKind::RoleAuth(kind) => Ok(BootstrapStateUpdateKind::RoleAuth(kind)),
4110            StateUpdateKind::Database(kind) => Ok(BootstrapStateUpdateKind::Database(kind)),
4111            StateUpdateKind::Schema(kind) => Ok(BootstrapStateUpdateKind::Schema(kind)),
4112            StateUpdateKind::DefaultPrivilege(kind) => {
4113                Ok(BootstrapStateUpdateKind::DefaultPrivilege(kind))
4114            }
4115            StateUpdateKind::SystemPrivilege(kind) => {
4116                Ok(BootstrapStateUpdateKind::SystemPrivilege(kind))
4117            }
4118            StateUpdateKind::SystemConfiguration(kind) => {
4119                Ok(BootstrapStateUpdateKind::SystemConfiguration(kind))
4120            }
4121            StateUpdateKind::ClusterSystemConfiguration(kind) => {
4122                Ok(BootstrapStateUpdateKind::ClusterSystemConfiguration(kind))
4123            }
4124            StateUpdateKind::ReplicaSystemConfiguration(kind) => {
4125                Ok(BootstrapStateUpdateKind::ReplicaSystemConfiguration(kind))
4126            }
4127            StateUpdateKind::Cluster(kind) => Ok(BootstrapStateUpdateKind::Cluster(kind)),
4128            StateUpdateKind::NetworkPolicy(kind) => {
4129                Ok(BootstrapStateUpdateKind::NetworkPolicy(kind))
4130            }
4131            StateUpdateKind::IntrospectionSourceIndex(kind) => {
4132                Ok(BootstrapStateUpdateKind::IntrospectionSourceIndex(kind))
4133            }
4134            StateUpdateKind::ClusterReplica(kind) => {
4135                Ok(BootstrapStateUpdateKind::ClusterReplica(kind))
4136            }
4137            StateUpdateKind::SourceReferences(kind) => {
4138                Ok(BootstrapStateUpdateKind::SourceReferences(kind))
4139            }
4140            StateUpdateKind::SystemObjectMapping(kind) => {
4141                Ok(BootstrapStateUpdateKind::SystemObjectMapping(kind))
4142            }
4143            StateUpdateKind::TemporaryItem(kind) => Err(kind),
4144            StateUpdateKind::Item(kind) => Ok(BootstrapStateUpdateKind::Item(kind)),
4145            StateUpdateKind::Comment(kind) => Ok(BootstrapStateUpdateKind::Comment(kind)),
4146            StateUpdateKind::AuditLog(kind) => Ok(BootstrapStateUpdateKind::AuditLog(kind)),
4147            StateUpdateKind::StorageCollectionMetadata(kind) => {
4148                Ok(BootstrapStateUpdateKind::StorageCollectionMetadata(kind))
4149            }
4150            StateUpdateKind::UnfinalizedShard(kind) => {
4151                Ok(BootstrapStateUpdateKind::UnfinalizedShard(kind))
4152            }
4153        }
4154    }
4155}