Skip to main content

mz_catalog/
builtin.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//! Built-in catalog items.
11//!
12//! Builtins exist in the `mz_catalog` ambient schema. They are automatically
13//! installed into the catalog when it is opened. Their definitions are not
14//! persisted in the catalog, but hardcoded in this module. This makes it easy
15//! to add new builtins, or change the definition of existing builtins, in new
16//! versions of Materialize.
17//!
18//! Builtin's names, columns, and types are part of the stable API of
19//! Materialize. Be careful to maintain backwards compatibility when changing
20//! definitions of existing builtins!
21//!
22//! More information about builtin system tables and types can be found in
23//! <https://materialize.com/docs/sql/system-catalog/>.
24
25mod builtin;
26pub mod notice;
27mod ontology;
28mod pg_catalog;
29pub use pg_catalog::*;
30mod mz_catalog;
31pub use mz_catalog::*;
32mod mz_internal;
33pub use mz_internal::*;
34mod mz_object_dependencies;
35pub use mz_object_dependencies::*;
36mod mz_introspection;
37pub use mz_introspection::*;
38mod information_schema;
39pub use information_schema::*;
40
41use std::collections::BTreeMap;
42use std::hash::Hash;
43use std::string::ToString;
44use std::sync::LazyLock;
45
46use mz_compute_client::logging::LogVariant;
47use mz_ore::collections::HashMap;
48use mz_pgrepr::oid;
49use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem};
50use mz_repr::namespaces::{
51    INFORMATION_SCHEMA, MZ_CATALOG_SCHEMA, MZ_INTERNAL_SCHEMA, MZ_UNSAFE_SCHEMA, PG_CATALOG_SCHEMA,
52};
53use mz_repr::role_id::RoleId;
54use mz_repr::{RelationDesc, SemanticType, SqlRelationType};
55use mz_sql::catalog::RoleAttributesRaw;
56use mz_sql::catalog::{
57    CatalogItemType, CatalogTypeDetails, NameReference, ObjectType, SystemObjectType, TypeReference,
58};
59use mz_sql::rbac;
60use mz_sql::session::user::{
61    ANALYTICS_USER_NAME, JWT_SYNC_ROLE_NAME, MZ_ANALYTICS_ROLE_ID, MZ_JWT_SYNC_ROLE_ID,
62    MZ_MONITOR_REDACTED_ROLE_ID, MZ_MONITOR_ROLE_ID, MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID,
63    SUPPORT_USER_NAME, SYSTEM_USER_NAME,
64};
65use serde::Serialize;
66
67use crate::durable::objects::SystemObjectDescription;
68use crate::memory::objects::DataSourceDesc;
69
70pub const BUILTIN_PREFIXES: &[&str] = &["mz_", "pg_", "external_"];
71
72/// A sentinel used in place of a fingerprint that indicates that a builtin
73/// object is runtime alterable. Runtime alterable objects don't have meaningful
74/// fingerprints because they may have been intentionally changed by the user
75/// after creation.
76// NOTE(benesch): ideally we'd use a fingerprint type that used a sum type
77// rather than a loosely typed string to represent the runtime alterable
78// state like so:
79//
80//     enum Fingerprint {
81//         SqlText(String),
82//         RuntimeAlterable,
83//     }
84//
85// However, that would entail a complicated migration for the existing system object
86// mapping collection stored on disk.
87pub const RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL: &str = "<RUNTIME-ALTERABLE>";
88
89#[derive(Clone, Debug)]
90pub enum Builtin<T: 'static + TypeReference> {
91    Log(&'static BuiltinLog),
92    Table(&'static BuiltinTable),
93    View(&'static BuiltinView),
94    MaterializedView(&'static BuiltinMaterializedView),
95    Type(&'static BuiltinType<T>),
96    Func(BuiltinFunc),
97    Source(&'static BuiltinSource),
98    Index(&'static BuiltinIndex),
99    Connection(&'static BuiltinConnection),
100}
101
102impl<T: TypeReference> Builtin<T> {
103    pub fn name(&self) -> &'static str {
104        match self {
105            Builtin::Log(log) => log.name,
106            Builtin::Table(table) => table.name,
107            Builtin::View(view) => view.name,
108            Builtin::MaterializedView(mv) => mv.name,
109            Builtin::Type(typ) => typ.name,
110            Builtin::Func(func) => func.name,
111            Builtin::Source(coll) => coll.name,
112            Builtin::Index(index) => index.name,
113            Builtin::Connection(connection) => connection.name,
114        }
115    }
116
117    pub fn schema(&self) -> &'static str {
118        match self {
119            Builtin::Log(log) => log.schema,
120            Builtin::Table(table) => table.schema,
121            Builtin::View(view) => view.schema,
122            Builtin::MaterializedView(mv) => mv.schema,
123            Builtin::Type(typ) => typ.schema,
124            Builtin::Func(func) => func.schema,
125            Builtin::Source(coll) => coll.schema,
126            Builtin::Index(index) => index.schema,
127            Builtin::Connection(connection) => connection.schema,
128        }
129    }
130
131    pub fn catalog_item_type(&self) -> CatalogItemType {
132        match self {
133            Builtin::Log(_) => CatalogItemType::Source,
134            Builtin::Source(_) => CatalogItemType::Source,
135            Builtin::Table(_) => CatalogItemType::Table,
136            Builtin::View(_) => CatalogItemType::View,
137            Builtin::MaterializedView(_) => CatalogItemType::MaterializedView,
138            Builtin::Type(_) => CatalogItemType::Type,
139            Builtin::Func(_) => CatalogItemType::Func,
140            Builtin::Index(_) => CatalogItemType::Index,
141            Builtin::Connection(_) => CatalogItemType::Connection,
142        }
143    }
144
145    /// Whether the object can be altered at runtime by its owner.
146    pub fn runtime_alterable(&self) -> bool {
147        match self {
148            Builtin::Connection(c) => c.runtime_alterable,
149            _ => false,
150        }
151    }
152}
153
154#[derive(Clone, Debug, Hash, Serialize)]
155pub struct BuiltinLog {
156    pub variant: LogVariant,
157    pub name: &'static str,
158    pub schema: &'static str,
159    pub oid: u32,
160    /// ACL items to apply to the object
161    pub access: Vec<MzAclItem>,
162    #[serde(default)]
163    pub ontology: Option<Ontology>,
164}
165
166/// Ontology metadata for a builtin catalog object.
167///
168/// When present on a builtin, it marks it as an ontology entity with an explicit
169/// `entity_name`, `description`, and optional per-column semantic type annotations.
170///
171/// ## Why `column_semantic_types` lives here and not in `RelationDesc`
172///
173/// Semantic types are pure catalog-level metadata: they annotate what an ID
174/// column *means* (e.g. "this is a ClusterId") without affecting the Arrow
175/// data type used for encoding. Keeping them in `RelationDesc` would cause
176/// persist schema mismatches during zero-downtime upgrades: the old binary
177/// registers a schema without semantic types, the new binary tries to register
178/// a schema with them, and `register_schema` returns `None` because the schemas
179/// are not `PartialEq`. Since the only consumers of semantic types are the
180/// ontology views (which already have access to `Ontology`), storing them here
181/// is both correct and avoids the schema-evolution problem entirely.
182#[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize)]
183pub struct Ontology {
184    /// The ontology entity name (e.g., "database", "table", "mv"). Names a
185    /// single row of this relation, so prefer singular event/object nouns
186    /// (e.g., "replica_status_event" not "replica_status_history").
187    pub entity_name: &'static str,
188    /// One-line description of this entity.
189    pub description: &'static str,
190    /// Relationships originating from this entity (foreign keys, unions,
191    /// mappings, dependencies, metrics).
192    pub links: &'static [OntologyLink],
193    /// Per-column semantic type annotations: `(column_name, SemanticType)`.
194    /// Only columns that carry a meaningful semantic type need to appear here.
195    pub column_semantic_types: &'static [(&'static str, SemanticType)],
196}
197
198/// Cardinality of an ontology link.
199#[derive(
200    Clone,
201    Copy,
202    Debug,
203    Hash,
204    PartialEq,
205    Eq,
206    serde::Serialize,
207    serde::Deserialize
208)]
209#[serde(rename_all = "snake_case")]
210pub enum Cardinality {
211    OneToOne,
212    ManyToOne,
213}
214
215/// Helper used by serde to skip serializing `false` boolean fields.
216fn is_false(v: &bool) -> bool {
217    !v
218}
219
220/// Typed properties for an ontology link. Serialized to the `properties` JSONB
221/// column in `mz_ontology_link_types`. The `kind` field is inlined from the
222/// enum variant name via `#[serde(tag = "kind")]`.
223///
224/// Choosing the right variant matters:
225///
226/// - [`LinkProperties::ForeignKey`]: the source entity has a column whose value is an ID that
227///   directly references a row in the target entity. Use this when there is an explicit FK column
228///   (e.g. `schema_id` -> `schema`).
229/// - [`LinkProperties::DependsOn`]: this entity logically depends on the target entity via a
230///   graph-edge table (e.g. `mz_compute_dependencies` records that a compute object depends on
231///   another object). The `source_column` is the column **in this entity** that holds the
232///   dependent's ID; `target_column` is the column in the target entity being depended upon. Use
233///   this for dependency-graph tables, **not** `ForeignKey`.
234/// - [`LinkProperties::Union`]: the source entity is a superset view that contains the target
235///   entity as a subset, optionally filtered by a discriminator column.
236/// - [`LinkProperties::MapsTo`]: the source entity provides an ID translation to the target entity,
237///   possibly via an intermediate table or across ID namespaces.
238/// - [`LinkProperties::Measures`]: the source entity records metric measurements about the target
239///   entity.
240#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, serde::Serialize)]
241#[serde(tag = "kind", rename_all = "snake_case")]
242pub enum LinkProperties {
243    /// A foreign-key relationship: `source_column` in the source entity
244    /// references `target_column` in the target entity.
245    ForeignKey {
246        /// Column in the source entity that holds the reference.
247        source_column: &'static str,
248        /// Column in the target entity being referenced (usually `id`).
249        target_column: &'static str,
250        /// How many source rows may reference a single target row.
251        cardinality: Cardinality,
252        /// Semantic type of the source column, if it carries an ID that
253        /// requires type-aware resolution (e.g. `CatalogItemId`, `GlobalId`).
254        #[serde(skip_serializing_if = "Option::is_none")]
255        source_id_type: Option<mz_repr::SemanticType>,
256        /// Intermediate mapping relation needed when `source_id_type` does not
257        /// directly match the target entity's ID type (e.g.
258        /// `mz_internal.mz_object_global_ids` to go from `GlobalId` to catalog
259        /// object).
260        #[serde(skip_serializing_if = "Option::is_none")]
261        requires_mapping: Option<&'static str>,
262        /// True when the source column may be NULL (the reference is optional).
263        #[serde(default, skip_serializing_if = "is_false")]
264        nullable: bool,
265        /// Free-form annotation for cases that need extra context.
266        #[serde(skip_serializing_if = "Option::is_none")]
267        note: Option<&'static str>,
268        /// Additional `(source_column, target_column)` pairs that together with
269        /// `source_column`/`target_column` form a composite join key. Used for
270        /// `_per_worker` entities whose primary key is `(id, worker_id)`, and
271        /// for message-count raw relations whose join key includes worker IDs.
272        /// Serialized as an array; omitted when `None`.
273        #[serde(skip_serializing_if = "Option::is_none")]
274        extra_key_columns: Option<&'static [(&'static str, &'static str)]>,
275    },
276    /// A union relationship: the source entity is a superset view that includes
277    /// the target entity, optionally filtered by a discriminator column/value.
278    Union {
279        /// Column used to discriminate between subtypes (e.g. `type`).
280        #[serde(skip_serializing_if = "Option::is_none")]
281        discriminator_column: Option<&'static str>,
282        /// Value of `discriminator_column` that selects the target entity.
283        #[serde(skip_serializing_if = "Option::is_none")]
284        discriminator_value: Option<&'static str>,
285        /// Free-form annotation for cases that need extra context.
286        #[serde(skip_serializing_if = "Option::is_none")]
287        note: Option<&'static str>,
288    },
289    /// A mapping relationship: the source entity maps to the target entity,
290    /// optionally via an intermediate table and/or with an ID-type conversion.
291    MapsTo {
292        /// Column in the source entity that holds the ID to map from.
293        source_column: &'static str,
294        /// Column in the target entity being mapped to.
295        target_column: &'static str,
296        /// Intermediate relation used to perform the mapping.
297        #[serde(skip_serializing_if = "Option::is_none")]
298        via: Option<&'static str>,
299        /// Semantic type of the source ID before mapping.
300        #[serde(skip_serializing_if = "Option::is_none")]
301        from_type: Option<mz_repr::SemanticType>,
302        /// Semantic type of the target ID after mapping.
303        #[serde(skip_serializing_if = "Option::is_none")]
304        to_type: Option<mz_repr::SemanticType>,
305        /// Free-form annotation for cases that need extra context.
306        #[serde(skip_serializing_if = "Option::is_none")]
307        note: Option<&'static str>,
308    },
309    /// A dependency relationship: this entity directly depends on the
310    /// target entity (e.g. a materialization that references an object).
311    DependsOn {
312        /// Column in this entity that holds the dependency ID.
313        source_column: &'static str,
314        /// Column in the target entity being depended upon (usually `id`).
315        target_column: &'static str,
316        /// Semantic type of the source column.
317        #[serde(skip_serializing_if = "Option::is_none")]
318        source_id_type: Option<mz_repr::SemanticType>,
319        /// Intermediate mapping relation needed when `source_id_type` does not
320        /// directly match the target entity's ID type (e.g. GlobalId →
321        /// `mz_internal.mz_object_global_ids` to reach a catalog object).
322        #[serde(skip_serializing_if = "Option::is_none")]
323        requires_mapping: Option<&'static str>,
324    },
325    /// A metric relationship: the source entity records measurements of a named
326    /// metric on the target entity.
327    Measures {
328        /// Column in the source entity that references the target entity.
329        source_column: &'static str,
330        /// Column in the target entity being measured (usually `id`).
331        target_column: &'static str,
332        /// Name of the metric being measured (e.g. `cpu_time_ns`).
333        metric: &'static str,
334        /// Semantic type of the source column, if ID-type resolution is needed.
335        #[serde(skip_serializing_if = "Option::is_none")]
336        source_id_type: Option<mz_repr::SemanticType>,
337        /// Intermediate mapping relation needed when the source ID type differs
338        /// from the target entity's ID type.
339        #[serde(skip_serializing_if = "Option::is_none")]
340        requires_mapping: Option<&'static str>,
341        /// Free-form annotation for cases that need extra context.
342        #[serde(skip_serializing_if = "Option::is_none")]
343        note: Option<&'static str>,
344        /// Additional `(source_column, target_column)` pairs that together with
345        /// `source_column`/`target_column` form a composite join key.
346        #[serde(skip_serializing_if = "Option::is_none")]
347        extra_key_columns: Option<&'static [(&'static str, &'static str)]>,
348    },
349}
350
351impl LinkProperties {
352    /// Basic foreign-key link with no optional fields set.
353    pub const fn fk(
354        source_column: &'static str,
355        target_column: &'static str,
356        cardinality: Cardinality,
357    ) -> Self {
358        Self::ForeignKey {
359            source_column,
360            target_column,
361            cardinality,
362            source_id_type: None,
363            requires_mapping: None,
364            nullable: false,
365            note: None,
366            extra_key_columns: None,
367        }
368    }
369
370    /// Foreign-key link where the source column may be NULL.
371    pub const fn fk_nullable(
372        source_column: &'static str,
373        target_column: &'static str,
374        cardinality: Cardinality,
375    ) -> Self {
376        Self::ForeignKey {
377            source_column,
378            target_column,
379            cardinality,
380            source_id_type: None,
381            requires_mapping: None,
382            nullable: true,
383            note: None,
384            extra_key_columns: None,
385        }
386    }
387
388    /// Foreign-key link whose source column carries a typed ID (e.g.
389    /// `CatalogItemId`) but does not require an intermediate mapping table.
390    pub const fn fk_typed(
391        source_column: &'static str,
392        target_column: &'static str,
393        cardinality: Cardinality,
394        source_id_type: mz_repr::SemanticType,
395    ) -> Self {
396        Self::ForeignKey {
397            source_column,
398            target_column,
399            cardinality,
400            source_id_type: Some(source_id_type),
401            requires_mapping: None,
402            nullable: false,
403            note: None,
404            extra_key_columns: None,
405        }
406    }
407
408    /// Foreign-key link whose source column carries a typed ID that requires
409    /// an intermediate mapping table to resolve (e.g. `GlobalId` →
410    /// `mz_internal.mz_object_global_ids`).
411    pub const fn fk_mapped(
412        source_column: &'static str,
413        target_column: &'static str,
414        cardinality: Cardinality,
415        source_id_type: mz_repr::SemanticType,
416        requires_mapping: &'static str,
417    ) -> Self {
418        Self::ForeignKey {
419            source_column,
420            target_column,
421            cardinality,
422            source_id_type: Some(source_id_type),
423            requires_mapping: Some(requires_mapping),
424            nullable: false,
425            note: None,
426            extra_key_columns: None,
427        }
428    }
429
430    /// Foreign-key link with a composite join key. `extra_key_columns` lists
431    /// additional `(source_column, target_column)` pairs beyond the primary
432    /// `source_column`/`target_column` pair. Examples:
433    /// - `&[("worker_id", "worker_id")]` for `_per_worker` entities
434    /// - `&[("from_worker_id", "worker_id")]` for message-count raw relations
435    pub const fn fk_composite(
436        source_column: &'static str,
437        target_column: &'static str,
438        cardinality: Cardinality,
439        extra_key_columns: &'static [(&'static str, &'static str)],
440    ) -> Self {
441        Self::ForeignKey {
442            source_column,
443            target_column,
444            cardinality,
445            source_id_type: None,
446            requires_mapping: None,
447            nullable: false,
448            note: None,
449            extra_key_columns: Some(extra_key_columns),
450        }
451    }
452
453    /// Union link filtered by a discriminator column/value pair.
454    pub const fn union_disc(
455        discriminator_column: &'static str,
456        discriminator_value: &'static str,
457    ) -> Self {
458        Self::Union {
459            discriminator_column: Some(discriminator_column),
460            discriminator_value: Some(discriminator_value),
461            note: None,
462        }
463    }
464
465    /// Basic measures link with no optional fields set.
466    pub const fn measures(
467        source_column: &'static str,
468        target_column: &'static str,
469        metric: &'static str,
470    ) -> Self {
471        Self::Measures {
472            source_column,
473            target_column,
474            metric,
475            source_id_type: None,
476            requires_mapping: None,
477            note: None,
478            extra_key_columns: None,
479        }
480    }
481
482    /// Measures link with a composite join key.
483    pub const fn measures_composite(
484        source_column: &'static str,
485        target_column: &'static str,
486        metric: &'static str,
487        extra_key_columns: &'static [(&'static str, &'static str)],
488    ) -> Self {
489        Self::Measures {
490            source_column,
491            target_column,
492            metric,
493            source_id_type: None,
494            requires_mapping: None,
495            note: None,
496            extra_key_columns: Some(extra_key_columns),
497        }
498    }
499
500    /// Measures link whose source ID requires an intermediate mapping table.
501    pub const fn measures_mapped(
502        source_column: &'static str,
503        target_column: &'static str,
504        metric: &'static str,
505        source_id_type: mz_repr::SemanticType,
506        requires_mapping: &'static str,
507    ) -> Self {
508        Self::Measures {
509            source_column,
510            target_column,
511            metric,
512            source_id_type: Some(source_id_type),
513            requires_mapping: Some(requires_mapping),
514            note: None,
515            extra_key_columns: None,
516        }
517    }
518}
519
520/// A directed relationship from one ontology entity to another.
521///
522/// Each link has a `name` (the relationship label, e.g. `"owned_by"`), a
523/// `target` entity name, and a [`LinkProperties`] variant that captures the
524/// kind of relationship.
525#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
526pub struct OntologyLink {
527    /// Relationship name describing the relationship FROM this entity TO the
528    /// target (e.g., `"owned_by"` means "this entity is owned by the target",
529    /// `"depends_on"` means "this entity depends on the target"). When the
530    /// same name appears on multiple links of the same entity, all links
531    /// share that relationship role (e.g., several `"union_includes"` links).
532    pub name: &'static str,
533    /// Target entity name (e.g., "role", "schema").
534    pub target: &'static str,
535    /// Typed properties for the `properties` JSONB column.
536    pub properties: LinkProperties,
537}
538
539#[derive(Clone, Hash, Debug, PartialEq, Eq)]
540pub struct BuiltinTable {
541    pub name: &'static str,
542    pub schema: &'static str,
543    pub oid: u32,
544    pub desc: RelationDesc,
545    pub column_comments: BTreeMap<&'static str, &'static str>,
546    /// Whether the table's retention policy is controlled by
547    /// the system variable `METRICS_RETENTION`
548    pub is_retained_metrics_object: bool,
549    /// ACL items to apply to the object
550    pub access: Vec<MzAclItem>,
551    /// Ontology metadata. None means this builtin is not an ontology entity.
552    pub ontology: Option<Ontology>,
553}
554
555#[derive(Clone, Debug, PartialEq, Eq)]
556pub struct BuiltinSource {
557    pub name: &'static str,
558    pub schema: &'static str,
559    pub oid: u32,
560    pub desc: RelationDesc,
561    pub column_comments: BTreeMap<&'static str, &'static str>,
562    pub data_source: DataSourceDesc,
563    /// Whether the source's retention policy is controlled by
564    /// the system variable `METRICS_RETENTION`
565    pub is_retained_metrics_object: bool,
566    /// ACL items to apply to the object
567    pub access: Vec<MzAclItem>,
568    /// Ontology metadata. None means this builtin is not an ontology entity.
569    pub ontology: Option<Ontology>,
570}
571
572#[derive(Hash, Debug)]
573pub struct BuiltinView {
574    pub name: &'static str,
575    pub schema: &'static str,
576    pub oid: u32,
577    pub desc: RelationDesc,
578    pub column_comments: BTreeMap<&'static str, &'static str>,
579    pub sql: &'static str,
580    /// ACL items to apply to the object
581    pub access: Vec<MzAclItem>,
582    /// Ontology metadata. None means this builtin is not an ontology entity.
583    pub ontology: Option<Ontology>,
584}
585
586impl BuiltinView {
587    pub fn create_sql(&self) -> String {
588        format!("CREATE VIEW {}.{} AS {}", self.schema, self.name, self.sql)
589    }
590}
591
592#[derive(Hash, Debug)]
593pub struct BuiltinMaterializedView {
594    pub name: &'static str,
595    pub schema: &'static str,
596    pub oid: u32,
597    pub desc: RelationDesc,
598    pub column_comments: BTreeMap<&'static str, &'static str>,
599    /// SQL fragment for the MV, following `CREATE MATERIALIZED VIEW [name]`
600    ///
601    /// Format: `IN CLUSTER [cluster_name] AS [query]`
602    pub sql: &'static str,
603    /// Whether the MV's retention policy is controlled by
604    /// the system variable `METRICS_RETENTION`
605    pub is_retained_metrics_object: bool,
606    /// ACL items to apply to the object
607    pub access: Vec<MzAclItem>,
608    /// Ontology metadata. None means this builtin is not an ontology entity.
609    pub ontology: Option<Ontology>,
610}
611
612impl BuiltinMaterializedView {
613    pub fn create_sql(&self) -> String {
614        format!(
615            "CREATE MATERIALIZED VIEW {}.{} {}",
616            self.schema, self.name, self.sql
617        )
618    }
619}
620
621#[derive(Debug)]
622pub struct BuiltinType<T: TypeReference> {
623    pub name: &'static str,
624    pub schema: &'static str,
625    pub oid: u32,
626    pub details: CatalogTypeDetails<T>,
627}
628
629#[derive(Clone, Debug)]
630pub struct BuiltinFunc {
631    pub schema: &'static str,
632    pub name: &'static str,
633    pub inner: &'static mz_sql::func::Func,
634}
635
636/// Note: When creating a built-in index, it's usually best to choose a key that has only one
637/// component. For example, if you created an index
638/// `ON mz_internal.mz_object_lifetimes (id, object_type)`, then this index couldn't be used for a
639/// lookup for `WHERE object_type = ...`, and neither for joins keyed on just `id`.
640/// See <https://materialize.com/docs/transform-data/optimization/#matching-multi-column-indexes-to-multi-column-where-clauses>
641#[derive(Debug)]
642pub struct BuiltinIndex {
643    pub name: &'static str,
644    pub schema: &'static str,
645    pub oid: u32,
646    /// SQL fragment for the index, following `CREATE INDEX [name]`
647    ///
648    /// Format: `IN CLUSTER [cluster_name] ON [table_name] ([column_exprs])`
649    pub sql: &'static str,
650    pub is_retained_metrics_object: bool,
651}
652
653impl BuiltinIndex {
654    pub fn create_sql(&self) -> String {
655        format!("CREATE INDEX {}\n{}", self.name, self.sql)
656    }
657}
658
659#[derive(Hash, Debug)]
660pub struct BuiltinConnection {
661    pub name: &'static str,
662    pub schema: &'static str,
663    pub oid: u32,
664    pub sql: &'static str,
665    pub access: &'static [MzAclItem],
666    pub owner_id: &'static RoleId,
667    /// Whether the object can be altered at runtime by its owner.
668    ///
669    /// Note that when `runtime_alterable` is true, changing the `sql` in future
670    /// versions does not trigger a migration.
671    pub runtime_alterable: bool,
672}
673
674#[derive(Clone, Debug)]
675pub struct BuiltinRole {
676    pub id: RoleId,
677    /// Name of the builtin role.
678    ///
679    /// IMPORTANT: Must start with a prefix from [`BUILTIN_PREFIXES`].
680    pub name: &'static str,
681    pub oid: u32,
682    pub attributes: RoleAttributesRaw,
683}
684
685#[derive(Clone, Debug)]
686pub struct BuiltinCluster {
687    /// Name of the cluster.
688    ///
689    /// IMPORTANT: Must start with a prefix from [`BUILTIN_PREFIXES`].
690    pub name: &'static str,
691    pub privileges: &'static [MzAclItem],
692    pub owner_id: &'static RoleId,
693}
694
695/// Uniquely identifies the definition of a builtin object.
696pub trait Fingerprint {
697    fn fingerprint(&self) -> String;
698}
699
700impl<T: TypeReference> Fingerprint for &Builtin<T> {
701    fn fingerprint(&self) -> String {
702        match self {
703            Builtin::Log(log) => log.fingerprint(),
704            Builtin::Table(table) => table.fingerprint(),
705            Builtin::View(view) => view.fingerprint(),
706            Builtin::MaterializedView(mv) => mv.fingerprint(),
707            Builtin::Type(typ) => typ.fingerprint(),
708            Builtin::Func(func) => func.fingerprint(),
709            Builtin::Source(coll) => coll.fingerprint(),
710            Builtin::Index(index) => index.fingerprint(),
711            Builtin::Connection(connection) => connection.fingerprint(),
712        }
713    }
714}
715
716// Types and Funcs never change fingerprints so we just return constant 0
717impl<T: TypeReference> Fingerprint for &BuiltinType<T> {
718    fn fingerprint(&self) -> String {
719        "".to_string()
720    }
721}
722
723impl Fingerprint for &BuiltinFunc {
724    fn fingerprint(&self) -> String {
725        "".to_string()
726    }
727}
728
729impl Fingerprint for &BuiltinLog {
730    fn fingerprint(&self) -> String {
731        self.variant.desc().fingerprint()
732    }
733}
734
735impl Fingerprint for &BuiltinTable {
736    fn fingerprint(&self) -> String {
737        self.desc.fingerprint()
738    }
739}
740
741impl Fingerprint for &BuiltinView {
742    fn fingerprint(&self) -> String {
743        self.sql.to_string()
744    }
745}
746
747impl Fingerprint for &BuiltinSource {
748    fn fingerprint(&self) -> String {
749        self.desc.fingerprint()
750    }
751}
752
753// The fingerprint is the SQL text, so a change to the row shape of a relation
754// the MV reads (for example `mz_indexes` reading `mz_builtin_indexes`) goes
755// unnoticed by the fingerprint check in the adapter's `update_fingerprints`
756// unless the MV's own SQL changes. The ways an upstream change can regress a
757// builtin MV, and what catches each:
758//
759// 1. The upstream relation drops a referenced column: planning the MV fails at
760//    catalog open, which every test that opens a catalog hits.
761// 2. The upstream relation adds a column that the MV is changed to reference:
762//    the SQL changes, so the fingerprint changes, and `update_fingerprints`
763//    panics at upgrade until a `MigrationStep` is added.
764// 3. The upstream relation changes a referenced column's type: the planned
765//    desc changes and `verify_builtin_descs` fails until `desc` is updated. The
766//    fingerprint does not change, so without a `MigrationStep` the coordinator
767//    evolves the existing shard to the new desc at bootstrap, in
768//    `evolve_nullability_for_bootstrap`. Persist refuses when the Arrow
769//    encoding differs (int32 to int64), which terminates environmentd, and
770//    cannot tell when the encoding is shared (timestamp to timestamptz), which
771//    silently reinterprets the stored bytes.
772// 4. The upstream relation changes a referenced column's nullability: as in 3,
773//    `verify_builtin_descs` fails until `desc` is updated. Boot then evolves the
774//    shard to the new desc (`evolve_nullability_for_bootstrap`), which persist
775//    accepts for widening and rejects for narrowing, terminating envd.
776// 5. The MV is `SELECT *` over the upstream relation, which reorders or swaps
777//    columns: persist matches columns by position, so this is case 3 for each
778//    moved column.
779//
780// `verify_builtin_descs` therefore asks for a `MigrationStep` whenever a
781// shard-backed builtin's `desc` changes in more than nullability.
782impl Fingerprint for &BuiltinMaterializedView {
783    fn fingerprint(&self) -> String {
784        self.create_sql()
785    }
786}
787
788impl Fingerprint for &BuiltinIndex {
789    fn fingerprint(&self) -> String {
790        self.create_sql()
791    }
792}
793
794impl Fingerprint for &BuiltinConnection {
795    fn fingerprint(&self) -> String {
796        self.sql.to_string()
797    }
798}
799
800impl Fingerprint for RelationDesc {
801    fn fingerprint(&self) -> String {
802        self.typ().fingerprint()
803    }
804}
805
806impl Fingerprint for SqlRelationType {
807    fn fingerprint(&self) -> String {
808        serde_json::to_string(self).expect("serialization cannot fail")
809    }
810}
811
812/// Asserts that `name` is safe to embed unquoted inside a `'...'`-quoted SQL literal
813/// or inside a `"..."`-quoted SQL identifier. Generated builtin relations
814/// (`make_builtin_indexes`, `make_mz_object_dependencies_raw`, ...) concatenate builtin names
815/// into SQL fragments, so a quote or backslash would produce malformed SQL. Builtin
816/// names should always be plain ASCII identifiers.
817pub(super) fn assert_safe_builtin_name(name: &str, kind: &str) {
818    assert!(
819        !name.contains('\'') && !name.contains('"') && !name.contains('\\'),
820        "builtin {kind} name {name:?} contains an unsupported character; \
821         generated builtin relations reconstruct SQL via string \
822         concatenation and assume names contain no quotes or backslashes"
823    );
824}
825
826pub(super) const PUBLIC_SELECT: MzAclItem = MzAclItem {
827    grantee: RoleId::Public,
828    grantor: MZ_SYSTEM_ROLE_ID,
829    acl_mode: AclMode::SELECT,
830};
831
832pub(super) const SUPPORT_SELECT: MzAclItem = MzAclItem {
833    grantee: MZ_SUPPORT_ROLE_ID,
834    grantor: MZ_SYSTEM_ROLE_ID,
835    acl_mode: AclMode::SELECT,
836};
837
838pub(super) const ANALYTICS_SELECT: MzAclItem = MzAclItem {
839    grantee: MZ_ANALYTICS_ROLE_ID,
840    grantor: MZ_SYSTEM_ROLE_ID,
841    acl_mode: AclMode::SELECT,
842};
843
844pub(super) const MONITOR_SELECT: MzAclItem = MzAclItem {
845    grantee: MZ_MONITOR_ROLE_ID,
846    grantor: MZ_SYSTEM_ROLE_ID,
847    acl_mode: AclMode::SELECT,
848};
849
850pub(super) const MONITOR_REDACTED_SELECT: MzAclItem = MzAclItem {
851    grantee: MZ_MONITOR_REDACTED_ROLE_ID,
852    grantor: MZ_SYSTEM_ROLE_ID,
853    acl_mode: AclMode::SELECT,
854};
855
856pub static MZ_CATALOG_RAW_DESCRIPTION: LazyLock<SystemObjectDescription> =
857    LazyLock::new(|| SystemObjectDescription {
858        schema_name: MZ_CATALOG_RAW.schema.to_string(),
859        object_type: CatalogItemType::Source,
860        object_name: MZ_CATALOG_RAW.name.to_string(),
861    });
862
863pub static MZ_STORAGE_USAGE_BY_SHARD_DESCRIPTION: LazyLock<SystemObjectDescription> =
864    LazyLock::new(|| SystemObjectDescription {
865        schema_name: MZ_STORAGE_USAGE_BY_SHARD.schema.to_string(),
866        object_type: CatalogItemType::Table,
867        object_name: MZ_STORAGE_USAGE_BY_SHARD.name.to_string(),
868    });
869
870/// Identifies [`MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY`] for the schema-migration
871/// guard in `builtin_schema_migration.rs`, which forbids migrating this table
872/// because its startup pruner assumes it is the only source of retractions.
873pub static MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_DESCRIPTION: LazyLock<SystemObjectDescription> =
874    LazyLock::new(|| SystemObjectDescription {
875        schema_name: MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.schema.to_string(),
876        object_type: CatalogItemType::Table,
877        object_name: MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.name.to_string(),
878    });
879
880/// Identifies [`MZ_OBJECT_HYDRATION_HISTORY`] for the schema-migration guard.
881pub static MZ_OBJECT_HYDRATION_HISTORY_DESCRIPTION: LazyLock<SystemObjectDescription> =
882    LazyLock::new(|| SystemObjectDescription {
883        schema_name: MZ_OBJECT_HYDRATION_HISTORY.schema.to_string(),
884        object_type: CatalogItemType::Table,
885        object_name: MZ_OBJECT_HYDRATION_HISTORY.name.to_string(),
886    });
887
888/// Identifies [`MZ_REPLICA_HYDRATION_HISTORY`] for the schema-migration guard.
889pub static MZ_REPLICA_HYDRATION_HISTORY_DESCRIPTION: LazyLock<SystemObjectDescription> =
890    LazyLock::new(|| SystemObjectDescription {
891        schema_name: MZ_REPLICA_HYDRATION_HISTORY.schema.to_string(),
892        object_type: CatalogItemType::Table,
893        object_name: MZ_REPLICA_HYDRATION_HISTORY.name.to_string(),
894    });
895
896/// Identifies [`MZ_CLUSTER_REPLICA_FRONTIERS`] for the schema-migration guard in
897/// `builtin_schema_migration.rs`, which forbids migrating this source because the 0dt
898/// caught-up gate reads the leader's shard for it to learn the live frontiers.
899pub static MZ_CLUSTER_REPLICA_FRONTIERS_DESCRIPTION: LazyLock<SystemObjectDescription> =
900    LazyLock::new(|| SystemObjectDescription {
901        schema_name: MZ_CLUSTER_REPLICA_FRONTIERS.schema.to_string(),
902        object_type: CatalogItemType::Source,
903        object_name: MZ_CLUSTER_REPLICA_FRONTIERS.name.to_string(),
904    });
905
906/// Identifies the leader hydration source that the 0dt caught-up gate reads.
907pub static MZ_COMPUTE_HYDRATION_TIMES_DESCRIPTION: LazyLock<SystemObjectDescription> =
908    LazyLock::new(|| SystemObjectDescription {
909        schema_name: MZ_COMPUTE_HYDRATION_TIMES.schema.to_string(),
910        object_type: CatalogItemType::Source,
911        object_name: MZ_COMPUTE_HYDRATION_TIMES.name.to_string(),
912    });
913
914pub const MZ_SYSTEM_ROLE: BuiltinRole = BuiltinRole {
915    id: MZ_SYSTEM_ROLE_ID,
916    name: SYSTEM_USER_NAME,
917    oid: oid::ROLE_MZ_SYSTEM_OID,
918    attributes: RoleAttributesRaw::new().with_all(),
919};
920
921pub const MZ_SUPPORT_ROLE: BuiltinRole = BuiltinRole {
922    id: MZ_SUPPORT_ROLE_ID,
923    name: SUPPORT_USER_NAME,
924    oid: oid::ROLE_MZ_SUPPORT_OID,
925    attributes: RoleAttributesRaw::new(),
926};
927
928pub const MZ_ANALYTICS_ROLE: BuiltinRole = BuiltinRole {
929    id: MZ_ANALYTICS_ROLE_ID,
930    name: ANALYTICS_USER_NAME,
931    oid: oid::ROLE_MZ_ANALYTICS_OID,
932    attributes: RoleAttributesRaw::new(),
933};
934
935/// This role can `SELECT` from various query history objects,
936/// e.g. `mz_prepared_statement_history`.
937pub const MZ_MONITOR_ROLE: BuiltinRole = BuiltinRole {
938    id: MZ_MONITOR_ROLE_ID,
939    name: "mz_monitor",
940    oid: oid::ROLE_MZ_MONITOR_OID,
941    attributes: RoleAttributesRaw::new(),
942};
943
944/// This role is like [`MZ_MONITOR_ROLE`], but can only query
945/// the redacted versions of the objects.
946pub const MZ_MONITOR_REDACTED: BuiltinRole = BuiltinRole {
947    id: MZ_MONITOR_REDACTED_ROLE_ID,
948    name: "mz_monitor_redacted",
949    oid: oid::ROLE_MZ_MONITOR_REDACTED_OID,
950    attributes: RoleAttributesRaw::new(),
951};
952
953/// Sentinel role used as the grantor for JWT group-sync-managed
954/// role memberships. Never logged into directly.
955pub const MZ_JWT_SYNC_ROLE: BuiltinRole = BuiltinRole {
956    id: MZ_JWT_SYNC_ROLE_ID,
957    name: JWT_SYNC_ROLE_NAME,
958    oid: oid::ROLE_MZ_JWT_SYNC_OID,
959    attributes: RoleAttributesRaw::new(),
960};
961
962pub const MZ_SYSTEM_CLUSTER: BuiltinCluster = BuiltinCluster {
963    name: SYSTEM_USER_NAME,
964    owner_id: &MZ_SYSTEM_ROLE_ID,
965    privileges: &[
966        MzAclItem {
967            grantee: MZ_SUPPORT_ROLE_ID,
968            grantor: MZ_SYSTEM_ROLE_ID,
969            acl_mode: AclMode::USAGE,
970        },
971        rbac::owner_privilege(ObjectType::Cluster, MZ_SYSTEM_ROLE_ID),
972    ],
973};
974
975pub const MZ_CATALOG_SERVER_CLUSTER: BuiltinCluster = BuiltinCluster {
976    name: "mz_catalog_server",
977    owner_id: &MZ_SYSTEM_ROLE_ID,
978    privileges: &[
979        MzAclItem {
980            grantee: RoleId::Public,
981            grantor: MZ_SYSTEM_ROLE_ID,
982            acl_mode: AclMode::USAGE,
983        },
984        MzAclItem {
985            grantee: MZ_SUPPORT_ROLE_ID,
986            grantor: MZ_SYSTEM_ROLE_ID,
987            acl_mode: AclMode::USAGE.union(AclMode::CREATE),
988        },
989        rbac::owner_privilege(ObjectType::Cluster, MZ_SYSTEM_ROLE_ID),
990    ],
991};
992
993pub const MZ_PROBE_CLUSTER: BuiltinCluster = BuiltinCluster {
994    name: "mz_probe",
995    owner_id: &MZ_SYSTEM_ROLE_ID,
996    privileges: &[
997        MzAclItem {
998            grantee: MZ_SUPPORT_ROLE_ID,
999            grantor: MZ_SYSTEM_ROLE_ID,
1000            acl_mode: AclMode::USAGE,
1001        },
1002        MzAclItem {
1003            grantee: MZ_MONITOR_ROLE_ID,
1004            grantor: MZ_SYSTEM_ROLE_ID,
1005            acl_mode: AclMode::USAGE,
1006        },
1007        rbac::owner_privilege(ObjectType::Cluster, MZ_SYSTEM_ROLE_ID),
1008    ],
1009};
1010
1011pub const MZ_SUPPORT_CLUSTER: BuiltinCluster = BuiltinCluster {
1012    name: "mz_support",
1013    owner_id: &MZ_SUPPORT_ROLE_ID,
1014    privileges: &[
1015        MzAclItem {
1016            grantee: MZ_SYSTEM_ROLE_ID,
1017            grantor: MZ_SUPPORT_ROLE_ID,
1018            acl_mode: rbac::all_object_privileges(SystemObjectType::Object(ObjectType::Cluster)),
1019        },
1020        rbac::owner_privilege(ObjectType::Cluster, MZ_SUPPORT_ROLE_ID),
1021    ],
1022};
1023
1024pub const MZ_ANALYTICS_CLUSTER: BuiltinCluster = BuiltinCluster {
1025    name: "mz_analytics",
1026    owner_id: &MZ_ANALYTICS_ROLE_ID,
1027    privileges: &[
1028        MzAclItem {
1029            grantee: MZ_SYSTEM_ROLE_ID,
1030            grantor: MZ_ANALYTICS_ROLE_ID,
1031            acl_mode: rbac::all_object_privileges(SystemObjectType::Object(ObjectType::Cluster)),
1032        },
1033        rbac::owner_privilege(ObjectType::Cluster, MZ_ANALYTICS_ROLE_ID),
1034    ],
1035};
1036
1037/// List of all builtin objects sorted topologically by dependency.
1038pub static BUILTINS_STATIC: LazyLock<Vec<Builtin<NameReference>>> = LazyLock::new(|| {
1039    let mut builtin_types = vec![
1040        Builtin::Type(&TYPE_ANY),
1041        Builtin::Type(&TYPE_ANYARRAY),
1042        Builtin::Type(&TYPE_ANYELEMENT),
1043        Builtin::Type(&TYPE_ANYNONARRAY),
1044        Builtin::Type(&TYPE_ANYRANGE),
1045        Builtin::Type(&TYPE_BOOL),
1046        Builtin::Type(&TYPE_BOOL_ARRAY),
1047        Builtin::Type(&TYPE_BYTEA),
1048        Builtin::Type(&TYPE_BYTEA_ARRAY),
1049        Builtin::Type(&TYPE_BPCHAR),
1050        Builtin::Type(&TYPE_BPCHAR_ARRAY),
1051        Builtin::Type(&TYPE_CHAR),
1052        Builtin::Type(&TYPE_CHAR_ARRAY),
1053        Builtin::Type(&TYPE_DATE),
1054        Builtin::Type(&TYPE_DATE_ARRAY),
1055        Builtin::Type(&TYPE_FLOAT4),
1056        Builtin::Type(&TYPE_FLOAT4_ARRAY),
1057        Builtin::Type(&TYPE_FLOAT8),
1058        Builtin::Type(&TYPE_FLOAT8_ARRAY),
1059        Builtin::Type(&TYPE_INT4),
1060        Builtin::Type(&TYPE_INT4_ARRAY),
1061        Builtin::Type(&TYPE_INT8),
1062        Builtin::Type(&TYPE_INT8_ARRAY),
1063        Builtin::Type(&TYPE_INTERVAL),
1064        Builtin::Type(&TYPE_INTERVAL_ARRAY),
1065        Builtin::Type(&TYPE_JSONB),
1066        Builtin::Type(&TYPE_JSONB_ARRAY),
1067        Builtin::Type(&TYPE_LIST),
1068        Builtin::Type(&TYPE_MAP),
1069        Builtin::Type(&TYPE_NAME),
1070        Builtin::Type(&TYPE_NAME_ARRAY),
1071        Builtin::Type(&TYPE_NUMERIC),
1072        Builtin::Type(&TYPE_NUMERIC_ARRAY),
1073        Builtin::Type(&TYPE_OID),
1074        Builtin::Type(&TYPE_OID_ARRAY),
1075        Builtin::Type(&TYPE_RECORD),
1076        Builtin::Type(&TYPE_RECORD_ARRAY),
1077        Builtin::Type(&TYPE_REGCLASS),
1078        Builtin::Type(&TYPE_REGCLASS_ARRAY),
1079        Builtin::Type(&TYPE_REGPROC),
1080        Builtin::Type(&TYPE_REGPROC_ARRAY),
1081        Builtin::Type(&TYPE_REGTYPE),
1082        Builtin::Type(&TYPE_REGTYPE_ARRAY),
1083        Builtin::Type(&TYPE_INT2),
1084        Builtin::Type(&TYPE_INT2_ARRAY),
1085        Builtin::Type(&TYPE_TEXT),
1086        Builtin::Type(&TYPE_TEXT_ARRAY),
1087        Builtin::Type(&TYPE_TIME),
1088        Builtin::Type(&TYPE_TIME_ARRAY),
1089        Builtin::Type(&TYPE_TIMESTAMP),
1090        Builtin::Type(&TYPE_TIMESTAMP_ARRAY),
1091        Builtin::Type(&TYPE_TIMESTAMPTZ),
1092        Builtin::Type(&TYPE_TIMESTAMPTZ_ARRAY),
1093        Builtin::Type(&TYPE_UUID),
1094        Builtin::Type(&TYPE_UUID_ARRAY),
1095        Builtin::Type(&TYPE_VARCHAR),
1096        Builtin::Type(&TYPE_VARCHAR_ARRAY),
1097        Builtin::Type(&TYPE_INT2_VECTOR),
1098        Builtin::Type(&TYPE_INT2_VECTOR_ARRAY),
1099        Builtin::Type(&TYPE_ANYCOMPATIBLE),
1100        Builtin::Type(&TYPE_ANYCOMPATIBLEARRAY),
1101        Builtin::Type(&TYPE_ANYCOMPATIBLENONARRAY),
1102        Builtin::Type(&TYPE_ANYCOMPATIBLELIST),
1103        Builtin::Type(&TYPE_ANYCOMPATIBLEMAP),
1104        Builtin::Type(&TYPE_ANYCOMPATIBLERANGE),
1105        Builtin::Type(&TYPE_UINT2),
1106        Builtin::Type(&TYPE_UINT2_ARRAY),
1107        Builtin::Type(&TYPE_UINT4),
1108        Builtin::Type(&TYPE_UINT4_ARRAY),
1109        Builtin::Type(&TYPE_UINT8),
1110        Builtin::Type(&TYPE_UINT8_ARRAY),
1111        Builtin::Type(&TYPE_MZ_TIMESTAMP),
1112        Builtin::Type(&TYPE_MZ_TIMESTAMP_ARRAY),
1113        Builtin::Type(&TYPE_INT4_RANGE),
1114        Builtin::Type(&TYPE_INT4_RANGE_ARRAY),
1115        Builtin::Type(&TYPE_INT8_RANGE),
1116        Builtin::Type(&TYPE_INT8_RANGE_ARRAY),
1117        Builtin::Type(&TYPE_DATE_RANGE),
1118        Builtin::Type(&TYPE_DATE_RANGE_ARRAY),
1119        Builtin::Type(&TYPE_NUM_RANGE),
1120        Builtin::Type(&TYPE_NUM_RANGE_ARRAY),
1121        Builtin::Type(&TYPE_TS_RANGE),
1122        Builtin::Type(&TYPE_TS_RANGE_ARRAY),
1123        Builtin::Type(&TYPE_TSTZ_RANGE),
1124        Builtin::Type(&TYPE_TSTZ_RANGE_ARRAY),
1125        Builtin::Type(&TYPE_MZ_ACL_ITEM),
1126        Builtin::Type(&TYPE_MZ_ACL_ITEM_ARRAY),
1127        Builtin::Type(&TYPE_ACL_ITEM),
1128        Builtin::Type(&TYPE_ACL_ITEM_ARRAY),
1129        Builtin::Type(&TYPE_INTERNAL),
1130    ];
1131
1132    let mut builtin_funcs = Vec::new();
1133    for (schema, funcs) in &[
1134        (PG_CATALOG_SCHEMA, &*mz_sql::func::PG_CATALOG_BUILTINS),
1135        (
1136            INFORMATION_SCHEMA,
1137            &*mz_sql::func::INFORMATION_SCHEMA_BUILTINS,
1138        ),
1139        (MZ_CATALOG_SCHEMA, &*mz_sql::func::MZ_CATALOG_BUILTINS),
1140        (MZ_INTERNAL_SCHEMA, &*mz_sql::func::MZ_INTERNAL_BUILTINS),
1141        (MZ_UNSAFE_SCHEMA, &*mz_sql::func::MZ_UNSAFE_BUILTINS),
1142    ] {
1143        for (name, func) in funcs.iter() {
1144            builtin_funcs.push(Builtin::Func(BuiltinFunc {
1145                name,
1146                schema,
1147                inner: func,
1148            }));
1149        }
1150    }
1151
1152    let mut builtin_items = vec![
1153        Builtin::Source(&MZ_CATALOG_RAW),
1154        Builtin::Log(&MZ_ARRANGEMENT_SHARING_RAW),
1155        Builtin::Log(&MZ_ARRANGEMENT_BATCHES_RAW),
1156        Builtin::Log(&MZ_ARRANGEMENT_RECORDS_RAW),
1157        Builtin::Log(&MZ_ARRANGEMENT_BATCHER_RECORDS_RAW),
1158        Builtin::Log(&MZ_ARRANGEMENT_BATCHER_SIZE_RAW),
1159        Builtin::Log(&MZ_ARRANGEMENT_BATCHER_CAPACITY_RAW),
1160        Builtin::Log(&MZ_ARRANGEMENT_BATCHER_ALLOCATIONS_RAW),
1161        Builtin::Log(&MZ_DATAFLOW_CHANNELS_PER_WORKER),
1162        Builtin::Log(&MZ_DATAFLOW_OPERATORS_PER_WORKER),
1163        Builtin::Log(&MZ_DATAFLOW_ADDRESSES_PER_WORKER),
1164        Builtin::Log(&MZ_DATAFLOW_OPERATOR_REACHABILITY_RAW),
1165        Builtin::Log(&MZ_COMPUTE_EXPORTS_PER_WORKER),
1166        Builtin::Log(&MZ_COMPUTE_DATAFLOW_GLOBAL_IDS_PER_WORKER),
1167        Builtin::Log(&MZ_CLUSTER_PROMETHEUS_METRICS),
1168        Builtin::Log(&MZ_CLUSTER_REPLICA_RESOURCE_USAGE),
1169        Builtin::Log(&MZ_MESSAGE_COUNTS_RECEIVED_RAW),
1170        Builtin::Log(&MZ_MESSAGE_COUNTS_SENT_RAW),
1171        Builtin::Log(&MZ_MESSAGE_BATCH_COUNTS_RECEIVED_RAW),
1172        Builtin::Log(&MZ_MESSAGE_BATCH_COUNTS_SENT_RAW),
1173        Builtin::Log(&MZ_ACTIVE_PEEKS_PER_WORKER),
1174        Builtin::Log(&MZ_PEEK_DURATIONS_HISTOGRAM_RAW),
1175        Builtin::Log(&MZ_ARRANGEMENT_HEAP_CAPACITY_RAW),
1176        Builtin::Log(&MZ_ARRANGEMENT_HEAP_ALLOCATIONS_RAW),
1177        Builtin::Log(&MZ_ARRANGEMENT_HEAP_SIZE_RAW),
1178        Builtin::Log(&MZ_SCHEDULING_ELAPSED_RAW),
1179        Builtin::Log(&MZ_COMPUTE_OPERATOR_DURATIONS_HISTOGRAM_RAW),
1180        Builtin::Log(&MZ_SCHEDULING_PARKS_HISTOGRAM_RAW),
1181        Builtin::Log(&MZ_COMPUTE_FRONTIERS_PER_WORKER),
1182        Builtin::Log(&MZ_COMPUTE_IMPORT_FRONTIERS_PER_WORKER),
1183        Builtin::Log(&MZ_COMPUTE_ERROR_COUNTS_RAW),
1184        Builtin::Log(&MZ_COMPUTE_HYDRATION_TIMES_PER_WORKER),
1185        Builtin::Log(&MZ_COMPUTE_OPERATOR_HYDRATION_STATUSES_PER_WORKER),
1186        Builtin::MaterializedView(&MZ_KAFKA_SINKS),
1187        Builtin::MaterializedView(&MZ_KAFKA_CONNECTIONS),
1188        Builtin::MaterializedView(&MZ_KAFKA_SOURCES),
1189        // mz_object_dependencies_raw is generated dynamically below with inlined
1190        // builtin VALUES and inserted directly before this entry.
1191        Builtin::MaterializedView(&MZ_OBJECT_DEPENDENCIES),
1192        Builtin::MaterializedView(&MZ_ICEBERG_SINKS),
1193        Builtin::MaterializedView(&MZ_DATABASES),
1194        Builtin::MaterializedView(&MZ_SCHEMAS),
1195        Builtin::Table(&MZ_COLUMNS),
1196        Builtin::MaterializedView(&MZ_INDEXES),
1197        Builtin::Table(&MZ_INDEX_COLUMNS),
1198        Builtin::MaterializedView(&MZ_TABLES),
1199        Builtin::MaterializedView(&MZ_SOURCES),
1200        Builtin::MaterializedView(&MZ_SOURCE_REFERENCES),
1201        Builtin::MaterializedView(&MZ_POSTGRES_SOURCES),
1202        Builtin::MaterializedView(&MZ_POSTGRES_SOURCE_TABLES),
1203        Builtin::MaterializedView(&MZ_MYSQL_SOURCE_TABLES),
1204        Builtin::MaterializedView(&MZ_SQL_SERVER_SOURCE_TABLES),
1205        Builtin::MaterializedView(&MZ_KAFKA_SOURCE_TABLES),
1206        Builtin::MaterializedView(&MZ_SINKS),
1207        Builtin::MaterializedView(&MZ_VIEWS),
1208        Builtin::Table(&MZ_TYPES),
1209        Builtin::Table(&MZ_TYPE_PG_METADATA),
1210        Builtin::Table(&MZ_ARRAY_TYPES),
1211        Builtin::Table(&MZ_BASE_TYPES),
1212        Builtin::Table(&MZ_LIST_TYPES),
1213        Builtin::Table(&MZ_MAP_TYPES),
1214        Builtin::MaterializedView(&MZ_ROLES),
1215        Builtin::Table(&MZ_ROLE_AUTH),
1216        Builtin::MaterializedView(&MZ_ROLE_MEMBERS),
1217        Builtin::MaterializedView(&MZ_ROLE_PARAMETERS),
1218        Builtin::Table(&MZ_PSEUDO_TYPES),
1219        Builtin::Table(&MZ_FUNCTIONS),
1220        Builtin::Table(&MZ_OPERATORS),
1221        Builtin::Table(&MZ_AGGREGATES),
1222        Builtin::Table(&MZ_CLUSTER_REPLICA_SIZES),
1223        Builtin::Table(&MZ_CLUSTER_REPLICA_SIZE_INTERNAL),
1224        Builtin::MaterializedView(&MZ_CLUSTERS),
1225        Builtin::MaterializedView(&MZ_CLUSTER_WORKLOAD_CLASSES),
1226        Builtin::MaterializedView(&MZ_CLUSTER_SCHEDULES),
1227        Builtin::MaterializedView(&MZ_CLUSTER_RECONFIGURATIONS),
1228        Builtin::MaterializedView(&MZ_CLUSTER_AUTO_SCALING_STRATEGIES),
1229        Builtin::MaterializedView(&MZ_SECRETS),
1230        Builtin::MaterializedView(&MZ_CONNECTIONS),
1231        Builtin::MaterializedView(&MZ_SSH_TUNNEL_CONNECTIONS),
1232        Builtin::MaterializedView(&MZ_CLUSTER_REPLICAS),
1233        Builtin::Source(&MZ_CLUSTER_REPLICA_METRICS_HISTORY),
1234        Builtin::View(&MZ_CLUSTER_REPLICA_METRICS),
1235        Builtin::Source(&MZ_CLUSTER_REPLICA_STATUS_HISTORY),
1236        Builtin::View(&MZ_CLUSTER_REPLICA_STATUSES),
1237        Builtin::MaterializedView(&MZ_INTERNAL_CLUSTER_REPLICAS),
1238        Builtin::MaterializedView(&MZ_PENDING_CLUSTER_REPLICAS),
1239        Builtin::MaterializedView(&MZ_AUDIT_EVENTS),
1240        Builtin::Table(&MZ_STORAGE_USAGE_BY_SHARD),
1241        Builtin::Table(&MZ_EGRESS_IPS),
1242        Builtin::MaterializedView(&MZ_AWS_PRIVATELINK_CONNECTIONS),
1243        Builtin::MaterializedView(&MZ_AWS_CONNECTIONS),
1244        Builtin::Table(&MZ_SUBSCRIPTIONS),
1245        Builtin::Table(&MZ_SESSIONS),
1246        Builtin::MaterializedView(&MZ_OVERRIDDEN_SYSTEM_PARAMETERS),
1247        Builtin::MaterializedView(&MZ_CLUSTER_SYSTEM_PARAMETERS),
1248        Builtin::MaterializedView(&MZ_REPLICA_SYSTEM_PARAMETERS),
1249        Builtin::MaterializedView(&MZ_DEFAULT_PRIVILEGES),
1250        Builtin::MaterializedView(&MZ_SYSTEM_PRIVILEGES),
1251        Builtin::MaterializedView(&MZ_COMMENTS),
1252        Builtin::Table(&MZ_WEBHOOKS_SOURCES),
1253        Builtin::MaterializedView(&MZ_METRIC_SINKS),
1254        Builtin::MaterializedView(&MZ_HISTORY_RETENTION_STRATEGIES),
1255        Builtin::MaterializedView(&MZ_MATERIALIZED_VIEWS),
1256        Builtin::MaterializedView(&MZ_MATERIALIZED_VIEW_REFRESH_STRATEGIES),
1257        Builtin::MaterializedView(&MZ_NETWORK_POLICIES),
1258        Builtin::MaterializedView(&MZ_NETWORK_POLICY_RULES),
1259        Builtin::Table(&MZ_LICENSE_KEYS),
1260        Builtin::MaterializedView(&MZ_REPLACEMENTS),
1261        Builtin::View(&MZ_RELATIONS),
1262        Builtin::View(&MZ_OBJECT_OID_ALIAS),
1263        Builtin::View(&MZ_OBJECTS),
1264        Builtin::View(&MZ_OBJECT_FULLY_QUALIFIED_NAMES),
1265        Builtin::View(&MZ_OBJECTS_ID_NAMESPACE_TYPES),
1266        Builtin::View(&MZ_OBJECT_HISTORY),
1267        Builtin::View(&MZ_OBJECT_LIFETIMES),
1268        Builtin::MaterializedView(&MZ_OBJECT_GLOBAL_IDS),
1269        Builtin::View(&MZ_ARRANGEMENT_SHARING_PER_WORKER),
1270        Builtin::View(&MZ_ARRANGEMENT_SHARING),
1271        Builtin::View(&MZ_ARRANGEMENT_SIZES_PER_WORKER),
1272        Builtin::View(&MZ_ARRANGEMENT_SIZES),
1273        Builtin::View(&MZ_DATAFLOWS_PER_WORKER),
1274        Builtin::View(&MZ_DATAFLOWS),
1275        Builtin::View(&MZ_DATAFLOW_ADDRESSES),
1276        Builtin::View(&MZ_DATAFLOW_CHANNELS),
1277        Builtin::View(&MZ_DATAFLOW_OPERATORS),
1278        Builtin::View(&MZ_DATAFLOW_GLOBAL_IDS),
1279        Builtin::View(&MZ_COMPUTE_EXPORTS),
1280        Builtin::View(&MZ_MAPPABLE_OBJECTS),
1281        Builtin::View(&MZ_DATAFLOW_OPERATOR_DATAFLOWS_PER_WORKER),
1282        Builtin::View(&MZ_DATAFLOW_OPERATOR_DATAFLOWS),
1283        Builtin::View(&MZ_OBJECT_TRANSITIVE_DEPENDENCIES),
1284        Builtin::View(&MZ_DATAFLOW_OPERATOR_REACHABILITY_PER_WORKER),
1285        Builtin::View(&MZ_DATAFLOW_OPERATOR_REACHABILITY),
1286        Builtin::View(&MZ_CLUSTER_REPLICA_UTILIZATION),
1287        Builtin::View(&MZ_CLUSTER_REPLICA_UTILIZATION_HISTORY),
1288        Builtin::View(&MZ_DATAFLOW_OPERATOR_PARENTS_PER_WORKER),
1289        Builtin::View(&MZ_DATAFLOW_OPERATOR_PARENTS),
1290        Builtin::View(&MZ_DATAFLOW_ARRANGEMENT_SIZES),
1291        Builtin::View(&MZ_EXPECTED_GROUP_SIZE_ADVICE),
1292        Builtin::View(&MZ_COMPUTE_FRONTIERS),
1293        Builtin::View(&MZ_DATAFLOW_CHANNEL_OPERATORS_PER_WORKER),
1294        Builtin::View(&MZ_DATAFLOW_CHANNEL_OPERATORS),
1295        Builtin::View(&MZ_COMPUTE_IMPORT_FRONTIERS),
1296        Builtin::View(&MZ_MESSAGE_COUNTS_PER_WORKER),
1297        Builtin::View(&MZ_MESSAGE_COUNTS),
1298        Builtin::View(&MZ_ACTIVE_PEEKS),
1299        Builtin::View(&MZ_COMPUTE_OPERATOR_DURATIONS_HISTOGRAM_PER_WORKER),
1300        Builtin::View(&MZ_COMPUTE_OPERATOR_DURATIONS_HISTOGRAM),
1301        Builtin::View(&MZ_RECORDS_PER_DATAFLOW_OPERATOR_PER_WORKER),
1302        Builtin::View(&MZ_RECORDS_PER_DATAFLOW_OPERATOR),
1303        Builtin::View(&MZ_RECORDS_PER_DATAFLOW_PER_WORKER),
1304        Builtin::View(&MZ_RECORDS_PER_DATAFLOW),
1305        Builtin::View(&MZ_PEEK_DURATIONS_HISTOGRAM_PER_WORKER),
1306        Builtin::View(&MZ_PEEK_DURATIONS_HISTOGRAM),
1307        Builtin::View(&MZ_SCHEDULING_ELAPSED_PER_WORKER),
1308        Builtin::View(&MZ_SCHEDULING_ELAPSED),
1309        Builtin::View(&MZ_SCHEDULING_PARKS_HISTOGRAM_PER_WORKER),
1310        Builtin::View(&MZ_SCHEDULING_PARKS_HISTOGRAM),
1311        Builtin::View(&MZ_SHOW_ALL_OBJECTS),
1312        Builtin::View(&MZ_SHOW_COLUMNS),
1313        Builtin::View(&MZ_SHOW_CLUSTERS),
1314        Builtin::View(&MZ_SHOW_SECRETS),
1315        Builtin::View(&MZ_SHOW_DATABASES),
1316        Builtin::View(&MZ_SHOW_SCHEMAS),
1317        Builtin::View(&MZ_SHOW_TABLES),
1318        Builtin::View(&MZ_SHOW_VIEWS),
1319        Builtin::View(&MZ_SHOW_TYPES),
1320        Builtin::View(&MZ_SHOW_ROLES),
1321        Builtin::View(&MZ_SHOW_CONNECTIONS),
1322        Builtin::View(&MZ_SHOW_SOURCES),
1323        Builtin::View(&MZ_SHOW_SINKS),
1324        Builtin::View(&MZ_SHOW_MATERIALIZED_VIEWS),
1325        Builtin::View(&MZ_SHOW_INDEXES),
1326        Builtin::View(&MZ_CLUSTER_REPLICA_HISTORY),
1327        Builtin::View(&MZ_CLUSTER_REPLICA_NAME_HISTORY),
1328        Builtin::View(&MZ_TIMEZONE_NAMES),
1329        Builtin::View(&MZ_TIMEZONE_ABBREVIATIONS),
1330        Builtin::View(&PG_NAMESPACE_ALL_DATABASES),
1331        Builtin::Index(&PG_NAMESPACE_ALL_DATABASES_IND),
1332        Builtin::View(&PG_NAMESPACE),
1333        Builtin::View(&PG_CLASS_ALL_DATABASES),
1334        Builtin::Index(&PG_CLASS_ALL_DATABASES_IND),
1335        Builtin::View(&PG_CLASS),
1336        Builtin::View(&PG_DEPEND),
1337        Builtin::View(&PG_DATABASE),
1338        Builtin::View(&PG_INDEX),
1339        Builtin::View(&PG_TYPE_ALL_DATABASES),
1340        Builtin::Index(&PG_TYPE_ALL_DATABASES_IND),
1341        Builtin::View(&PG_TYPE),
1342        Builtin::View(&PG_DESCRIPTION_ALL_DATABASES),
1343        Builtin::Index(&PG_DESCRIPTION_ALL_DATABASES_IND),
1344        Builtin::View(&PG_DESCRIPTION),
1345        Builtin::View(&PG_ATTRIBUTE_ALL_DATABASES),
1346        Builtin::Index(&PG_ATTRIBUTE_ALL_DATABASES_IND),
1347        Builtin::View(&PG_ATTRIBUTE),
1348        Builtin::View(&PG_PROC),
1349        Builtin::View(&PG_OPERATOR),
1350        Builtin::View(&PG_RANGE),
1351        Builtin::View(&PG_ENUM),
1352        Builtin::View(&PG_ATTRDEF_ALL_DATABASES),
1353        Builtin::Index(&PG_ATTRDEF_ALL_DATABASES_IND),
1354        Builtin::View(&PG_ATTRDEF),
1355        Builtin::View(&PG_SETTINGS),
1356        Builtin::View(&PG_AUTH_MEMBERS),
1357        Builtin::View(&PG_CONSTRAINT),
1358        Builtin::View(&PG_TABLES),
1359        Builtin::View(&PG_TABLESPACE),
1360        Builtin::View(&PG_ACCESS_METHODS),
1361        Builtin::View(&PG_LOCKS),
1362        Builtin::View(&PG_AUTHID_CORE),
1363        Builtin::Index(&PG_AUTHID_CORE_IND),
1364        Builtin::View(&PG_AUTHID),
1365        Builtin::View(&PG_ROLES),
1366        Builtin::View(&PG_USER),
1367        Builtin::View(&PG_VIEWS),
1368        Builtin::View(&PG_MATVIEWS),
1369        Builtin::View(&PG_COLLATION),
1370        Builtin::View(&PG_POLICY),
1371        Builtin::View(&PG_INHERITS),
1372        Builtin::View(&PG_AGGREGATE),
1373        Builtin::View(&PG_TRIGGER),
1374        Builtin::View(&PG_REWRITE),
1375        Builtin::View(&PG_EXTENSION),
1376        Builtin::View(&PG_EVENT_TRIGGER),
1377        Builtin::View(&PG_LANGUAGE),
1378        Builtin::View(&PG_SHDESCRIPTION),
1379        Builtin::View(&PG_INDEXES),
1380        Builtin::View(&PG_TIMEZONE_ABBREVS),
1381        Builtin::View(&PG_TIMEZONE_NAMES),
1382        Builtin::View(&INFORMATION_SCHEMA_APPLICABLE_ROLES),
1383        Builtin::View(&INFORMATION_SCHEMA_COLUMNS),
1384        Builtin::View(&INFORMATION_SCHEMA_ENABLED_ROLES),
1385        Builtin::View(&INFORMATION_SCHEMA_KEY_COLUMN_USAGE),
1386        Builtin::View(&INFORMATION_SCHEMA_REFERENTIAL_CONSTRAINTS),
1387        Builtin::View(&INFORMATION_SCHEMA_ROUTINES),
1388        Builtin::View(&INFORMATION_SCHEMA_SCHEMATA),
1389        Builtin::View(&INFORMATION_SCHEMA_TABLES),
1390        Builtin::View(&INFORMATION_SCHEMA_TABLE_CONSTRAINTS),
1391        Builtin::View(&INFORMATION_SCHEMA_TABLE_PRIVILEGES),
1392        Builtin::View(&INFORMATION_SCHEMA_ROLE_TABLE_GRANTS),
1393        Builtin::View(&INFORMATION_SCHEMA_TRIGGERS),
1394        Builtin::View(&INFORMATION_SCHEMA_VIEWS),
1395        Builtin::View(&INFORMATION_SCHEMA_CHARACTER_SETS),
1396        Builtin::View(&MZ_SHOW_ROLE_MEMBERS),
1397        Builtin::View(&MZ_SHOW_MY_ROLE_MEMBERS),
1398        Builtin::View(&MZ_SHOW_SYSTEM_PRIVILEGES),
1399        Builtin::View(&MZ_SHOW_MY_SYSTEM_PRIVILEGES),
1400        Builtin::View(&MZ_SHOW_CLUSTER_PRIVILEGES),
1401        Builtin::View(&MZ_SHOW_MY_CLUSTER_PRIVILEGES),
1402        Builtin::View(&MZ_SHOW_DATABASE_PRIVILEGES),
1403        Builtin::View(&MZ_SHOW_MY_DATABASE_PRIVILEGES),
1404        Builtin::View(&MZ_SHOW_SCHEMA_PRIVILEGES),
1405        Builtin::View(&MZ_SHOW_MY_SCHEMA_PRIVILEGES),
1406        Builtin::View(&MZ_SHOW_OBJECT_PRIVILEGES),
1407        Builtin::View(&MZ_SHOW_MY_OBJECT_PRIVILEGES),
1408        Builtin::View(&MZ_SHOW_ALL_PRIVILEGES),
1409        Builtin::View(&MZ_SHOW_ALL_MY_PRIVILEGES),
1410        Builtin::View(&MZ_SHOW_DEFAULT_PRIVILEGES),
1411        Builtin::View(&MZ_SHOW_MY_DEFAULT_PRIVILEGES),
1412        Builtin::Source(&MZ_SINK_STATUS_HISTORY),
1413        Builtin::View(&MZ_SINK_STATUSES),
1414        Builtin::Source(&MZ_SOURCE_STATUS_HISTORY),
1415        Builtin::Source(&MZ_AWS_PRIVATELINK_CONNECTION_STATUS_HISTORY),
1416        Builtin::View(&MZ_AWS_PRIVATELINK_CONNECTION_STATUSES),
1417        Builtin::Source(&MZ_STATEMENT_EXECUTION_HISTORY),
1418        Builtin::View(&MZ_STATEMENT_EXECUTION_HISTORY_REDACTED),
1419        Builtin::Source(&MZ_PREPARED_STATEMENT_HISTORY),
1420        Builtin::Source(&MZ_SESSION_HISTORY),
1421        Builtin::Source(&MZ_SQL_TEXT),
1422        Builtin::View(&MZ_SQL_TEXT_REDACTED),
1423        Builtin::View(&MZ_RECENT_SQL_TEXT),
1424        Builtin::View(&MZ_RECENT_SQL_TEXT_REDACTED),
1425        Builtin::Index(&MZ_RECENT_SQL_TEXT_IND),
1426        Builtin::View(&MZ_ACTIVITY_LOG_THINNED),
1427        Builtin::View(&MZ_RECENT_ACTIVITY_LOG_THINNED),
1428        Builtin::View(&MZ_RECENT_ACTIVITY_LOG),
1429        Builtin::View(&MZ_RECENT_ACTIVITY_LOG_REDACTED),
1430        Builtin::Index(&MZ_RECENT_ACTIVITY_LOG_THINNED_IND),
1431        Builtin::View(&MZ_SOURCE_STATUSES),
1432        Builtin::Source(&MZ_STATEMENT_LIFECYCLE_HISTORY),
1433        Builtin::Source(&MZ_STORAGE_SHARDS),
1434        Builtin::Source(&MZ_SOURCE_STATISTICS_RAW),
1435        Builtin::Source(&MZ_SINK_STATISTICS_RAW),
1436        Builtin::View(&MZ_SOURCE_STATISTICS_WITH_HISTORY),
1437        Builtin::Index(&MZ_SOURCE_STATISTICS_WITH_HISTORY_IND),
1438        Builtin::View(&MZ_SOURCE_STATISTICS),
1439        Builtin::Index(&MZ_SOURCE_STATISTICS_IND),
1440        Builtin::View(&MZ_SINK_STATISTICS),
1441        Builtin::Index(&MZ_SINK_STATISTICS_IND),
1442        Builtin::View(&MZ_STORAGE_USAGE),
1443        Builtin::Source(&MZ_FRONTIERS),
1444        Builtin::View(&MZ_GLOBAL_FRONTIERS),
1445        Builtin::Source(&MZ_WALLCLOCK_LAG_HISTORY),
1446        Builtin::View(&MZ_WALLCLOCK_GLOBAL_LAG_HISTORY),
1447        Builtin::View(&MZ_WALLCLOCK_GLOBAL_LAG_RECENT_HISTORY),
1448        Builtin::View(&MZ_WALLCLOCK_GLOBAL_LAG),
1449        Builtin::Source(&MZ_WALLCLOCK_GLOBAL_LAG_HISTOGRAM_RAW),
1450        Builtin::View(&MZ_WALLCLOCK_GLOBAL_LAG_HISTOGRAM),
1451        Builtin::Source(&MZ_MATERIALIZED_VIEW_REFRESHES),
1452        Builtin::Source(&MZ_COMPUTE_DEPENDENCIES),
1453        Builtin::View(&MZ_MATERIALIZATION_DEPENDENCIES),
1454        Builtin::View(&MZ_OBJECT_GRAPH_EDGES),
1455        Builtin::View(&MZ_MATERIALIZATION_LAG),
1456        Builtin::View(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW),
1457        Builtin::View(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW_3H),
1458        Builtin::View(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW_24H),
1459        Builtin::View(&MZ_COMPUTE_ERROR_COUNTS_PER_WORKER),
1460        Builtin::View(&MZ_COMPUTE_ERROR_COUNTS),
1461        Builtin::Source(&MZ_COMPUTE_ERROR_COUNTS_RAW_UNIFIED),
1462        Builtin::Source(&MZ_COMPUTE_HYDRATION_TIMES),
1463        Builtin::Source(&MZ_OBJECT_ARRANGEMENT_SIZES_UNIFIED),
1464        Builtin::Index(&MZ_OBJECT_ARRANGEMENT_SIZES_IND),
1465        Builtin::Table(&MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY),
1466        Builtin::Index(&MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_OBJECT_IND),
1467        Builtin::Index(&MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_TS_IND),
1468        Builtin::Log(&MZ_COMPUTE_LIR_MAPPING_PER_WORKER),
1469        Builtin::View(&MZ_LIR_MAPPING),
1470        Builtin::Source(&MZ_COMPUTE_OPERATOR_HYDRATION_STATUSES),
1471        Builtin::Source(&MZ_CLUSTER_REPLICA_FRONTIERS),
1472        Builtin::View(&MZ_COMPUTE_HYDRATION_STATUSES),
1473        Builtin::View(&MZ_HYDRATION_STATUSES),
1474        Builtin::Index(&MZ_HYDRATION_STATUSES_IND),
1475        Builtin::View(&MZ_SHOW_CLUSTER_REPLICAS),
1476        Builtin::View(&MZ_SHOW_NETWORK_POLICIES),
1477        Builtin::View(&MZ_CLUSTER_DEPLOYMENT_LINEAGE),
1478        Builtin::Index(&MZ_SHOW_DATABASES_IND),
1479        Builtin::Index(&MZ_SHOW_SCHEMAS_IND),
1480        Builtin::Index(&MZ_SHOW_CONNECTIONS_IND),
1481        Builtin::Index(&MZ_SHOW_TABLES_IND),
1482        Builtin::Index(&MZ_SHOW_SOURCES_IND),
1483        Builtin::Index(&MZ_SHOW_VIEWS_IND),
1484        Builtin::Index(&MZ_SHOW_MATERIALIZED_VIEWS_IND),
1485        Builtin::Index(&MZ_SHOW_SINKS_IND),
1486        Builtin::Index(&MZ_SHOW_TYPES_IND),
1487        Builtin::Index(&MZ_SHOW_ALL_OBJECTS_IND),
1488        Builtin::Index(&MZ_SHOW_INDEXES_IND),
1489        Builtin::Index(&MZ_SHOW_COLUMNS_IND),
1490        Builtin::Index(&MZ_SHOW_CLUSTERS_IND),
1491        Builtin::Index(&MZ_SHOW_CLUSTER_REPLICAS_IND),
1492        Builtin::Index(&MZ_SHOW_SECRETS_IND),
1493        Builtin::Index(&MZ_SHOW_ROLES_IND),
1494        Builtin::Index(&MZ_CLUSTERS_IND),
1495        Builtin::Index(&MZ_CLUSTER_RECONFIGURATIONS_IND),
1496        Builtin::Index(&MZ_CLUSTER_AUTO_SCALING_STRATEGIES_IND),
1497        Builtin::Index(&MZ_INDEXES_IND),
1498        Builtin::Index(&MZ_ROLES_IND),
1499        Builtin::Index(&MZ_SOURCES_IND),
1500        Builtin::Index(&MZ_SINKS_IND),
1501        Builtin::Index(&MZ_METRIC_SINKS_IND),
1502        Builtin::Index(&MZ_MATERIALIZED_VIEWS_IND),
1503        Builtin::Index(&MZ_SOURCE_STATUSES_IND),
1504        Builtin::Index(&MZ_SOURCE_STATUS_HISTORY_IND),
1505        Builtin::Index(&MZ_SINK_STATUSES_IND),
1506        Builtin::Index(&MZ_SINK_STATUS_HISTORY_IND),
1507        Builtin::Index(&MZ_CLUSTER_REPLICAS_IND),
1508        Builtin::Index(&MZ_CLUSTER_REPLICA_SIZES_IND),
1509        Builtin::Index(&MZ_CLUSTER_REPLICA_SIZE_INTERNAL_IND),
1510        Builtin::Index(&MZ_CLUSTER_REPLICA_STATUSES_IND),
1511        Builtin::Index(&MZ_CLUSTER_REPLICA_STATUS_HISTORY_IND),
1512        Builtin::Index(&MZ_CLUSTER_REPLICA_METRICS_IND),
1513        Builtin::Index(&MZ_CLUSTER_REPLICA_METRICS_HISTORY_IND),
1514        Builtin::Index(&MZ_CLUSTER_REPLICA_HISTORY_IND),
1515        Builtin::Index(&MZ_CLUSTER_REPLICA_NAME_HISTORY_IND),
1516        Builtin::Index(&MZ_OBJECT_LIFETIMES_IND),
1517        Builtin::Index(&MZ_OBJECT_HISTORY_IND),
1518        Builtin::Index(&MZ_OBJECT_DEPENDENCIES_IND),
1519        Builtin::Index(&MZ_COMPUTE_DEPENDENCIES_IND),
1520        Builtin::Index(&MZ_OBJECT_TRANSITIVE_DEPENDENCIES_IND),
1521        Builtin::Index(&MZ_OBJECT_GRAPH_EDGES_IND),
1522        Builtin::Index(&MZ_FRONTIERS_IND),
1523        Builtin::Index(&MZ_WALLCLOCK_GLOBAL_LAG_RECENT_HISTORY_IND),
1524        Builtin::Index(&MZ_KAFKA_SOURCES_IND),
1525        Builtin::Index(&MZ_WEBHOOK_SOURCES_IND),
1526        Builtin::Index(&MZ_COMMENTS_IND),
1527        Builtin::Index(&MZ_DATABASES_IND),
1528        Builtin::Index(&MZ_SCHEMAS_IND),
1529        Builtin::Index(&MZ_CONNECTIONS_IND),
1530        Builtin::Index(&MZ_TABLES_IND),
1531        Builtin::Index(&MZ_TYPES_IND),
1532        Builtin::Index(&MZ_OBJECTS_IND),
1533        Builtin::Index(&MZ_COLUMNS_IND),
1534        Builtin::Index(&MZ_SECRETS_IND),
1535        Builtin::Index(&MZ_VIEWS_IND),
1536        Builtin::Index(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW_IND),
1537        Builtin::Index(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW_3H_IND),
1538        Builtin::Index(&MZ_CONSOLE_CLUSTER_UTILIZATION_OVERVIEW_24H_IND),
1539        Builtin::Index(&MZ_CLUSTER_DEPLOYMENT_LINEAGE_IND),
1540        Builtin::Index(&MZ_CLUSTER_REPLICA_FRONTIERS_IND),
1541        Builtin::Index(&MZ_COMPUTE_HYDRATION_TIMES_IND),
1542        Builtin::View(&MZ_RECENT_STORAGE_USAGE),
1543        Builtin::Index(&MZ_RECENT_STORAGE_USAGE_IND),
1544        Builtin::Connection(&MZ_ANALYTICS),
1545        Builtin::View(&MZ_INDEX_ADVICE),
1546        Builtin::View(&MZ_MCP_DATA_PRODUCTS),
1547        Builtin::View(&MZ_MCP_DATA_PRODUCT_DETAILS),
1548        Builtin::Table(&MZ_OBJECT_HYDRATION_HISTORY),
1549        Builtin::Table(&MZ_REPLICA_HYDRATION_HISTORY),
1550    ];
1551
1552    builtin_items.extend(notice::builtins());
1553
1554    // Generate mz_object_dependencies_raw, which inlines every builtin's
1555    // dependency edges as VALUES.
1556    //
1557    // It has to run before ontology::generate_views and builtin::builtins, because those consume
1558    // mz_object_dependencies. However, because the views those two generators produce
1559    // have dependency edges of their own, it must use them as inputs. So this block runs both
1560    // generators early, keeps the edges of the views they hand back, and discards the views
1561    // themselves. The real ones are built below.
1562    {
1563        let ontology_preview = ontology::generate_views(&builtin_items);
1564        let builtin_reporter_preview: Vec<_> = builtin::builtins(&builtin_items).collect();
1565        let generator_input: Vec<_> = builtin_types
1566            .iter()
1567            .chain(builtin_funcs.iter())
1568            .chain(builtin_reporter_preview.iter())
1569            .chain(builtin_items.iter())
1570            .chain(ontology_preview.iter())
1571            .cloned()
1572            .collect();
1573        let mz_object_dependencies_raw =
1574            mz_object_dependencies::make_mz_object_dependencies_raw(&generator_input);
1575        let mz_object_dependencies_raw_ref: &'static BuiltinView =
1576            Box::leak(Box::new(mz_object_dependencies_raw));
1577        // The view goes directly before mz_object_dependencies, which reads it,
1578        // because BUILTINS_STATIC must list dependencies before their dependents.
1579        let insert_pos = builtin_items
1580            .iter()
1581            .position(|b| b.name() == "mz_object_dependencies")
1582            .expect("mz_object_dependencies must be present in builtin_items");
1583        builtin_items.insert(insert_pos, Builtin::View(mz_object_dependencies_raw_ref));
1584    }
1585
1586    // Generate ontology views by enumerating existing builtins.
1587    builtin_items.extend(ontology::generate_views(&builtin_items));
1588
1589    // Generate builtin relations reporting builtin objects last, since they need a complete view
1590    // of all other builtins.
1591    let mut builtin_builtins = builtin::builtins(&builtin_items).collect();
1592
1593    // Construct the full list of builtins, retaining dependency order.
1594    let mut builtins = Vec::new();
1595    builtins.append(&mut builtin_types);
1596    builtins.append(&mut builtin_funcs);
1597    builtins.append(&mut builtin_builtins);
1598    builtins.append(&mut builtin_items);
1599
1600    builtins
1601});
1602pub const BUILTIN_ROLES: &[&BuiltinRole] = &[
1603    &MZ_SYSTEM_ROLE,
1604    &MZ_SUPPORT_ROLE,
1605    &MZ_ANALYTICS_ROLE,
1606    &MZ_MONITOR_ROLE,
1607    &MZ_MONITOR_REDACTED,
1608    &MZ_JWT_SYNC_ROLE,
1609];
1610pub const BUILTIN_CLUSTERS: &[&BuiltinCluster] = &[
1611    &MZ_SYSTEM_CLUSTER,
1612    &MZ_CATALOG_SERVER_CLUSTER,
1613    &MZ_PROBE_CLUSTER,
1614    &MZ_SUPPORT_CLUSTER,
1615    &MZ_ANALYTICS_CLUSTER,
1616];
1617
1618#[allow(non_snake_case)]
1619pub mod BUILTINS {
1620    use super::*;
1621
1622    pub fn logs() -> impl Iterator<Item = &'static BuiltinLog> {
1623        BUILTINS_STATIC.iter().filter_map(|b| match b {
1624            Builtin::Log(log) => Some(*log),
1625            _ => None,
1626        })
1627    }
1628
1629    pub fn types() -> impl Iterator<Item = &'static BuiltinType<NameReference>> {
1630        BUILTINS_STATIC.iter().filter_map(|b| match b {
1631            Builtin::Type(typ) => Some(*typ),
1632            _ => None,
1633        })
1634    }
1635
1636    pub fn views() -> impl Iterator<Item = &'static BuiltinView> {
1637        BUILTINS_STATIC.iter().filter_map(|b| match b {
1638            Builtin::View(view) => Some(*view),
1639            _ => None,
1640        })
1641    }
1642
1643    pub fn materialized_views() -> impl Iterator<Item = &'static BuiltinMaterializedView> {
1644        BUILTINS_STATIC.iter().filter_map(|b| match b {
1645            Builtin::MaterializedView(mv) => Some(*mv),
1646            _ => None,
1647        })
1648    }
1649
1650    pub fn funcs() -> impl Iterator<Item = &'static BuiltinFunc> {
1651        BUILTINS_STATIC.iter().filter_map(|b| match b {
1652            Builtin::Func(func) => Some(func),
1653            _ => None,
1654        })
1655    }
1656
1657    pub fn iter() -> impl Iterator<Item = &'static Builtin<NameReference>> {
1658        BUILTINS_STATIC.iter()
1659    }
1660}
1661
1662pub static BUILTIN_LOG_LOOKUP: LazyLock<HashMap<&'static str, &'static BuiltinLog>> =
1663    LazyLock::new(|| BUILTINS::logs().map(|log| (log.name, log)).collect());
1664/// Keys are builtin object description, values are the builtin index when sorted by dependency and
1665/// the builtin itself.
1666pub static BUILTIN_LOOKUP: LazyLock<
1667    HashMap<SystemObjectDescription, (usize, &'static Builtin<NameReference>)>,
1668> = LazyLock::new(|| {
1669    BUILTINS_STATIC
1670        .iter()
1671        .enumerate()
1672        .map(|(idx, builtin)| {
1673            (
1674                SystemObjectDescription {
1675                    schema_name: builtin.schema().to_string(),
1676                    object_type: builtin.catalog_item_type(),
1677                    object_name: builtin.name().to_string(),
1678                },
1679                (idx, builtin),
1680            )
1681        })
1682        .collect()
1683});
1684
1685#[cfg(test)]
1686mod tests {
1687    use std::collections::{BTreeMap, BTreeSet};
1688
1689    use mz_pgrepr::oid::FIRST_MATERIALIZE_OID;
1690    use mz_sql_parser::ast::visit::{self, Visit};
1691    use mz_sql_parser::ast::{Raw, RawItemName, UnresolvedItemName};
1692
1693    use super::*;
1694
1695    /// Recomputes `mz_pgrepr::regproc::NAMES` from the builtin function registry
1696    /// and fails when the checked-in copy has drifted. It is checked in as data
1697    /// because `mz-pgrepr` sits below this crate in the dependency graph and so
1698    /// cannot read the registry itself.
1699    ///
1700    /// Run with `REWRITE=1` to splice the recomputed table back into
1701    /// `src/pgrepr-consts/src/regproc.rs`.
1702    #[mz_ore::test]
1703    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
1704    fn test_regproc_names_match_builtin_functions() {
1705        // `effective_search_path` unconditionally prepends these two, so a
1706        // uniquely named function in either resolves from its bare name.
1707        const IMPLICITLY_SEARCHED: &[&str] = &[MZ_CATALOG_SCHEMA, PG_CATALOG_SCHEMA];
1708
1709        // A bare name only identifies one OID when exactly one impl anywhere in
1710        // the registry carries it, so count impls across schemas.
1711        let mut impls_per_name: BTreeMap<&str, usize> = BTreeMap::new();
1712        for func in BUILTINS::funcs() {
1713            *impls_per_name.entry(func.name).or_default() += func.inner.func_impls().len();
1714        }
1715
1716        let mut expected: BTreeMap<u32, String> = BTreeMap::new();
1717        for func in BUILTINS::funcs() {
1718            // Mirrors PostgreSQL's `regprocout`, which qualifies a name that
1719            // would not resolve back to this OID on its own.
1720            let rendered =
1721                if impls_per_name[func.name] == 1 && IMPLICITLY_SEARCHED.contains(&func.schema) {
1722                    func.name.to_string()
1723                } else {
1724                    format!("{}.{}", func.schema, func.name)
1725                };
1726            for details in func.inner.func_impls() {
1727                let previous = expected.insert(details.oid, rendered.clone());
1728                assert_eq!(
1729                    previous, None,
1730                    "two builtin functions share OID {}",
1731                    details.oid
1732                );
1733            }
1734        }
1735
1736        let table: String = expected
1737            .iter()
1738            .map(|(oid, name)| format!("    ({}, \"{}\"),\n", oid, name))
1739            .collect();
1740
1741        if std::env::var_os("REWRITE").is_some() {
1742            rewrite_regproc_names(&table);
1743            return;
1744        }
1745
1746        let actual: BTreeMap<u32, String> = mz_pgrepr::regproc::NAMES
1747            .iter()
1748            .map(|(oid, name)| (*oid, name.to_string()))
1749            .collect();
1750
1751        if actual != expected {
1752            panic!(
1753                "mz_pgrepr::regproc::NAMES has drifted from the builtin function \
1754                 registry. Regenerate it with:\n\n    \
1755                 REWRITE=1 cargo test -p mz-catalog \
1756                 test_regproc_names_match_builtin_functions\n"
1757            );
1758        }
1759    }
1760
1761    /// Replaces the generated region of `mz_pgrepr::regproc::NAMES` with
1762    /// `table`, leaving every other byte of the file alone.
1763    ///
1764    /// The path is relative to this crate's directory, which is the working
1765    /// directory `cargo test` runs in.
1766    ///
1767    /// A splice anchored anywhere but the table would clobber the lookup
1768    /// functions below it, so both markers have to appear exactly once.
1769    fn rewrite_regproc_names(table: &str) {
1770        const PATH: &str = "../pgrepr-consts/src/regproc.rs";
1771        const BEGIN: &str = "    // BEGIN GENERATED\n";
1772        const END: &str = "    // END GENERATED\n";
1773
1774        let contents =
1775            std::fs::read_to_string(PATH).unwrap_or_else(|e| panic!("reading '{PATH}': {e}"));
1776        for marker in [BEGIN, END] {
1777            let count = contents.matches(marker).count();
1778            assert_eq!(
1779                count,
1780                1,
1781                "'{}' appears {} times in '{}', expected exactly once",
1782                marker.trim(),
1783                count,
1784                PATH
1785            );
1786        }
1787        let begin = contents.find(BEGIN).expect("checked above") + BEGIN.len();
1788        let end = contents.find(END).expect("checked above");
1789        assert!(
1790            begin <= end,
1791            "'{}' precedes '{}' in '{}'",
1792            END.trim(),
1793            BEGIN.trim(),
1794            PATH
1795        );
1796
1797        let rewritten = format!("{}{}{}", &contents[..begin], table, &contents[end..]);
1798        std::fs::write(PATH, rewritten).unwrap_or_else(|e| panic!("writing '{PATH}': {e}"));
1799    }
1800
1801    #[mz_ore::test]
1802    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
1803    fn test_builtin_type_schema() {
1804        for typ in BUILTINS::types() {
1805            if typ.oid < FIRST_MATERIALIZE_OID {
1806                assert_eq!(
1807                    typ.schema, PG_CATALOG_SCHEMA,
1808                    "{typ:?} should be in {PG_CATALOG_SCHEMA} schema"
1809                );
1810            } else {
1811                // `mz_pgrepr::Type` resolution relies on all non-PG types existing in the
1812                // mz_catalog schema.
1813                assert_eq!(
1814                    typ.schema, MZ_CATALOG_SCHEMA,
1815                    "{typ:?} should be in {MZ_CATALOG_SCHEMA} schema"
1816                );
1817            }
1818        }
1819    }
1820
1821    /// Visitor that collects the last component of all referenced
1822    /// item names from a SQL AST.
1823    struct ItemNameCollector {
1824        names: BTreeSet<String>,
1825    }
1826
1827    impl<'ast> Visit<'ast, Raw> for ItemNameCollector {
1828        fn visit_item_name(&mut self, name: &'ast <Raw as mz_sql_parser::ast::AstInfo>::ItemName) {
1829            let unresolved: &UnresolvedItemName = match name {
1830                RawItemName::Name(n) | RawItemName::Id(_, n, _) => n,
1831            };
1832            let parts = &unresolved.0;
1833            if !parts.is_empty() {
1834                let obj_name = parts[parts.len() - 1].as_str().to_string();
1835                self.names.insert(obj_name);
1836            }
1837            visit::visit_item_name(self, name);
1838        }
1839    }
1840
1841    /// Tests that `BUILTINS_STATIC` is ordered respecting dependencies:
1842    /// if builtin A references builtin B in its SQL, then B must appear
1843    /// before A in the list. (This ordering is assumed by, e.g.,
1844    /// `sort_updates` during catalog migrations.)
1845    #[mz_ore::test]
1846    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
1847    fn test_builtins_static_dependency_order() {
1848        // Build a map from name -> (schema, index) for all builtins.
1849        // We look up by just the name (last component) to catch
1850        // unqualified references in SQL.
1851        let mut builtin_by_name: BTreeMap<&str, (&str, usize)> = BTreeMap::new();
1852        let mut duplicate_names = Vec::new();
1853        for (idx, builtin) in BUILTINS_STATIC.iter().enumerate() {
1854            if let Some((prev_schema, prev_idx)) =
1855                builtin_by_name.insert(builtin.name(), (builtin.schema(), idx))
1856            {
1857                // Only flag duplicates across different schemas.
1858                // Same-schema duplicates (e.g., range types that
1859                // appear as both Type and Func) are fine because
1860                // they resolve to the same schema.
1861                if prev_schema != builtin.schema() {
1862                    duplicate_names.push(format!(
1863                        "name {:?} appears in both {}.{} (index \
1864                         {}) and {}.{} (index {})",
1865                        builtin.name(),
1866                        prev_schema,
1867                        builtin.name(),
1868                        prev_idx,
1869                        builtin.schema(),
1870                        builtin.name(),
1871                        idx,
1872                    ));
1873                }
1874            }
1875        }
1876        assert!(
1877            duplicate_names.is_empty(),
1878            "BUILTINS_STATIC has duplicate names across different \
1879             schemas (this test needs adjustment if such duplicates \
1880             are intentional):\n{}",
1881            duplicate_names.join("\n"),
1882        );
1883
1884        // Get the `CREATE ...` SQL for builtins that have it.
1885        let get_create_sql = |builtin: &Builtin<NameReference>| -> Option<String> {
1886            match builtin {
1887                Builtin::View(v) => Some(v.create_sql()),
1888                Builtin::MaterializedView(mv) => Some(mv.create_sql()),
1889                Builtin::Index(idx) => Some(idx.create_sql()),
1890                _ => None,
1891            }
1892        };
1893
1894        // For each SQL-bearing builtin, parse its SQL, walk the AST to
1895        // find referenced item names, and check that all referenced
1896        // builtins appear earlier in BUILTINS_STATIC.
1897        let mut violations = Vec::new();
1898        for (idx, builtin) in BUILTINS_STATIC.iter().enumerate() {
1899            let create_sql = match get_create_sql(builtin) {
1900                Some(sql) => sql,
1901                None => continue,
1902            };
1903
1904            let stmts = mz_sql_parser::parser::parse_statements(&create_sql).unwrap_or_else(|e| {
1905                panic!(
1906                    "failed to parse SQL for {}.{}: \
1907                         {e}\nSQL: {create_sql}",
1908                    builtin.schema(),
1909                    builtin.name(),
1910                )
1911            });
1912
1913            let mut collector = ItemNameCollector {
1914                names: BTreeSet::new(),
1915            };
1916            for stmt in &stmts {
1917                collector.visit_statement(&stmt.ast);
1918            }
1919
1920            for ref_name in &collector.names {
1921                if let Some(&(ref_schema, dep_idx)) = builtin_by_name.get(ref_name.as_str()) {
1922                    if dep_idx > idx {
1923                        violations.push(format!(
1924                            "{}.{} (index {}) references \
1925                             {}.{} (index {}), but the \
1926                             dependency appears later in \
1927                             BUILTINS_STATIC",
1928                            builtin.schema(),
1929                            builtin.name(),
1930                            idx,
1931                            ref_schema,
1932                            ref_name,
1933                            dep_idx,
1934                        ));
1935                    }
1936                }
1937            }
1938        }
1939
1940        assert!(
1941            violations.is_empty(),
1942            "BUILTINS_STATIC has dependency ordering violations:\n{}",
1943            violations.join("\n"),
1944        );
1945    }
1946
1947    /// Validates ontology metadata consistency:
1948    /// - Every link target references an entity that exists.
1949    /// - No duplicate entity names.
1950    /// - Every annotated builtin has a non-empty entity_name and description.
1951    #[mz_ore::test]
1952    #[cfg_attr(miri, ignore)]
1953    fn test_ontology_consistency() {
1954        // Collect all entity names from builtins with ontology annotations.
1955        let mut entity_names: BTreeSet<String> = BTreeSet::new();
1956        let mut duplicate_entities = Vec::new();
1957
1958        for builtin in BUILTINS_STATIC.iter() {
1959            let ontology = match builtin {
1960                Builtin::Table(t) => t.ontology.as_ref(),
1961                Builtin::View(v) => v.ontology.as_ref(),
1962                Builtin::MaterializedView(mv) => mv.ontology.as_ref(),
1963                Builtin::Source(s) => s.ontology.as_ref(),
1964                Builtin::Log(l) => l.ontology.as_ref(),
1965                _ => None,
1966            };
1967            if let Some(ont) = ontology {
1968                assert!(
1969                    !ont.entity_name.is_empty(),
1970                    "builtin {} has empty ontology entity_name",
1971                    builtin.name()
1972                );
1973                assert!(
1974                    !ont.description.is_empty(),
1975                    "builtin {} ({}) has empty ontology description",
1976                    builtin.name(),
1977                    ont.entity_name
1978                );
1979                if !entity_names.insert(ont.entity_name.to_string()) {
1980                    duplicate_entities.push(format!(
1981                        "duplicate entity_name {:?} on builtin {}",
1982                        ont.entity_name,
1983                        builtin.name()
1984                    ));
1985                }
1986            }
1987        }
1988        assert!(
1989            duplicate_entities.is_empty(),
1990            "ontology has duplicate entity names:\n{}",
1991            duplicate_entities.join("\n"),
1992        );
1993
1994        // Validate link targets reference existing entities.
1995        let mut bad_targets = Vec::new();
1996        for builtin in BUILTINS_STATIC.iter() {
1997            let ontology = match builtin {
1998                Builtin::Table(t) => t.ontology.as_ref(),
1999                Builtin::View(v) => v.ontology.as_ref(),
2000                Builtin::MaterializedView(mv) => mv.ontology.as_ref(),
2001                Builtin::Source(s) => s.ontology.as_ref(),
2002                Builtin::Log(l) => l.ontology.as_ref(),
2003                _ => None,
2004            };
2005            if let Some(ont) = ontology {
2006                for link in ont.links {
2007                    if !entity_names.contains(link.target) {
2008                        bad_targets.push(format!(
2009                            "entity {:?} link {:?} targets {:?} which is not a known entity",
2010                            ont.entity_name, link.name, link.target
2011                        ));
2012                    }
2013                }
2014            }
2015        }
2016        assert!(
2017            bad_targets.is_empty(),
2018            "ontology has links targeting unknown entities:\n{}",
2019            bad_targets.join("\n"),
2020        );
2021
2022        // Semantic type annotations are typed (SemanticType enum), so validity
2023        // is guaranteed at compile time — no runtime check needed.
2024
2025        // Validate that every "reference" column (one whose semantic type implies
2026        // a FK relationship) is covered by an OntologyLink on entities that
2027        // already have at least one FK-style link.
2028        //
2029        // Scope: only entities that have started FK annotation (at least one
2030        // link with a source_column). Entities with only union/maps_to links,
2031        // or no links at all, are not yet fully annotated and are skipped to
2032        // avoid noise.
2033        //
2034        // Exemptions:
2035        // - Column at index 0 named "id": almost always the entity's own PK, not a FK (e.g.
2036        //   mz_objects.id, mz_functions.id).
2037        // - Columns in the relation's declared key set.
2038        //
2039        // "Reference" types are ID types that imply a FK. Discriminators
2040        // (ObjectType, ConnectionType, SourceType), OID, and metric types
2041        // (ByteCount, etc.) are excluded.
2042        let reference_sem_types: BTreeSet<SemanticType> = BTreeSet::from([
2043            SemanticType::CatalogItemId,
2044            SemanticType::GlobalId,
2045            SemanticType::ClusterId,
2046            SemanticType::ReplicaId,
2047            SemanticType::SchemaId,
2048            SemanticType::DatabaseId,
2049            SemanticType::RoleId,
2050            SemanticType::NetworkPolicyId,
2051        ]);
2052
2053        let mut uncovered_fk_cols = Vec::new();
2054        for builtin in BUILTINS_STATIC.iter() {
2055            let desc_storage;
2056            let (name, desc, ontology): (&str, &RelationDesc, Option<&Ontology>) = match builtin {
2057                Builtin::Table(t) => (t.name, &t.desc, t.ontology.as_ref()),
2058                Builtin::View(v) => (v.name, &v.desc, v.ontology.as_ref()),
2059                Builtin::MaterializedView(mv) => (mv.name, &mv.desc, mv.ontology.as_ref()),
2060                Builtin::Source(s) => (s.name, &s.desc, s.ontology.as_ref()),
2061                Builtin::Log(l) => {
2062                    desc_storage = l.variant.desc();
2063                    (l.name, &desc_storage, l.ontology.as_ref())
2064                }
2065                _ => continue,
2066            };
2067            let Some(ont) = ontology else { continue };
2068
2069            // Collect all source_column values declared by existing links.
2070            let linked_cols: BTreeSet<&str> = ont
2071                .links
2072                .iter()
2073                .filter_map(|link| match &link.properties {
2074                    LinkProperties::ForeignKey { source_column, .. } => Some(*source_column),
2075                    LinkProperties::Measures { source_column, .. } => Some(*source_column),
2076                    LinkProperties::DependsOn { source_column, .. } => Some(*source_column),
2077                    LinkProperties::MapsTo { source_column, .. } => Some(*source_column),
2078                    LinkProperties::Union { .. } => None,
2079                })
2080                .collect();
2081
2082            // Skip entities that have no FK-style links yet — they are either
2083            // unannotated or use only union/maps_to links. Only enforce
2084            // coverage on entities that have started FK annotation.
2085            if linked_cols.is_empty() {
2086                continue;
2087            }
2088
2089            // Column indices that are part of the declared key set.
2090            let pk_indices: BTreeSet<usize> = desc.typ().keys.iter().flatten().copied().collect();
2091
2092            for (col_name, sem) in ont.column_semantic_types {
2093                if !reference_sem_types.contains(sem) {
2094                    continue;
2095                }
2096                let Some(idx) = desc.iter_names().position(|n| n.as_str() == *col_name) else {
2097                    continue;
2098                };
2099                // Exempt the entity's own primary identifier: column 0 named
2100                // "id" is by convention the entity's own PK (not a FK), even
2101                // when no explicit with_key() is declared on the relation.
2102                if idx == 0 && *col_name == "id" {
2103                    continue;
2104                }
2105                if pk_indices.contains(&idx) {
2106                    continue;
2107                }
2108                if linked_cols.contains(*col_name) {
2109                    continue;
2110                }
2111                uncovered_fk_cols.push(format!(
2112                    "entity {:?} (builtin {}) column {:?} has semantic type {:?} but no OntologyLink covers it (add a link with source_column: {:?})",
2113                    ont.entity_name, name, col_name, sem, col_name
2114                ));
2115            }
2116        }
2117        assert!(
2118            uncovered_fk_cols.is_empty(),
2119            "ontology entities have FK-typed columns with no OntologyLink:\n{}",
2120            uncovered_fk_cols.join("\n"),
2121        );
2122
2123        // Validate that every source_column in a link actually names a column
2124        // in the entity's RelationDesc. This catches stale annotations after
2125        // column renames or removals. With typed LinkProperties this is mostly
2126        // belt-and-suspenders since the type system enforces field presence, but
2127        // we still need to verify the string value matches a real column.
2128        let mut bad_source_cols = Vec::new();
2129        for builtin in BUILTINS_STATIC.iter() {
2130            let desc_storage;
2131            let (name, desc, ontology): (&str, &RelationDesc, Option<&Ontology>) = match builtin {
2132                Builtin::Table(t) => (t.name, &t.desc, t.ontology.as_ref()),
2133                Builtin::View(v) => (v.name, &v.desc, v.ontology.as_ref()),
2134                Builtin::MaterializedView(mv) => (mv.name, &mv.desc, mv.ontology.as_ref()),
2135                Builtin::Source(s) => (s.name, &s.desc, s.ontology.as_ref()),
2136                Builtin::Log(l) => {
2137                    desc_storage = l.variant.desc();
2138                    (l.name, &desc_storage, l.ontology.as_ref())
2139                }
2140                _ => continue,
2141            };
2142            let Some(ont) = ontology else { continue };
2143
2144            let col_names: BTreeSet<&str> = desc.iter_names().map(|c| c.as_str()).collect();
2145
2146            for link in ont.links {
2147                let source_col = match &link.properties {
2148                    LinkProperties::ForeignKey { source_column, .. } => Some(*source_column),
2149                    LinkProperties::Measures { source_column, .. } => Some(*source_column),
2150                    LinkProperties::DependsOn { source_column, .. } => Some(*source_column),
2151                    LinkProperties::MapsTo { source_column, .. } => Some(*source_column),
2152                    LinkProperties::Union { .. } => None,
2153                };
2154                let Some(col) = source_col else { continue };
2155                if !col_names.contains(col) {
2156                    bad_source_cols.push(format!(
2157                        "entity {:?} (builtin {}) link {:?} references source_column {:?} which does not exist in the relation",
2158                        ont.entity_name, name, link.name, col
2159                    ));
2160                }
2161                let extra_key_columns = match &link.properties {
2162                    LinkProperties::ForeignKey {
2163                        extra_key_columns: Some(extras),
2164                        ..
2165                    } => Some(*extras),
2166                    LinkProperties::Measures {
2167                        extra_key_columns: Some(extras),
2168                        ..
2169                    } => Some(*extras),
2170                    _ => None,
2171                };
2172                if let Some(extras) = extra_key_columns {
2173                    for (src_col, _) in extras {
2174                        if !col_names.contains(*src_col) {
2175                            bad_source_cols.push(format!(
2176                                "entity {:?} (builtin {}) link {:?} extra_key_columns references {:?} which does not exist in the relation",
2177                                ont.entity_name, name, link.name, src_col
2178                            ));
2179                        }
2180                    }
2181                }
2182            }
2183        }
2184        assert!(
2185            bad_source_cols.is_empty(),
2186            "ontology links reference non-existent source_columns:\n{}",
2187            bad_source_cols.join("\n"),
2188        );
2189
2190        // Sanity check: we have a reasonable number of annotated entities.
2191        assert!(
2192            entity_names.len() > 120,
2193            "expected > 120 ontology entities, found {}",
2194            entity_names.len()
2195        );
2196    }
2197
2198    /// Verify that `LinkProperties` serializes to the same JSON that the old
2199    /// hand-written `properties_json` strings contained. One representative
2200    /// case per constructor/variant is enough — the important thing is that
2201    /// field names, enum tag values, and skip-if-None/false behaviour are all
2202    /// correct.
2203    #[mz_ore::test]
2204    fn test_link_properties_serialization() {
2205        let check = |props: LinkProperties, expected: &str| {
2206            let got = serde_json::to_string(&props).expect("serialize");
2207            let got_val: serde_json::Value = serde_json::from_str(&got).expect("parse got");
2208            let exp_val: serde_json::Value =
2209                serde_json::from_str(expected).expect("parse expected");
2210            assert_eq!(got_val, exp_val, "mismatch for {expected}");
2211        };
2212
2213        // fk — basic, no optional fields
2214        check(
2215            LinkProperties::fk("owner_id", "id", Cardinality::ManyToOne),
2216            r#"{"kind":"foreign_key","source_column":"owner_id","target_column":"id","cardinality":"many_to_one"}"#,
2217        );
2218        // fk_composite — extra_key_columns present
2219        check(
2220            LinkProperties::fk_composite(
2221                "operator_id",
2222                "id",
2223                Cardinality::ManyToOne,
2224                &[("worker_id", "worker_id")],
2225            ),
2226            r#"{"kind":"foreign_key","source_column":"operator_id","target_column":"id","cardinality":"many_to_one","extra_key_columns":[["worker_id","worker_id"]]}"#,
2227        );
2228        // fk — one_to_one cardinality
2229        check(
2230            LinkProperties::fk("id", "id", Cardinality::OneToOne),
2231            r#"{"kind":"foreign_key","source_column":"id","target_column":"id","cardinality":"one_to_one"}"#,
2232        );
2233        // fk_nullable — nullable field present and true
2234        check(
2235            LinkProperties::fk_nullable("database_id", "id", Cardinality::ManyToOne),
2236            r#"{"kind":"foreign_key","source_column":"database_id","target_column":"id","cardinality":"many_to_one","nullable":true}"#,
2237        );
2238        // fk_typed — source_id_type present, requires_mapping absent
2239        check(
2240            LinkProperties::fk_typed(
2241                "replica_id",
2242                "id",
2243                Cardinality::ManyToOne,
2244                mz_repr::SemanticType::CatalogItemId,
2245            ),
2246            r#"{"kind":"foreign_key","source_column":"replica_id","target_column":"id","cardinality":"many_to_one","source_id_type":"CatalogItemId"}"#,
2247        );
2248        // fk_mapped — source_id_type + requires_mapping both present
2249        check(
2250            LinkProperties::fk_mapped(
2251                "object_id",
2252                "id",
2253                Cardinality::ManyToOne,
2254                mz_repr::SemanticType::GlobalId,
2255                "mz_internal.mz_object_global_ids",
2256            ),
2257            r#"{"kind":"foreign_key","source_column":"object_id","target_column":"id","cardinality":"many_to_one","source_id_type":"GlobalId","requires_mapping":"mz_internal.mz_object_global_ids"}"#,
2258        );
2259        // union_disc — discriminator fields present, note absent
2260        check(
2261            LinkProperties::union_disc("type", "table"),
2262            r#"{"kind":"union","discriminator_column":"type","discriminator_value":"table"}"#,
2263        );
2264        // Union — note only, discriminator absent
2265        check(
2266            LinkProperties::Union {
2267                discriminator_column: None,
2268                discriminator_value: None,
2269                note: Some("example note"),
2270            },
2271            r#"{"kind":"union","note":"example note"}"#,
2272        );
2273        // measures — basic
2274        check(
2275            LinkProperties::measures("id", "id", "cpu_time_ns"),
2276            r#"{"kind":"measures","source_column":"id","target_column":"id","metric":"cpu_time_ns"}"#,
2277        );
2278        // measures_composite — extra_key_columns present
2279        check(
2280            LinkProperties::measures_composite(
2281                "export_id",
2282                "export_id",
2283                "time_ns",
2284                &[("worker_id", "worker_id")],
2285            ),
2286            r#"{"kind":"measures","source_column":"export_id","target_column":"export_id","metric":"time_ns","extra_key_columns":[["worker_id","worker_id"]]}"#,
2287        );
2288        // measures_mapped — source_id_type + requires_mapping present
2289        check(
2290            LinkProperties::measures_mapped(
2291                "object_id",
2292                "id",
2293                "wallclock_lag",
2294                mz_repr::SemanticType::GlobalId,
2295                "mz_internal.mz_object_global_ids",
2296            ),
2297            r#"{"kind":"measures","source_column":"object_id","target_column":"id","metric":"wallclock_lag","source_id_type":"GlobalId","requires_mapping":"mz_internal.mz_object_global_ids"}"#,
2298        );
2299        // DependsOn — with mapping
2300        check(
2301            LinkProperties::DependsOn {
2302                source_column: "object_id",
2303                target_column: "id",
2304                source_id_type: Some(mz_repr::SemanticType::GlobalId),
2305                requires_mapping: Some("mz_internal.mz_object_global_ids"),
2306            },
2307            r#"{"kind":"depends_on","source_column":"object_id","target_column":"id","source_id_type":"GlobalId","requires_mapping":"mz_internal.mz_object_global_ids"}"#,
2308        );
2309        // DependsOn — direct (CatalogItemId, no mapping)
2310        check(
2311            LinkProperties::DependsOn {
2312                source_column: "object_id",
2313                target_column: "id",
2314                source_id_type: Some(mz_repr::SemanticType::CatalogItemId),
2315                requires_mapping: None,
2316            },
2317            r#"{"kind":"depends_on","source_column":"object_id","target_column":"id","source_id_type":"CatalogItemId"}"#,
2318        );
2319        // MapsTo — via + from_type + to_type
2320        check(
2321            LinkProperties::MapsTo {
2322                source_column: "id",
2323                target_column: "global_id",
2324                via: Some("mz_internal.mz_object_global_ids"),
2325                from_type: Some(mz_repr::SemanticType::CatalogItemId),
2326                to_type: Some(mz_repr::SemanticType::GlobalId),
2327                note: None,
2328            },
2329            r#"{"kind":"maps_to","source_column":"id","target_column":"global_id","via":"mz_internal.mz_object_global_ids","from_type":"CatalogItemId","to_type":"GlobalId"}"#,
2330        );
2331    }
2332
2333    /// Because `mz_object_dependencies_raw`` is built from prior copies of the ontology and builtin
2334    /// reporter views to get their edges since they must be built afterwards. Thus we test
2335    /// that regenerating the view using all builtins (excluding itself) equals the SQL of the real
2336    /// view.
2337    #[mz_ore::test]
2338    #[cfg_attr(miri, ignore)] // slow: parses every builtin's SQL twice
2339    fn test_mz_object_dependencies_raw_sql_is_stable() {
2340        let without_self: Vec<Builtin<NameReference>> = BUILTINS_STATIC
2341            .iter()
2342            .filter(|b| {
2343                !matches!(b, Builtin::View(v) if v.name == mz_object_dependencies::MZ_OBJECT_DEPENDENCIES_RAW)
2344            })
2345            .cloned()
2346            .collect();
2347
2348        let regenerated = mz_object_dependencies::make_mz_object_dependencies_raw(&without_self);
2349
2350        let from_static = BUILTINS_STATIC
2351            .iter()
2352            .find_map(|b| match b {
2353                Builtin::View(v)
2354                    if v.name == mz_object_dependencies::MZ_OBJECT_DEPENDENCIES_RAW =>
2355                {
2356                    Some(*v)
2357                }
2358                _ => None,
2359            })
2360            .expect("mz_object_dependencies_raw must be present in BUILTINS_STATIC");
2361
2362        assert_eq!(
2363            regenerated.sql, from_static.sql,
2364            "regenerating mz_object_dependencies_raw from the final builtin \
2365             list must reproduce the SQL generated during static init"
2366        );
2367    }
2368}