Skip to main content

mz_adapter/
catalog.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// TODO(jkosh44) Move to mz_catalog crate.
11
12//! Persistent metadata storage for the coordinator.
13
14use std::borrow::Cow;
15use std::collections::{BTreeMap, BTreeSet};
16use std::convert;
17use std::sync::Arc;
18use std::sync::atomic::AtomicU64;
19
20use futures::future::BoxFuture;
21use futures::{Future, FutureExt};
22use itertools::Itertools;
23use mz_adapter_types::bootstrap_builtin_cluster_config::{
24    ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR, BootstrapBuiltinClusterConfig,
25    CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR, PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR,
26    SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR, SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR,
27};
28use mz_adapter_types::connection::ConnectionId;
29use mz_audit_log::{EventType, FullNameV1, ObjectType, VersionedStorageUsage};
30use mz_build_info::{BuildInfo, DUMMY_BUILD_INFO};
31use mz_catalog::builtin::{
32    BUILTIN_PREFIXES, BuiltinCluster, BuiltinLog, BuiltinSource, BuiltinTable,
33    MZ_CATALOG_SERVER_CLUSTER,
34};
35use mz_catalog::config::{
36    AwsPrincipalContext, BuiltinItemMigrationConfig, ClusterReplicaSizeMap, Config, StateConfig,
37};
38#[cfg(test)]
39use mz_catalog::durable::CatalogError;
40use mz_catalog::durable::{
41    BootstrapArgs, DurableCatalogState, STORAGE_USAGE_ID_ALLOC_KEY, TestCatalogStateBuilder,
42    test_bootstrap_args,
43};
44use mz_catalog::expr_cache::{ExpressionCacheHandle, GlobalExpressions, LocalExpressions};
45use mz_catalog::memory::error::{Error, ErrorKind};
46use mz_catalog::memory::objects::{
47    CatalogCollectionEntry, CatalogEntry, CatalogItem, Cluster, ClusterReplica, Database,
48    NetworkPolicy, Role, RoleAuth, Schema,
49};
50use mz_cloud_resources::AwsExternalIdPrefix;
51use mz_compute_types::dataflows::DataflowDescription;
52use mz_controller::clusters::ReplicaLocation;
53use mz_controller_types::{ClusterId, ReplicaId};
54use mz_expr::OptimizedMirRelationExpr;
55use mz_license_keys::ValidatedLicenseKey;
56use mz_ore::metrics::MetricsRegistry;
57use mz_ore::now::{EpochMillis, NowFn, SYSTEM_TIME};
58use mz_ore::result::ResultExt as _;
59use mz_persist_client::PersistClient;
60use mz_repr::adt::mz_acl_item::{AclMode, PrivilegeMap};
61use mz_repr::explain::ExprHumanizer;
62use mz_repr::network_policy_id::NetworkPolicyId;
63use mz_repr::optimize::OptimizerFeatures;
64use mz_repr::role_id::RoleId;
65use mz_repr::{CatalogItemId, Diff, GlobalId, RelationVersionSelector, SqlScalarType};
66use mz_secrets::InMemorySecretsController;
67use mz_sql::catalog::{
68    CatalogCluster, CatalogClusterReplica, CatalogDatabase, CatalogError as SqlCatalogError,
69    CatalogItem as SqlCatalogItem, CatalogItemType as SqlCatalogItemType, CatalogNetworkPolicy,
70    CatalogRole, CatalogSchema, DefaultPrivilegeAclItem, DefaultPrivilegeObject, EnvironmentId,
71    SessionCatalog, SystemObjectType,
72};
73use mz_sql::names::{
74    CommentObjectId, DatabaseId, FullItemName, FullSchemaName, ItemQualifiers, ObjectId,
75    PUBLIC_ROLE_NAME, PartialItemName, QualifiedItemName, QualifiedSchemaName,
76    ResolvedDatabaseSpecifier, ResolvedIds, SchemaId, SchemaSpecifier, SystemObjectId,
77};
78use mz_sql::plan::{Plan, PlanNotice, StatementDesc};
79use mz_sql::rbac;
80use mz_sql::session::metadata::SessionMetadata;
81use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, SUPPORT_USER, SYSTEM_USER};
82use mz_sql::session::vars::SystemVars;
83use mz_sql_parser::ast::QualifiedReplica;
84use mz_storage_types::connections::ConnectionContext;
85use mz_storage_types::connections::inline::{ConnectionResolver, InlinedConnection};
86use mz_transform::dataflow::DataflowMetainfo;
87use mz_transform::notice::OptimizerNotice;
88use tokio::sync::MutexGuard;
89use tokio::sync::mpsc::UnboundedSender;
90use uuid::Uuid;
91
92// DO NOT add any more imports from `crate` outside of `crate::catalog`.
93pub use crate::catalog::builtin_table_updates::BuiltinTableUpdate;
94pub use crate::catalog::open::{InitializeStateResult, OpenCatalogResult};
95pub use crate::catalog::state::CatalogState;
96pub use crate::catalog::transact::{
97    DropObjectInfo, InjectedAuditEvent, Op, ReplicaCreateDropReason, TransactionResult,
98};
99use crate::command::CatalogDump;
100use crate::coord::TargetCluster;
101#[cfg(test)]
102use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdate;
103use crate::session::{Portal, PreparedStatement, Session};
104use crate::util::ResultExt;
105use crate::{AdapterError, AdapterNotice, ExecuteResponse};
106
107mod builtin_table_updates;
108pub(crate) mod consistency;
109mod migrate;
110
111mod apply;
112pub(crate) mod cluster_state;
113mod open;
114mod state;
115mod timeline;
116mod transact;
117
118/// A `Catalog` keeps track of the SQL objects known to the planner.
119///
120/// For each object, it keeps track of both forward and reverse dependencies:
121/// i.e., which objects are depended upon by the object, and which objects
122/// depend upon the object. It enforces the SQL rules around dropping: an object
123/// cannot be dropped until all of the objects that depend upon it are dropped.
124/// It also enforces uniqueness of names.
125///
126/// SQL mandates a hierarchy of exactly three layers. A catalog contains
127/// databases, databases contain schemas, and schemas contain catalog items,
128/// like sources, sinks, view, and indexes.
129///
130/// To the outside world, databases, schemas, and items are all identified by
131/// name. Items can be referred to by their [`FullItemName`], which fully and
132/// unambiguously specifies the item, or a [`PartialItemName`], which can omit the
133/// database name and/or the schema name. Partial names can be converted into
134/// full names via a complicated resolution process documented by the
135/// [`CatalogState::resolve`] method.
136///
137/// The catalog also maintains special "ambient schemas": virtual schemas,
138/// implicitly present in all databases, that house various system views.
139/// The big examples of ambient schemas are `pg_catalog` and `mz_catalog`.
140#[derive(Debug)]
141pub struct Catalog {
142    state: CatalogState,
143    expr_cache_handle: Option<ExpressionCacheHandle>,
144    storage: Arc<tokio::sync::Mutex<Box<dyn mz_catalog::durable::DurableCatalogState>>>,
145    transient_revision: u64,
146    /// The latest `transient_revision`, shared by all clones of this catalog.
147    /// While `transient_revision` is this clone's own revision, frozen when
148    /// the snapshot was taken, this field always tracks the latest revision
149    /// across all clones. Comparing the two lets a snapshot holder detect
150    /// from off-thread whether its snapshot is still current, via
151    /// [`Catalog::transient_revision_is_current`], without a Coordinator
152    /// round-trip (see `PeekClient::catalog_snapshot`).
153    ///
154    /// The store happens in `transact`, before the transaction's effects can
155    /// be observed anywhere (responses, notices, builtin table writes), so a
156    /// session that has observed any evidence of a catalog change is
157    /// guaranteed to see the corresponding bump.
158    shared_transient_revision: Arc<AtomicU64>,
159}
160
161/// A handle for advancing the durable catalog upper off the coordinator loop.
162#[derive(Debug, Clone)]
163pub struct CatalogUpperHandle {
164    storage: Arc<tokio::sync::Mutex<Box<dyn mz_catalog::durable::DurableCatalogState>>>,
165}
166
167impl CatalogUpperHandle {
168    /// Advances the durable catalog upper to at least `new_upper`.
169    pub async fn advance_upper(
170        &self,
171        new_upper: mz_repr::Timestamp,
172    ) -> Result<(), mz_catalog::durable::CatalogError> {
173        self.storage.lock().await.advance_upper(new_upper).await
174    }
175}
176
177// Implement our own Clone because derive can't unless S is Clone, which it's
178// not (hence the Arc).
179impl Clone for Catalog {
180    fn clone(&self) -> Self {
181        Self {
182            state: self.state.clone(),
183            expr_cache_handle: self.expr_cache_handle.clone(),
184            storage: Arc::clone(&self.storage),
185            transient_revision: self.transient_revision,
186            shared_transient_revision: Arc::clone(&self.shared_transient_revision),
187        }
188    }
189}
190
191impl Catalog {
192    /// Set the optimized plan for the item identified by `id`.
193    ///
194    /// # Panics
195    /// If the item is not an `Index`, `MaterializedView`, or
196    /// `ContinualTask`.
197    #[mz_ore::instrument(level = "trace")]
198    pub fn set_optimized_plan(
199        &mut self,
200        id: GlobalId,
201        plan: DataflowDescription<OptimizedMirRelationExpr>,
202    ) {
203        self.state.set_optimized_plan(id, plan);
204    }
205
206    /// Set the physical plan for the item identified by `id`.
207    ///
208    /// # Panics
209    /// If the item is not an `Index`, `MaterializedView`, or
210    /// `ContinualTask`.
211    #[mz_ore::instrument(level = "trace")]
212    pub fn set_physical_plan(
213        &mut self,
214        id: GlobalId,
215        plan: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
216    ) {
217        self.state.set_physical_plan(id, plan);
218    }
219
220    /// Try to get the optimized plan for the item identified by `id`.
221    #[mz_ore::instrument(level = "trace")]
222    pub fn try_get_optimized_plan(
223        &self,
224        id: &GlobalId,
225    ) -> Option<&DataflowDescription<OptimizedMirRelationExpr>> {
226        let entry = self.state.try_get_entry_by_global_id(id)?;
227        entry.item().optimized_plan().map(AsRef::as_ref)
228    }
229
230    /// Try to get the physical plan for the item identified by `id`.
231    #[mz_ore::instrument(level = "trace")]
232    pub fn try_get_physical_plan(
233        &self,
234        id: &GlobalId,
235    ) -> Option<&DataflowDescription<mz_compute_types::plan::LirRelationExpr>> {
236        let entry = self.state.try_get_entry_by_global_id(id)?;
237        entry.item().physical_plan().map(AsRef::as_ref)
238    }
239
240    /// Set the `DataflowMetainfo` for the item identified by `id`.
241    ///
242    /// # Panics
243    /// If the item is not an `Index`, `MaterializedView`, or
244    /// `ContinualTask`.
245    #[mz_ore::instrument(level = "trace")]
246    pub fn set_dataflow_metainfo(
247        &mut self,
248        id: GlobalId,
249        metainfo: DataflowMetainfo<Arc<OptimizerNotice>>,
250    ) {
251        self.state.set_dataflow_metainfo(id, metainfo);
252    }
253
254    /// Try to get the `DataflowMetainfo` for the item identified by `id`.
255    #[mz_ore::instrument(level = "trace")]
256    pub fn try_get_dataflow_metainfo(
257        &self,
258        id: &GlobalId,
259    ) -> Option<&DataflowMetainfo<Arc<OptimizerNotice>>> {
260        let entry = self.state.try_get_entry_by_global_id(id)?;
261        entry.item().dataflow_metainfo()
262    }
263}
264
265#[derive(Debug)]
266pub struct ConnCatalog<'a> {
267    state: Cow<'a, CatalogState>,
268    /// Because we don't have any way of removing items from the catalog
269    /// temporarily, we allow the ConnCatalog to pretend that a set of items
270    /// don't exist during resolution.
271    ///
272    /// This feature is necessary to allow re-planning of statements, which is
273    /// either incredibly useful or required when altering item definitions.
274    ///
275    /// Note that uses of this field should be used by short-lived
276    /// catalogs.
277    unresolvable_ids: BTreeSet<CatalogItemId>,
278    conn_id: ConnectionId,
279    cluster: String,
280    database: Option<DatabaseId>,
281    search_path: Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
282    role_id: RoleId,
283    prepared_statements: Option<&'a BTreeMap<String, PreparedStatement>>,
284    portals: Option<&'a BTreeMap<String, Portal>>,
285    notices_tx: UnboundedSender<AdapterNotice>,
286    restrict_to_user_objects: bool,
287}
288
289impl ConnCatalog<'_> {
290    pub fn conn_id(&self) -> &ConnectionId {
291        &self.conn_id
292    }
293
294    pub fn state(&self) -> &CatalogState {
295        &*self.state
296    }
297
298    /// Prevent planning from resolving item with the provided ID. Instead,
299    /// return an error as if the item did not exist.
300    ///
301    /// This feature is meant exclusively to permit re-planning statements
302    /// during update operations and should not be used otherwise given its
303    /// extremely "powerful" semantics.
304    ///
305    /// # Panics
306    /// If the catalog's role ID is not [`MZ_SYSTEM_ROLE_ID`].
307    pub fn mark_id_unresolvable_for_replanning(&mut self, id: CatalogItemId) {
308        assert_eq!(
309            self.role_id, MZ_SYSTEM_ROLE_ID,
310            "only the system role can mark IDs unresolvable",
311        );
312        self.unresolvable_ids.insert(id);
313    }
314
315    /// Returns the schemas:
316    /// - mz_catalog
317    /// - pg_catalog
318    /// - temp (if requested)
319    /// - all schemas from the session's search_path var that exist
320    pub fn effective_search_path(
321        &self,
322        include_temp_schema: bool,
323    ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
324        self.state
325            .effective_search_path(&self.search_path, include_temp_schema)
326    }
327}
328
329impl ConnectionResolver for ConnCatalog<'_> {
330    fn resolve_connection(
331        &self,
332        id: CatalogItemId,
333    ) -> mz_storage_types::connections::Connection<InlinedConnection> {
334        self.state().resolve_connection(id)
335    }
336}
337
338/// AWS environment context for a debug catalog.
339///
340/// The `mz_aws_connections` and `mz_aws_privatelink_connections` builtin
341/// materialized views fold `mz_aws_account_id()`, `mz_aws_external_id_prefix()`,
342/// and `mz_aws_connection_role_arn()` into their optimized expressions. A debug
343/// catalog opened to be compared against a live environment (the testdrive
344/// consistency check) must fold those functions to the same values the live
345/// environment used, otherwise the optimized expressions diverge. This mirrors
346/// how `environment_id` is threaded into the catalog copy. Fields left `None`
347/// fold to SQL NULL, matching an environment without that context.
348#[derive(Debug, Clone, Default)]
349pub struct DebugAwsContext {
350    pub aws_account_id: Option<String>,
351    pub aws_external_id_prefix: Option<String>,
352    pub aws_connection_role_arn: Option<String>,
353}
354
355impl Catalog {
356    /// Returns the catalog's transient revision, which starts at 1 and is
357    /// incremented on every change. This is not persisted to disk, and will
358    /// restart on every load.
359    pub fn transient_revision(&self) -> u64 {
360        self.transient_revision
361    }
362
363    /// Reports whether this catalog's transient revision is still the latest,
364    /// i.e., whether no catalog transaction has committed since this snapshot
365    /// was taken. Can be called on a snapshot from off-thread, without a
366    /// Coordinator round-trip. See the field documentation on
367    /// `shared_transient_revision`.
368    pub fn transient_revision_is_current(&self) -> bool {
369        self.transient_revision
370            == self
371                .shared_transient_revision
372                .load(std::sync::atomic::Ordering::SeqCst)
373    }
374
375    /// Creates a debug catalog from the current
376    /// `METADATA_BACKEND_URL` with parameters set appropriately for debug contexts,
377    /// like in tests.
378    ///
379    /// WARNING! This function can arbitrarily fail because it does not make any
380    /// effort to adjust the catalog's contents' structure or semantics to the
381    /// currently running version, i.e. it does not apply any migrations.
382    ///
383    /// This function must not be called in production contexts. Use
384    /// [`Catalog::open`] with appropriately set configuration parameters
385    /// instead.
386    pub async fn with_debug<F, Fut, T>(f: F) -> T
387    where
388        F: FnOnce(Catalog) -> Fut,
389        Fut: Future<Output = T>,
390    {
391        let persist_client = PersistClient::new_for_tests().await;
392        let organization_id = Uuid::new_v4();
393        let bootstrap_args = test_bootstrap_args();
394        let catalog = Self::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
395            .await
396            .expect("can open debug catalog");
397        f(catalog).await
398    }
399
400    /// Like [`Catalog::with_debug`], but folds the given AWS context into the
401    /// catalog. Used to exercise builtin materialized views that reproduce AWS
402    /// environment context in SQL (see [`DebugAwsContext`]).
403    pub async fn with_debug_aws_context<F, Fut, T>(aws_context: DebugAwsContext, f: F) -> T
404    where
405        F: FnOnce(Catalog) -> Fut,
406        Fut: Future<Output = T>,
407    {
408        let persist_client = PersistClient::new_for_tests().await;
409        let organization_id = Uuid::new_v4();
410        let bootstrap_args = test_bootstrap_args();
411        let catalog = Self::open_debug_catalog_with_aws_context(
412            persist_client,
413            organization_id,
414            &bootstrap_args,
415            Some(aws_context),
416        )
417        .await
418        .expect("can open debug catalog");
419        f(catalog).await
420    }
421
422    /// Like [`Catalog::with_debug`], but the catalog created believes that bootstrap is still
423    /// in progress.
424    pub async fn with_debug_in_bootstrap<F, Fut, T>(f: F) -> T
425    where
426        F: FnOnce(Catalog) -> Fut,
427        Fut: Future<Output = T>,
428    {
429        let persist_client = PersistClient::new_for_tests().await;
430        let organization_id = Uuid::new_v4();
431        let bootstrap_args = test_bootstrap_args();
432        let mut catalog =
433            Self::open_debug_catalog(persist_client.clone(), organization_id, &bootstrap_args)
434                .await
435                .expect("can open debug catalog");
436
437        // Replace `storage` in `catalog` with one that doesn't think bootstrap is over.
438        let now = SYSTEM_TIME.clone();
439        let openable_storage = TestCatalogStateBuilder::new(persist_client)
440            .with_organization_id(organization_id)
441            .with_default_deploy_generation()
442            .build()
443            .await
444            .expect("can create durable catalog");
445        let mut storage = openable_storage
446            .open(now().into(), &bootstrap_args)
447            .await
448            .expect("can open durable catalog");
449        // Drain updates.
450        let _ = storage
451            .sync_to_current_updates()
452            .await
453            .expect("can sync to current updates");
454        catalog.storage = Arc::new(tokio::sync::Mutex::new(storage));
455
456        f(catalog).await
457    }
458
459    /// Opens a debug catalog.
460    ///
461    /// See [`Catalog::with_debug`].
462    pub async fn open_debug_catalog(
463        persist_client: PersistClient,
464        organization_id: Uuid,
465        bootstrap_args: &BootstrapArgs,
466    ) -> Result<Catalog, anyhow::Error> {
467        let now = SYSTEM_TIME.clone();
468        let environment_id = None;
469        let openable_storage = TestCatalogStateBuilder::new(persist_client.clone())
470            .with_organization_id(organization_id)
471            .with_default_deploy_generation()
472            .build()
473            .await?;
474        let storage = openable_storage.open(now().into(), bootstrap_args).await?;
475        let system_parameter_defaults = BTreeMap::default();
476        Self::open_debug_catalog_inner(
477            persist_client,
478            storage,
479            now,
480            environment_id,
481            &DUMMY_BUILD_INFO,
482            system_parameter_defaults,
483            bootstrap_args,
484            None,
485            None,
486        )
487        .await
488    }
489
490    /// Like [`Catalog::open_debug_catalog`], but folds `aws_context` into the
491    /// catalog (see [`DebugAwsContext`]).
492    ///
493    /// Calls `open_debug_catalog_inner` directly rather than delegating through
494    /// [`Catalog::open_debug_catalog`], to avoid adding an `async fn` layer to
495    /// the returned future. The catalog-open future is close to the crate
496    /// `recursion_limit`, and the extra nesting overflows it when computing the
497    /// layout of callers such as the `catalog` benchmark.
498    pub async fn open_debug_catalog_with_aws_context(
499        persist_client: PersistClient,
500        organization_id: Uuid,
501        bootstrap_args: &BootstrapArgs,
502        aws_context: Option<DebugAwsContext>,
503    ) -> Result<Catalog, anyhow::Error> {
504        let now = SYSTEM_TIME.clone();
505        let environment_id = None;
506        let openable_storage = TestCatalogStateBuilder::new(persist_client.clone())
507            .with_organization_id(organization_id)
508            .with_default_deploy_generation()
509            .build()
510            .await?;
511        let storage = openable_storage.open(now().into(), bootstrap_args).await?;
512        let system_parameter_defaults = BTreeMap::default();
513        Self::open_debug_catalog_inner(
514            persist_client,
515            storage,
516            now,
517            environment_id,
518            &DUMMY_BUILD_INFO,
519            system_parameter_defaults,
520            bootstrap_args,
521            None,
522            aws_context,
523        )
524        .await
525    }
526
527    /// Opens a read only debug persist backed catalog defined by `persist_client` and
528    /// `organization_id`.
529    ///
530    /// See [`Catalog::with_debug`].
531    pub async fn open_debug_read_only_catalog(
532        persist_client: PersistClient,
533        organization_id: Uuid,
534        bootstrap_args: &BootstrapArgs,
535    ) -> Result<Catalog, anyhow::Error> {
536        let now = SYSTEM_TIME.clone();
537        let environment_id = None;
538        let openable_storage = TestCatalogStateBuilder::new(persist_client.clone())
539            .with_organization_id(organization_id)
540            .build()
541            .await?;
542        let storage = openable_storage
543            .open_read_only(&test_bootstrap_args())
544            .await?;
545        let system_parameter_defaults = BTreeMap::default();
546        Self::open_debug_catalog_inner(
547            persist_client,
548            storage,
549            now,
550            environment_id,
551            &DUMMY_BUILD_INFO,
552            system_parameter_defaults,
553            bootstrap_args,
554            None,
555            None,
556        )
557        .await
558    }
559
560    /// Opens a read only debug persist backed catalog defined by `persist_client` and
561    /// `organization_id`.
562    ///
563    /// See [`Catalog::with_debug`].
564    pub async fn open_debug_read_only_persist_catalog_config(
565        persist_client: PersistClient,
566        now: NowFn,
567        environment_id: EnvironmentId,
568        system_parameter_defaults: BTreeMap<String, String>,
569        build_info: &'static BuildInfo,
570        bootstrap_args: &BootstrapArgs,
571        enable_expression_cache_override: Option<bool>,
572        aws_context: Option<DebugAwsContext>,
573    ) -> Result<Catalog, anyhow::Error> {
574        let openable_storage = TestCatalogStateBuilder::new(persist_client.clone())
575            .with_organization_id(environment_id.organization_id())
576            .with_version(
577                build_info
578                    .version
579                    .parse()
580                    .expect("build version is parseable"),
581            )
582            .build()
583            .await?;
584        let storage = openable_storage.open_read_only(bootstrap_args).await?;
585        Self::open_debug_catalog_inner(
586            persist_client,
587            storage,
588            now,
589            Some(environment_id),
590            build_info,
591            system_parameter_defaults,
592            bootstrap_args,
593            enable_expression_cache_override,
594            aws_context,
595        )
596        .await
597    }
598
599    async fn open_debug_catalog_inner(
600        persist_client: PersistClient,
601        storage: Box<dyn DurableCatalogState>,
602        now: NowFn,
603        environment_id: Option<EnvironmentId>,
604        build_info: &'static BuildInfo,
605        system_parameter_defaults: BTreeMap<String, String>,
606        bootstrap_args: &BootstrapArgs,
607        enable_expression_cache_override: Option<bool>,
608        aws_context: Option<DebugAwsContext>,
609    ) -> Result<Catalog, anyhow::Error> {
610        let metrics_registry = &MetricsRegistry::new();
611        let secrets_reader = Arc::new(InMemorySecretsController::new());
612        // Used as a lower boundary of the boot_ts, but it's ok to use now() for
613        // debugging/testing.
614        let previous_ts = now().into();
615        let replica_size = &bootstrap_args.default_cluster_replica_size;
616        let read_only = false;
617
618        // Fold the requested AWS context into the connection context and
619        // principal context. The builtin AWS connection views reproduce these
620        // values in SQL, so a catalog compared against a live environment must
621        // resolve them identically. When `aws_context` is `None` the default
622        // `for_tests` context is used unchanged.
623        let mut connection_context = ConnectionContext::for_tests(secrets_reader);
624        let aws_principal_context = match aws_context {
625            None => None,
626            Some(aws_context) => {
627                connection_context.aws_external_id_prefix =
628                    aws_context.aws_external_id_prefix.as_deref().map(|prefix| {
629                        AwsExternalIdPrefix::new_from_cli_argument_or_environment_variable(prefix)
630                            .expect("infallible")
631                    });
632                connection_context.aws_connection_role_arn = aws_context.aws_connection_role_arn;
633                aws_context.aws_account_id.map(|aws_account_id| {
634                    AwsPrincipalContext {
635                        aws_account_id,
636                        // Only `aws_account_id` reaches the `mz_aws_account_id()`
637                        // fold. The external id prefix is read from the connection
638                        // context above, so any placeholder works here.
639                        aws_external_id_prefix:
640                            AwsExternalIdPrefix::new_from_cli_argument_or_environment_variable(
641                                "debug",
642                            )
643                            .expect("infallible"),
644                    }
645                })
646            }
647        };
648
649        let OpenCatalogResult {
650            catalog,
651            last_seen_version: _,
652            migrated_storage_collections_0dt: _,
653            new_builtin_collections: _,
654            builtin_table_updates: _,
655            cached_global_exprs: _,
656            uncached_local_exprs: _,
657        } = Catalog::open(Config {
658            storage,
659            metrics_registry,
660            state: StateConfig {
661                unsafe_mode: true,
662                all_features: false,
663                build_info,
664                environment_id: environment_id.unwrap_or_else(EnvironmentId::for_tests),
665                read_only,
666                now,
667                boot_ts: previous_ts,
668                skip_migrations: true,
669                cluster_replica_sizes: bootstrap_args.cluster_replica_size_map.clone(),
670                builtin_system_cluster_config: BootstrapBuiltinClusterConfig {
671                    size: replica_size.clone(),
672                    replication_factor: SYSTEM_CLUSTER_DEFAULT_REPLICATION_FACTOR,
673                },
674                builtin_catalog_server_cluster_config: BootstrapBuiltinClusterConfig {
675                    size: replica_size.clone(),
676                    replication_factor: CATALOG_SERVER_CLUSTER_DEFAULT_REPLICATION_FACTOR,
677                },
678                builtin_probe_cluster_config: BootstrapBuiltinClusterConfig {
679                    size: replica_size.clone(),
680                    replication_factor: PROBE_CLUSTER_DEFAULT_REPLICATION_FACTOR,
681                },
682                builtin_support_cluster_config: BootstrapBuiltinClusterConfig {
683                    size: replica_size.clone(),
684                    replication_factor: SUPPORT_CLUSTER_DEFAULT_REPLICATION_FACTOR,
685                },
686                builtin_analytics_cluster_config: BootstrapBuiltinClusterConfig {
687                    size: replica_size.clone(),
688                    replication_factor: ANALYTICS_CLUSTER_DEFAULT_REPLICATION_FACTOR,
689                },
690                system_parameter_defaults,
691                remote_system_parameters: None,
692                availability_zones: vec![],
693                egress_addresses: vec![],
694                aws_principal_context,
695                aws_privatelink_availability_zones: None,
696                http_host_name: None,
697                connection_context,
698                builtin_item_migration_config: BuiltinItemMigrationConfig {
699                    persist_client: persist_client.clone(),
700                    read_only,
701                    force_migration: None,
702                },
703                persist_client,
704                enable_expression_cache_override,
705                helm_chart_version: None,
706                external_login_password_mz_system: None,
707                license_key: ValidatedLicenseKey::for_tests(),
708            },
709        })
710        .await?;
711        Ok(catalog)
712    }
713
714    pub fn for_session<'a>(&'a self, session: &'a Session) -> ConnCatalog<'a> {
715        self.state.for_session(session)
716    }
717
718    pub fn for_sessionless_user(&self, role_id: RoleId) -> ConnCatalog<'_> {
719        self.state.for_sessionless_user(role_id)
720    }
721
722    pub fn for_system_session(&self) -> ConnCatalog<'_> {
723        self.state.for_system_session()
724    }
725
726    async fn storage<'a>(
727        &'a self,
728    ) -> MutexGuard<'a, Box<dyn mz_catalog::durable::DurableCatalogState>> {
729        self.storage.lock().await
730    }
731
732    pub async fn current_upper(&self) -> mz_repr::Timestamp {
733        self.storage().await.current_upper().await
734    }
735
736    /// Allocates and returns both a user [`CatalogItemId`] and [`GlobalId`], delegating to
737    /// [`DurableCatalogState::allocate_user_id`].
738    pub async fn allocate_user_id(
739        &self,
740        commit_ts: mz_repr::Timestamp,
741    ) -> Result<(CatalogItemId, GlobalId), Error> {
742        Ok(self
743            .storage()
744            .await
745            .allocate_user_id(commit_ts)
746            .await
747            .maybe_terminate("allocating user ids")?)
748    }
749
750    /// Allocate `amount` many user IDs. See [`DurableCatalogState::allocate_user_ids`].
751    pub async fn allocate_user_ids(
752        &self,
753        amount: u64,
754        commit_ts: mz_repr::Timestamp,
755    ) -> Result<Vec<(CatalogItemId, GlobalId)>, Error> {
756        Ok(self
757            .storage()
758            .await
759            .allocate_user_ids(amount, commit_ts)
760            .await
761            .maybe_terminate("allocating user ids")?)
762    }
763
764    pub async fn allocate_user_id_for_test(&self) -> Result<(CatalogItemId, GlobalId), Error> {
765        let commit_ts = self.storage().await.current_upper().await;
766        self.allocate_user_id(commit_ts).await
767    }
768
769    /// Allocates a single durable id for a storage usage collection batch.
770    ///
771    /// Bumps the durable `STORAGE_USAGE_ID_ALLOC_KEY` allocator by one and
772    /// returns the previous value. The bump is committed at `commit_ts`.
773    /// One id is shared by every row produced by a collection cycle (see
774    /// `Coordinator::storage_usage_update`), so the durable cost is one
775    /// allocator round-trip per cycle, not per shard.
776    pub async fn allocate_storage_usage_id(
777        &self,
778        commit_ts: mz_repr::Timestamp,
779    ) -> Result<u64, Error> {
780        use mz_ore::collections::CollectionExt;
781
782        self.storage()
783            .await
784            .allocate_id(STORAGE_USAGE_ID_ALLOC_KEY, 1, commit_ts)
785            .await
786            .maybe_terminate("allocating storage usage id")
787            .map(|ids| ids.into_element())
788            .err_into()
789    }
790
791    /// Get the next user item ID without allocating it.
792    pub async fn get_next_user_item_id(&self) -> Result<u64, Error> {
793        self.storage()
794            .await
795            .get_next_user_item_id()
796            .await
797            .err_into()
798    }
799
800    #[cfg(test)]
801    pub async fn allocate_system_id(
802        &self,
803        commit_ts: mz_repr::Timestamp,
804    ) -> Result<(CatalogItemId, GlobalId), Error> {
805        use mz_ore::collections::CollectionExt;
806
807        let mut storage = self.storage().await;
808        let mut txn = storage.transaction().await?;
809        let id = txn
810            .allocate_system_item_ids(1)
811            .maybe_terminate("allocating system ids")?
812            .into_element();
813        // Drain transaction.
814        let _ = txn.get_and_commit_op_updates();
815        txn.commit(commit_ts).await?;
816        Ok(id)
817    }
818
819    /// Get the next system item ID without allocating it.
820    pub async fn get_next_system_item_id(&self) -> Result<u64, Error> {
821        self.storage()
822            .await
823            .get_next_system_item_id()
824            .await
825            .err_into()
826    }
827
828    /// Allocates and returns a user [`ClusterId`], delegating to
829    /// [`DurableCatalogState::allocate_user_cluster_id`].
830    pub async fn allocate_user_cluster_id(
831        &self,
832        commit_ts: mz_repr::Timestamp,
833    ) -> Result<ClusterId, Error> {
834        Ok(self
835            .storage()
836            .await
837            .allocate_user_cluster_id(commit_ts)
838            .await
839            .maybe_terminate("allocating user cluster ids")?)
840    }
841
842    /// Allocate `amount` many user replica IDs. See
843    /// [`DurableCatalogState::allocate_user_replica_ids`].
844    pub async fn allocate_user_replica_ids(
845        &self,
846        amount: u64,
847        commit_ts: mz_repr::Timestamp,
848    ) -> Result<Vec<ReplicaId>, Error> {
849        Ok(self
850            .storage()
851            .await
852            .allocate_user_replica_ids(amount, commit_ts)
853            .await
854            .maybe_terminate("allocating user replica ids")?)
855    }
856
857    /// Allocate `amount` many system replica IDs. See
858    /// [`DurableCatalogState::allocate_system_replica_ids`].
859    pub async fn allocate_system_replica_ids(
860        &self,
861        amount: u64,
862        commit_ts: mz_repr::Timestamp,
863    ) -> Result<Vec<ReplicaId>, Error> {
864        Ok(self
865            .storage()
866            .await
867            .allocate_system_replica_ids(amount, commit_ts)
868            .await
869            .maybe_terminate("allocating system replica ids")?)
870    }
871
872    /// Allocate `amount` many replica IDs for `cluster_id`, picking user or
873    /// system IDs based on the cluster's ID type.
874    pub async fn allocate_replica_ids(
875        &self,
876        cluster_id: ClusterId,
877        amount: u64,
878        commit_ts: mz_repr::Timestamp,
879    ) -> Result<Vec<ReplicaId>, Error> {
880        if cluster_id.is_system() {
881            self.allocate_system_replica_ids(amount, commit_ts).await
882        } else {
883            self.allocate_user_replica_ids(amount, commit_ts).await
884        }
885    }
886
887    /// Get the next system replica id without allocating it.
888    pub async fn get_next_system_replica_id(&self) -> Result<u64, Error> {
889        self.storage()
890            .await
891            .get_next_system_replica_id()
892            .await
893            .err_into()
894    }
895
896    /// Get the next user replica id without allocating it.
897    pub async fn get_next_user_replica_id(&self) -> Result<u64, Error> {
898        self.storage()
899            .await
900            .get_next_user_replica_id()
901            .await
902            .err_into()
903    }
904
905    pub fn resolve_database(&self, database_name: &str) -> Result<&Database, SqlCatalogError> {
906        self.state.resolve_database(database_name)
907    }
908
909    pub fn resolve_schema(
910        &self,
911        current_database: Option<&DatabaseId>,
912        database_name: Option<&str>,
913        schema_name: &str,
914        conn_id: &ConnectionId,
915    ) -> Result<&Schema, SqlCatalogError> {
916        self.state
917            .resolve_schema(current_database, database_name, schema_name, conn_id)
918    }
919
920    pub fn resolve_schema_in_database(
921        &self,
922        database_spec: &ResolvedDatabaseSpecifier,
923        schema_name: &str,
924        conn_id: &ConnectionId,
925    ) -> Result<&Schema, SqlCatalogError> {
926        self.state
927            .resolve_schema_in_database(database_spec, schema_name, conn_id)
928    }
929
930    pub fn resolve_replica_in_cluster(
931        &self,
932        cluster_id: &ClusterId,
933        replica_name: &str,
934    ) -> Result<&ClusterReplica, SqlCatalogError> {
935        self.state
936            .resolve_replica_in_cluster(cluster_id, replica_name)
937    }
938
939    pub fn resolve_system_schema(&self, name: &'static str) -> SchemaId {
940        self.state.resolve_system_schema(name)
941    }
942
943    pub fn resolve_search_path(
944        &self,
945        session: &Session,
946    ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
947        self.state.resolve_search_path(session)
948    }
949
950    /// Resolves `name` to a non-function [`CatalogEntry`].
951    pub fn resolve_entry(
952        &self,
953        current_database: Option<&DatabaseId>,
954        search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
955        name: &PartialItemName,
956        conn_id: &ConnectionId,
957    ) -> Result<&CatalogEntry, SqlCatalogError> {
958        self.state
959            .resolve_entry(current_database, search_path, name, conn_id)
960    }
961
962    /// Resolves a `BuiltinTable`.
963    pub fn resolve_builtin_table(&self, builtin: &'static BuiltinTable) -> CatalogItemId {
964        self.state.resolve_builtin_table(builtin)
965    }
966
967    /// Resolves a `BuiltinLog`.
968    pub fn resolve_builtin_log(&self, builtin: &'static BuiltinLog) -> CatalogItemId {
969        self.state.resolve_builtin_log(builtin).0
970    }
971
972    /// Resolves a `BuiltinSource`.
973    pub fn resolve_builtin_storage_collection(
974        &self,
975        builtin: &'static BuiltinSource,
976    ) -> CatalogItemId {
977        self.state.resolve_builtin_source(builtin)
978    }
979
980    /// Resolves `name` to a function [`CatalogEntry`].
981    pub fn resolve_function(
982        &self,
983        current_database: Option<&DatabaseId>,
984        search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
985        name: &PartialItemName,
986        conn_id: &ConnectionId,
987    ) -> Result<&CatalogEntry, SqlCatalogError> {
988        self.state
989            .resolve_function(current_database, search_path, name, conn_id)
990    }
991
992    /// Resolves `name` to a type [`CatalogEntry`].
993    pub fn resolve_type(
994        &self,
995        current_database: Option<&DatabaseId>,
996        search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
997        name: &PartialItemName,
998        conn_id: &ConnectionId,
999    ) -> Result<&CatalogEntry, SqlCatalogError> {
1000        self.state
1001            .resolve_type(current_database, search_path, name, conn_id)
1002    }
1003
1004    pub fn resolve_cluster(&self, name: &str) -> Result<&Cluster, SqlCatalogError> {
1005        self.state.resolve_cluster(name)
1006    }
1007
1008    /// Resolves a [`Cluster`] for a [`BuiltinCluster`].
1009    ///
1010    /// # Panics
1011    /// * If the [`BuiltinCluster`] doesn't exist.
1012    ///
1013    pub fn resolve_builtin_cluster(&self, cluster: &BuiltinCluster) -> &Cluster {
1014        self.state.resolve_builtin_cluster(cluster)
1015    }
1016
1017    pub fn get_mz_catalog_server_cluster_id(&self) -> &ClusterId {
1018        &self.resolve_builtin_cluster(&MZ_CATALOG_SERVER_CLUSTER).id
1019    }
1020
1021    /// Resolves a [`Cluster`] for a TargetCluster.
1022    pub fn resolve_target_cluster(
1023        &self,
1024        target_cluster: TargetCluster,
1025        session: &Session,
1026    ) -> Result<&Cluster, AdapterError> {
1027        match target_cluster {
1028            TargetCluster::CatalogServer => {
1029                Ok(self.resolve_builtin_cluster(&MZ_CATALOG_SERVER_CLUSTER))
1030            }
1031            TargetCluster::Active => self.active_cluster(session),
1032            TargetCluster::Transaction(cluster_id) => self
1033                .try_get_cluster(cluster_id)
1034                .ok_or(AdapterError::ConcurrentClusterDrop),
1035        }
1036    }
1037
1038    pub fn active_cluster(&self, session: &Session) -> Result<&Cluster, AdapterError> {
1039        // TODO(benesch): this check here is not sufficiently protective. It'd
1040        // be very easy for a code path to accidentally avoid this check by
1041        // calling `resolve_cluster(session.vars().cluster())`.
1042        if session.user().name != SYSTEM_USER.name
1043            && session.user().name != SUPPORT_USER.name
1044            && session.vars().cluster() == SYSTEM_USER.name
1045        {
1046            coord_bail!(
1047                "system cluster '{}' cannot execute user queries",
1048                SYSTEM_USER.name
1049            );
1050        }
1051        let cluster = self.resolve_cluster(session.vars().cluster())?;
1052        Ok(cluster)
1053    }
1054
1055    pub fn state(&self) -> &CatalogState {
1056        &self.state
1057    }
1058
1059    pub fn resolve_full_name(
1060        &self,
1061        name: &QualifiedItemName,
1062        conn_id: Option<&ConnectionId>,
1063    ) -> FullItemName {
1064        self.state.resolve_full_name(name, conn_id)
1065    }
1066
1067    pub fn try_get_entry(&self, id: &CatalogItemId) -> Option<&CatalogEntry> {
1068        self.state.try_get_entry(id)
1069    }
1070
1071    pub fn try_get_entry_by_global_id(&self, id: &GlobalId) -> Option<&CatalogEntry> {
1072        self.state.try_get_entry_by_global_id(id)
1073    }
1074
1075    pub fn get_entry(&self, id: &CatalogItemId) -> &CatalogEntry {
1076        self.state.get_entry(id)
1077    }
1078
1079    pub fn get_entry_by_global_id(&self, id: &GlobalId) -> CatalogCollectionEntry {
1080        self.state.get_entry_by_global_id(id)
1081    }
1082
1083    pub fn get_global_ids<'a>(
1084        &'a self,
1085        id: &CatalogItemId,
1086    ) -> impl Iterator<Item = GlobalId> + use<'a> {
1087        self.get_entry(id).global_ids()
1088    }
1089
1090    pub fn resolve_item_id(&self, id: &GlobalId) -> CatalogItemId {
1091        self.get_entry_by_global_id(id).id()
1092    }
1093
1094    pub fn try_resolve_item_id(&self, id: &GlobalId) -> Option<CatalogItemId> {
1095        let item = self.try_get_entry_by_global_id(id)?;
1096        Some(item.id())
1097    }
1098
1099    pub fn get_schema(
1100        &self,
1101        database_spec: &ResolvedDatabaseSpecifier,
1102        schema_spec: &SchemaSpecifier,
1103        conn_id: &ConnectionId,
1104    ) -> &Schema {
1105        self.state.get_schema(database_spec, schema_spec, conn_id)
1106    }
1107
1108    pub fn try_get_schema(
1109        &self,
1110        database_spec: &ResolvedDatabaseSpecifier,
1111        schema_spec: &SchemaSpecifier,
1112        conn_id: &ConnectionId,
1113    ) -> Option<&Schema> {
1114        self.state
1115            .try_get_schema(database_spec, schema_spec, conn_id)
1116    }
1117
1118    pub fn get_mz_catalog_schema_id(&self) -> SchemaId {
1119        self.state.get_mz_catalog_schema_id()
1120    }
1121
1122    pub fn get_pg_catalog_schema_id(&self) -> SchemaId {
1123        self.state.get_pg_catalog_schema_id()
1124    }
1125
1126    pub fn get_information_schema_id(&self) -> SchemaId {
1127        self.state.get_information_schema_id()
1128    }
1129
1130    pub fn get_mz_internal_schema_id(&self) -> SchemaId {
1131        self.state.get_mz_internal_schema_id()
1132    }
1133
1134    pub fn get_mz_introspection_schema_id(&self) -> SchemaId {
1135        self.state.get_mz_introspection_schema_id()
1136    }
1137
1138    pub fn get_mz_unsafe_schema_id(&self) -> SchemaId {
1139        self.state.get_mz_unsafe_schema_id()
1140    }
1141
1142    pub fn system_schema_ids(&self) -> impl Iterator<Item = SchemaId> + '_ {
1143        self.state.system_schema_ids()
1144    }
1145
1146    pub fn get_database(&self, id: &DatabaseId) -> &Database {
1147        self.state.get_database(id)
1148    }
1149
1150    pub fn try_get_role(&self, id: &RoleId) -> Option<&Role> {
1151        self.state.try_get_role(id)
1152    }
1153
1154    pub fn get_role(&self, id: &RoleId) -> &Role {
1155        self.state.get_role(id)
1156    }
1157
1158    pub fn try_get_role_by_name(&self, role_name: &str) -> Option<&Role> {
1159        self.state.try_get_role_by_name(role_name)
1160    }
1161
1162    pub fn try_get_role_auth_by_id(&self, id: &RoleId) -> Option<&RoleAuth> {
1163        self.state.try_get_role_auth_by_id(id)
1164    }
1165
1166    /// Registers the connection's temporary namespace: the `uuid` <->
1167    /// `conn_id` mapping used to stamp and apply durable temporary items
1168    /// owned by the session. The `mz_temp` schema itself is created when
1169    /// the first temporary item is applied.
1170    ///
1171    /// The coordinator calls this at a session's first temporary-item
1172    /// creation, strictly before the transaction that persists the item, and
1173    /// guards on [`CatalogState::has_temporary_namespace`], so registering
1174    /// an already-registered namespace is a bug.
1175    pub fn register_temporary_namespace(&mut self, conn_id: &ConnectionId, uuid: Uuid) {
1176        self.state
1177            .temporary_namespaces
1178            .register(conn_id.clone(), uuid);
1179    }
1180
1181    fn item_exists_in_temp_schemas(&self, conn_id: &ConnectionId, item_name: &str) -> bool {
1182        // A temporary namespace is registered at the connection's first
1183        // temporary-item creation, so it's valid for one to not exist yet.
1184        self.state
1185            .temporary_namespaces
1186            .schema(conn_id)
1187            .map(|schema| schema.items.contains_key(item_name))
1188            .unwrap_or(false)
1189    }
1190
1191    /// Removes the connection's temporary namespace, if it has one.
1192    pub fn drop_temporary_namespace(&mut self, conn_id: &ConnectionId) {
1193        self.state.temporary_namespaces.unregister(conn_id)
1194    }
1195
1196    pub(crate) fn object_dependents(
1197        &self,
1198        object_ids: &Vec<ObjectId>,
1199        conn_id: &ConnectionId,
1200    ) -> Vec<ObjectId> {
1201        let mut seen = BTreeSet::new();
1202        self.state.object_dependents(object_ids, conn_id, &mut seen)
1203    }
1204
1205    fn full_name_detail(name: &FullItemName) -> FullNameV1 {
1206        FullNameV1 {
1207            database: name.database.to_string(),
1208            schema: name.schema.clone(),
1209            item: name.item.clone(),
1210        }
1211    }
1212
1213    pub fn find_available_cluster_name(&self, name: &str) -> String {
1214        let mut i = 0;
1215        let mut candidate = name.to_string();
1216        while self.state.clusters_by_name.contains_key(&candidate) {
1217            i += 1;
1218            candidate = format!("{}{}", name, i);
1219        }
1220        candidate
1221    }
1222
1223    pub fn get_role_allowed_cluster_sizes(&self, role_id: &Option<RoleId>) -> Vec<String> {
1224        if role_id == &Some(MZ_SYSTEM_ROLE_ID) {
1225            self.cluster_replica_sizes()
1226                .enabled_allocations()
1227                .map(|a| a.0.to_owned())
1228                .collect::<Vec<_>>()
1229        } else {
1230            self.system_config().allowed_cluster_replica_sizes()
1231        }
1232    }
1233
1234    pub fn concretize_replica_location(
1235        &self,
1236        location: mz_catalog::durable::ReplicaLocation,
1237        allowed_sizes: &Vec<String>,
1238        allowed_availability_zones: Option<&[String]>,
1239        allow_disabled: bool,
1240    ) -> Result<ReplicaLocation, Error> {
1241        self.state.concretize_replica_location(
1242            location,
1243            allowed_sizes,
1244            allowed_availability_zones,
1245            allow_disabled,
1246        )
1247    }
1248
1249    pub(crate) fn ensure_valid_replica_size(
1250        &self,
1251        allowed_sizes: &[String],
1252        size: &String,
1253        allow_disabled: bool,
1254    ) -> Result<(), Error> {
1255        self.state
1256            .ensure_valid_replica_size(allowed_sizes, size, allow_disabled)
1257    }
1258
1259    pub fn cluster_replica_sizes(&self) -> &ClusterReplicaSizeMap {
1260        &self.state.cluster_replica_sizes
1261    }
1262
1263    /// Returns the privileges of an object by its ID.
1264    pub fn get_privileges(
1265        &self,
1266        id: &SystemObjectId,
1267        conn_id: &ConnectionId,
1268    ) -> Option<&PrivilegeMap> {
1269        match id {
1270            SystemObjectId::Object(id) => match id {
1271                ObjectId::Cluster(id) => Some(self.get_cluster(*id).privileges()),
1272                ObjectId::Database(id) => Some(self.get_database(id).privileges()),
1273                ObjectId::Schema((database_spec, schema_spec)) => Some(
1274                    self.get_schema(database_spec, schema_spec, conn_id)
1275                        .privileges(),
1276                ),
1277                ObjectId::Item(id) => Some(self.get_entry(id).privileges()),
1278                ObjectId::ClusterReplica(_) | ObjectId::Role(_) => None,
1279                ObjectId::NetworkPolicy(id) => Some(self.get_network_policy(*id).privileges()),
1280            },
1281            SystemObjectId::System => Some(&self.state.system_privileges),
1282        }
1283    }
1284
1285    /// Advances the catalog upper to at least `new_upper`.
1286    ///
1287    /// Empty progress can overtake `new_upper`. A durable upper mismatch with content returns
1288    /// `CatalogOutOfSync`. See [`mz_catalog::durable::DurableCatalogState::advance_upper`] for
1289    /// fencing semantics.
1290    #[mz_ore::instrument(level = "debug")]
1291    pub async fn advance_upper(&self, new_upper: mz_repr::Timestamp) -> Result<(), AdapterError> {
1292        Ok(self.storage().await.advance_upper(new_upper).await?)
1293    }
1294
1295    /// Returns a durable-upper handle that shares the catalog storage mutex.
1296    pub fn upper_handle(&self) -> CatalogUpperHandle {
1297        CatalogUpperHandle {
1298            storage: Arc::clone(&self.storage),
1299        }
1300    }
1301
1302    /// Return the ids of all log sources the given object depends on.
1303    pub fn introspection_dependencies(&self, id: CatalogItemId) -> Vec<CatalogItemId> {
1304        self.state.introspection_dependencies(id)
1305    }
1306
1307    /// Serializes the catalog's in-memory state.
1308    ///
1309    /// There are no guarantees about the format of the serialized state, except
1310    /// that the serialized state for two identical catalogs will compare
1311    /// identically.
1312    pub fn dump(&self) -> Result<CatalogDump, Error> {
1313        Ok(CatalogDump::new(self.state.dump(None)?))
1314    }
1315
1316    /// Checks the [`Catalog`]s internal consistency.
1317    ///
1318    /// Returns a JSON object describing the inconsistencies, if there are any.
1319    pub fn check_consistency(&self) -> Result<(), serde_json::Value> {
1320        self.state.check_consistency().map_err(|inconsistencies| {
1321            serde_json::to_value(inconsistencies).unwrap_or_else(|_| {
1322                serde_json::Value::String("failed to serialize inconsistencies".to_string())
1323            })
1324        })
1325    }
1326
1327    pub fn config(&self) -> &mz_sql::catalog::CatalogConfig {
1328        self.state.config()
1329    }
1330
1331    pub fn entries(&self) -> impl Iterator<Item = &CatalogEntry> {
1332        self.state.entry_by_id.values()
1333    }
1334
1335    pub fn user_connections(&self) -> impl Iterator<Item = &CatalogEntry> {
1336        self.entries()
1337            .filter(|entry| entry.is_connection() && entry.id().is_user())
1338    }
1339
1340    pub fn user_tables(&self) -> impl Iterator<Item = &CatalogEntry> {
1341        self.entries()
1342            .filter(|entry| entry.is_table() && entry.id().is_user())
1343    }
1344
1345    pub fn user_sources(&self) -> impl Iterator<Item = &CatalogEntry> {
1346        self.entries()
1347            .filter(|entry| entry.is_source() && entry.id().is_user())
1348    }
1349
1350    pub fn user_sinks(&self) -> impl Iterator<Item = &CatalogEntry> {
1351        self.entries()
1352            .filter(|entry| entry.is_sink() && entry.id().is_user())
1353    }
1354
1355    pub fn user_materialized_views(&self) -> impl Iterator<Item = &CatalogEntry> {
1356        self.entries()
1357            .filter(|entry| entry.is_materialized_view() && entry.id().is_user())
1358    }
1359
1360    pub fn user_secrets(&self) -> impl Iterator<Item = &CatalogEntry> {
1361        self.entries()
1362            .filter(|entry| entry.is_secret() && entry.id().is_user())
1363    }
1364
1365    pub fn get_network_policy(&self, network_policy_id: NetworkPolicyId) -> &NetworkPolicy {
1366        self.state.get_network_policy(&network_policy_id)
1367    }
1368
1369    pub fn get_network_policy_by_name(&self, name: &str) -> Option<&NetworkPolicy> {
1370        self.state.try_get_network_policy_by_name(name)
1371    }
1372
1373    pub fn clusters(&self) -> impl Iterator<Item = &Cluster> {
1374        self.state.clusters_by_id.values()
1375    }
1376
1377    pub fn get_cluster(&self, cluster_id: ClusterId) -> &Cluster {
1378        self.state.get_cluster(cluster_id)
1379    }
1380
1381    pub fn try_get_cluster(&self, cluster_id: ClusterId) -> Option<&Cluster> {
1382        self.state.try_get_cluster(cluster_id)
1383    }
1384
1385    pub fn user_clusters(&self) -> impl Iterator<Item = &Cluster> {
1386        self.clusters().filter(|cluster| cluster.id.is_user())
1387    }
1388
1389    pub fn get_cluster_replica(
1390        &self,
1391        cluster_id: ClusterId,
1392        replica_id: ReplicaId,
1393    ) -> &ClusterReplica {
1394        self.state.get_cluster_replica(cluster_id, replica_id)
1395    }
1396
1397    pub fn try_get_cluster_replica(
1398        &self,
1399        cluster_id: ClusterId,
1400        replica_id: ReplicaId,
1401    ) -> Option<&ClusterReplica> {
1402        self.state.try_get_cluster_replica(cluster_id, replica_id)
1403    }
1404
1405    pub fn user_cluster_replicas(&self) -> impl Iterator<Item = &ClusterReplica> {
1406        self.user_clusters()
1407            .flat_map(|cluster| cluster.user_replicas())
1408    }
1409
1410    pub fn databases(&self) -> impl Iterator<Item = &Database> {
1411        self.state.database_by_id.values()
1412    }
1413
1414    pub fn user_roles(&self) -> impl Iterator<Item = &Role> {
1415        self.state
1416            .roles_by_id
1417            .values()
1418            .filter(|role| role.is_user())
1419    }
1420
1421    pub fn user_network_policies(&self) -> impl Iterator<Item = &NetworkPolicy> {
1422        self.state
1423            .network_policies_by_id
1424            .iter()
1425            .filter(|(id, _)| id.is_user())
1426            .map(|(_, policy)| policy)
1427    }
1428
1429    pub fn system_privileges(&self) -> &PrivilegeMap {
1430        &self.state.system_privileges
1431    }
1432
1433    pub fn default_privileges(
1434        &self,
1435    ) -> impl Iterator<
1436        Item = (
1437            &DefaultPrivilegeObject,
1438            impl Iterator<Item = &DefaultPrivilegeAclItem>,
1439        ),
1440    > {
1441        self.state.default_privileges.iter()
1442    }
1443
1444    pub fn pack_item_update(&self, id: CatalogItemId, diff: Diff) -> Vec<BuiltinTableUpdate> {
1445        self.state
1446            .resolve_builtin_table_updates(self.state.pack_item_update(id, diff))
1447    }
1448
1449    pub fn pack_storage_usage_update(
1450        &self,
1451        event: VersionedStorageUsage,
1452        diff: Diff,
1453    ) -> BuiltinTableUpdate {
1454        self.state
1455            .resolve_builtin_table_update(self.state.pack_storage_usage_update(event, diff))
1456    }
1457
1458    pub fn system_config(&self) -> &SystemVars {
1459        self.state.system_config()
1460    }
1461
1462    pub fn system_config_mut(&mut self) -> &mut SystemVars {
1463        self.state.system_config_mut()
1464    }
1465
1466    pub fn ensure_not_reserved_role(&self, role_id: &RoleId) -> Result<(), Error> {
1467        self.state.ensure_not_reserved_role(role_id)
1468    }
1469
1470    pub fn ensure_grantable_role(&self, role_id: &RoleId) -> Result<(), Error> {
1471        self.state.ensure_grantable_role(role_id)
1472    }
1473
1474    pub fn ensure_not_system_role(&self, role_id: &RoleId) -> Result<(), Error> {
1475        self.state.ensure_not_system_role(role_id)
1476    }
1477
1478    pub fn ensure_not_predefined_role(&self, role_id: &RoleId) -> Result<(), Error> {
1479        self.state.ensure_not_predefined_role(role_id)
1480    }
1481
1482    pub fn ensure_not_reserved_network_policy(
1483        &self,
1484        network_policy_id: &NetworkPolicyId,
1485    ) -> Result<(), Error> {
1486        self.state
1487            .ensure_not_reserved_network_policy(network_policy_id)
1488    }
1489
1490    pub fn ensure_not_reserved_object(
1491        &self,
1492        object_id: &ObjectId,
1493        conn_id: &ConnectionId,
1494    ) -> Result<(), Error> {
1495        match object_id {
1496            ObjectId::Cluster(cluster_id) => {
1497                if cluster_id.is_system() {
1498                    let cluster = self.get_cluster(*cluster_id);
1499                    Err(Error::new(ErrorKind::ReadOnlyCluster(
1500                        cluster.name().to_string(),
1501                    )))
1502                } else {
1503                    Ok(())
1504                }
1505            }
1506            ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1507                if replica_id.is_system() {
1508                    let replica = self.get_cluster_replica(*cluster_id, *replica_id);
1509                    Err(Error::new(ErrorKind::ReadOnlyClusterReplica(
1510                        replica.name().to_string(),
1511                    )))
1512                } else {
1513                    Ok(())
1514                }
1515            }
1516            ObjectId::Database(database_id) => {
1517                if database_id.is_system() {
1518                    let database = self.get_database(database_id);
1519                    Err(Error::new(ErrorKind::ReadOnlyDatabase(
1520                        database.name().to_string(),
1521                    )))
1522                } else {
1523                    Ok(())
1524                }
1525            }
1526            ObjectId::Schema((database_spec, schema_spec)) => {
1527                if schema_spec.is_system() {
1528                    let schema = self.get_schema(database_spec, schema_spec, conn_id);
1529                    Err(Error::new(ErrorKind::ReadOnlySystemSchema(
1530                        schema.name().schema.clone(),
1531                    )))
1532                } else {
1533                    Ok(())
1534                }
1535            }
1536            ObjectId::Role(role_id) => self.ensure_not_reserved_role(role_id),
1537            ObjectId::Item(item_id) => {
1538                if item_id.is_system() {
1539                    let item = self.get_entry(item_id);
1540                    let name = self.resolve_full_name(item.name(), Some(conn_id));
1541                    Err(Error::new(ErrorKind::ReadOnlyItem(name.to_string())))
1542                } else {
1543                    Ok(())
1544                }
1545            }
1546            ObjectId::NetworkPolicy(network_policy_id) => {
1547                self.ensure_not_reserved_network_policy(network_policy_id)
1548            }
1549        }
1550    }
1551
1552    /// See [`CatalogState::deserialize_plan_with_enable_for_item_parsing`].
1553    pub(crate) fn deserialize_plan_with_enable_for_item_parsing(
1554        &mut self,
1555        create_sql: &str,
1556        force_if_exists_skip: bool,
1557    ) -> Result<(Plan, ResolvedIds), AdapterError> {
1558        self.state
1559            .deserialize_plan_with_enable_for_item_parsing(create_sql, force_if_exists_skip)
1560    }
1561
1562    /// Cache global and, optionally, local expressions for the given
1563    /// `GlobalId`.
1564    ///
1565    /// Takes the plans and metainfo directly as parameters (rather than
1566    /// fishing them out of catalog state), so this can be called **before**
1567    /// the catalog transaction that creates the item. Returns the future
1568    /// returned by [`Catalog::update_expression_cache`]; callers should
1569    /// `.await` it before the catalog transaction commits, so the durable
1570    /// expression cache is observed to contain the entries by the time any
1571    /// other process (or a subsequent bootstrap on this process) reads them.
1572    pub(crate) fn cache_expressions(
1573        &self,
1574        id: GlobalId,
1575        local_mir: Option<OptimizedMirRelationExpr>,
1576        mut global_mir: DataflowDescription<OptimizedMirRelationExpr>,
1577        mut physical_plan: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
1578        dataflow_metainfos: DataflowMetainfo<Arc<OptimizerNotice>>,
1579        optimizer_features: OptimizerFeatures,
1580    ) -> BoxFuture<'static, ()> {
1581        // Make sure we're not caching the result of timestamp selection, as
1582        // it will almost certainly be wrong if we re-install the dataflow at
1583        // a later time.
1584        global_mir.as_of = None;
1585        global_mir.until = Default::default();
1586        physical_plan.as_of = None;
1587        physical_plan.until = Default::default();
1588
1589        let mut local_exprs = Vec::new();
1590        if let Some(local_mir) = local_mir {
1591            local_exprs.push((
1592                id,
1593                LocalExpressions {
1594                    local_mir,
1595                    optimizer_features: optimizer_features.clone(),
1596                },
1597            ));
1598        }
1599        let global_exprs = vec![(
1600            id,
1601            GlobalExpressions {
1602                global_mir,
1603                physical_plan,
1604                dataflow_metainfos,
1605                optimizer_features,
1606            },
1607        )];
1608        self.update_expression_cache(local_exprs, global_exprs, Default::default())
1609    }
1610
1611    pub(crate) fn update_expression_cache<'a, 'b>(
1612        &'a self,
1613        new_local_expressions: Vec<(GlobalId, LocalExpressions)>,
1614        new_global_expressions: Vec<(GlobalId, GlobalExpressions)>,
1615        invalidate_ids: BTreeSet<GlobalId>,
1616    ) -> BoxFuture<'b, ()> {
1617        if let Some(expr_cache) = &self.expr_cache_handle {
1618            expr_cache
1619                .update(
1620                    new_local_expressions,
1621                    new_global_expressions,
1622                    invalidate_ids,
1623                )
1624                .boxed()
1625        } else {
1626            async {}.boxed()
1627        }
1628    }
1629
1630    /// Listen for and apply all unconsumed updates to the durable catalog state.
1631    // TODO(jkosh44) When this method is actually used outside of a test we can remove the
1632    // `#[cfg(test)]` annotation.
1633    #[cfg(test)]
1634    async fn sync_to_current_updates(
1635        &mut self,
1636    ) -> Result<
1637        (
1638            Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
1639            Vec<ParsedStateUpdate>,
1640        ),
1641        CatalogError,
1642    > {
1643        let updates = self.storage().await.sync_to_current_updates().await?;
1644        let (builtin_table_updates, catalog_updates) = self
1645            .state
1646            .apply_updates(updates, &mut state::LocalExpressionCache::Closed)
1647            .await;
1648        Ok((builtin_table_updates, catalog_updates))
1649    }
1650}
1651
1652pub fn is_reserved_name(name: &str) -> bool {
1653    BUILTIN_PREFIXES
1654        .iter()
1655        .any(|prefix| name.starts_with(prefix))
1656}
1657
1658/// Role names that PostgreSQL reserves for role specifications in statements
1659/// like `GRANT ... TO CURRENT_USER` and `SET ROLE NONE`. A role with such a
1660/// name would be ambiguous there, so creating one is not allowed.
1661///
1662/// PostgreSQL rejects most of these at parse time, only when they appear as
1663/// unquoted keywords. Our parser does not track whether an identifier was
1664/// quoted, so we instead reject the names themselves. Only the lowercase
1665/// spellings are reserved, which is what the unquoted forms normalize to, so
1666/// quoted names like `"CURRENT_USER"` remain valid, as in PostgreSQL.
1667const RESERVED_ROLE_SPECIFICATION_NAMES: [&str; 5] = [
1668    "current_user",
1669    "current_role",
1670    "session_user",
1671    "user",
1672    "none",
1673];
1674
1675pub fn is_reserved_role_name(name: &str) -> bool {
1676    is_reserved_name(name)
1677        || is_public_role(name)
1678        || RESERVED_ROLE_SPECIFICATION_NAMES.contains(&name)
1679}
1680
1681pub fn is_public_role(name: &str) -> bool {
1682    name == &*PUBLIC_ROLE_NAME
1683}
1684
1685pub(crate) fn catalog_type_to_audit_object_type(sql_type: SqlCatalogItemType) -> ObjectType {
1686    object_type_to_audit_object_type(sql_type.into())
1687}
1688
1689pub(crate) fn comment_id_to_audit_object_type(id: CommentObjectId) -> ObjectType {
1690    match id {
1691        CommentObjectId::Table(_) => ObjectType::Table,
1692        CommentObjectId::View(_) => ObjectType::View,
1693        CommentObjectId::MaterializedView(_) => ObjectType::MaterializedView,
1694        CommentObjectId::Source(_) => ObjectType::Source,
1695        CommentObjectId::Sink(_) => ObjectType::Sink,
1696        // Unreachable by construction: `COMMENT ON METRIC SINK` is rejected at parse, so no
1697        // metric-sink comment id is ever built. The arm exists only for exhaustiveness.
1698        CommentObjectId::MetricSink(_) => ObjectType::MetricSink,
1699        CommentObjectId::Index(_) => ObjectType::Index,
1700        CommentObjectId::Func(_) => ObjectType::Func,
1701        CommentObjectId::Connection(_) => ObjectType::Connection,
1702        CommentObjectId::Type(_) => ObjectType::Type,
1703        CommentObjectId::Secret(_) => ObjectType::Secret,
1704        CommentObjectId::Role(_) => ObjectType::Role,
1705        CommentObjectId::Database(_) => ObjectType::Database,
1706        CommentObjectId::Schema(_) => ObjectType::Schema,
1707        CommentObjectId::Cluster(_) => ObjectType::Cluster,
1708        CommentObjectId::ClusterReplica(_) => ObjectType::ClusterReplica,
1709        CommentObjectId::NetworkPolicy(_) => ObjectType::NetworkPolicy,
1710    }
1711}
1712
1713pub(crate) fn object_type_to_audit_object_type(
1714    object_type: mz_sql::catalog::ObjectType,
1715) -> ObjectType {
1716    system_object_type_to_audit_object_type(&SystemObjectType::Object(object_type))
1717}
1718
1719pub(crate) fn system_object_type_to_audit_object_type(
1720    system_type: &SystemObjectType,
1721) -> ObjectType {
1722    match system_type {
1723        SystemObjectType::Object(object_type) => match object_type {
1724            mz_sql::catalog::ObjectType::Table => ObjectType::Table,
1725            mz_sql::catalog::ObjectType::View => ObjectType::View,
1726            mz_sql::catalog::ObjectType::MaterializedView => ObjectType::MaterializedView,
1727            mz_sql::catalog::ObjectType::Source => ObjectType::Source,
1728            mz_sql::catalog::ObjectType::Sink => ObjectType::Sink,
1729            mz_sql::catalog::ObjectType::MetricSink => ObjectType::MetricSink,
1730            mz_sql::catalog::ObjectType::Index => ObjectType::Index,
1731            mz_sql::catalog::ObjectType::Type => ObjectType::Type,
1732            mz_sql::catalog::ObjectType::Role => ObjectType::Role,
1733            mz_sql::catalog::ObjectType::Cluster => ObjectType::Cluster,
1734            mz_sql::catalog::ObjectType::ClusterReplica => ObjectType::ClusterReplica,
1735            mz_sql::catalog::ObjectType::Secret => ObjectType::Secret,
1736            mz_sql::catalog::ObjectType::Connection => ObjectType::Connection,
1737            mz_sql::catalog::ObjectType::Database => ObjectType::Database,
1738            mz_sql::catalog::ObjectType::Schema => ObjectType::Schema,
1739            mz_sql::catalog::ObjectType::Func => ObjectType::Func,
1740            mz_sql::catalog::ObjectType::NetworkPolicy => ObjectType::NetworkPolicy,
1741        },
1742        SystemObjectType::System => ObjectType::System,
1743    }
1744}
1745
1746#[derive(Debug, Copy, Clone)]
1747pub enum UpdatePrivilegeVariant {
1748    Grant,
1749    Revoke,
1750}
1751
1752impl From<UpdatePrivilegeVariant> for ExecuteResponse {
1753    fn from(variant: UpdatePrivilegeVariant) -> Self {
1754        match variant {
1755            UpdatePrivilegeVariant::Grant => ExecuteResponse::GrantedPrivilege,
1756            UpdatePrivilegeVariant::Revoke => ExecuteResponse::RevokedPrivilege,
1757        }
1758    }
1759}
1760
1761impl From<UpdatePrivilegeVariant> for EventType {
1762    fn from(variant: UpdatePrivilegeVariant) -> Self {
1763        match variant {
1764            UpdatePrivilegeVariant::Grant => EventType::Grant,
1765            UpdatePrivilegeVariant::Revoke => EventType::Revoke,
1766        }
1767    }
1768}
1769
1770impl ConnCatalog<'_> {
1771    fn resolve_item_name(
1772        &self,
1773        name: &PartialItemName,
1774    ) -> Result<&QualifiedItemName, SqlCatalogError> {
1775        self.resolve_item(name).map(|entry| entry.name())
1776    }
1777
1778    fn resolve_function_name(
1779        &self,
1780        name: &PartialItemName,
1781    ) -> Result<&QualifiedItemName, SqlCatalogError> {
1782        self.resolve_function(name).map(|entry| entry.name())
1783    }
1784
1785    fn resolve_type_name(
1786        &self,
1787        name: &PartialItemName,
1788    ) -> Result<&QualifiedItemName, SqlCatalogError> {
1789        self.resolve_type(name).map(|entry| entry.name())
1790    }
1791}
1792
1793impl ExprHumanizer for ConnCatalog<'_> {
1794    fn humanize_id(&self, id: GlobalId) -> Option<String> {
1795        let entry = self.state.try_get_entry_by_global_id(&id)?;
1796        Some(self.resolve_full_name(entry.name()).to_string())
1797    }
1798
1799    fn humanize_id_unqualified(&self, id: GlobalId) -> Option<String> {
1800        let entry = self.state.try_get_entry_by_global_id(&id)?;
1801        Some(entry.name().item.clone())
1802    }
1803
1804    fn humanize_id_parts(&self, id: GlobalId) -> Option<Vec<String>> {
1805        let entry = self.state.try_get_entry_by_global_id(&id)?;
1806        Some(self.resolve_full_name(entry.name()).into_parts())
1807    }
1808
1809    fn humanize_sql_scalar_type(&self, typ: &SqlScalarType, postgres_compat: bool) -> String {
1810        use SqlScalarType::*;
1811
1812        match typ {
1813            Array(t) => format!("{}[]", self.humanize_sql_scalar_type(t, postgres_compat)),
1814            List {
1815                custom_id: Some(item_id),
1816                ..
1817            }
1818            | Map {
1819                custom_id: Some(item_id),
1820                ..
1821            } => {
1822                let item = self.get_item(item_id);
1823                self.minimal_qualification(item.name()).to_string()
1824            }
1825            List { element_type, .. } => {
1826                format!(
1827                    "{} list",
1828                    self.humanize_sql_scalar_type(element_type, postgres_compat)
1829                )
1830            }
1831            Map { value_type, .. } => format!(
1832                "map[{}=>{}]",
1833                self.humanize_sql_scalar_type(&SqlScalarType::String, postgres_compat),
1834                self.humanize_sql_scalar_type(value_type, postgres_compat)
1835            ),
1836            Record {
1837                custom_id: Some(item_id),
1838                ..
1839            } => {
1840                let item = self.get_item(item_id);
1841                self.minimal_qualification(item.name()).to_string()
1842            }
1843            Record { fields, .. } => format!(
1844                "record({})",
1845                fields
1846                    .iter()
1847                    .map(|f| format!(
1848                        "{}: {}",
1849                        f.0,
1850                        self.humanize_sql_column_type(&f.1, postgres_compat)
1851                    ))
1852                    .join(",")
1853            ),
1854            PgLegacyChar => "\"char\"".into(),
1855            Char { length } if !postgres_compat => match length {
1856                None => "char".into(),
1857                Some(length) => format!("char({})", length.into_u32()),
1858            },
1859            VarChar { max_length } if !postgres_compat => match max_length {
1860                None => "varchar".into(),
1861                Some(length) => format!("varchar({})", length.into_u32()),
1862            },
1863            UInt16 => "uint2".into(),
1864            UInt32 => "uint4".into(),
1865            UInt64 => "uint8".into(),
1866            ty => {
1867                let pgrepr_type = mz_pgrepr::Type::from(ty);
1868                let pg_catalog_schema = SchemaSpecifier::Id(self.state.get_pg_catalog_schema_id());
1869
1870                let res = if self
1871                    .effective_search_path(true)
1872                    .iter()
1873                    .any(|(_, schema)| schema == &pg_catalog_schema)
1874                {
1875                    pgrepr_type.name().to_string()
1876                } else {
1877                    // If PG_CATALOG_SCHEMA is not in search path, you need
1878                    // qualified object name to refer to type.
1879                    let name = QualifiedItemName {
1880                        qualifiers: ItemQualifiers {
1881                            database_spec: ResolvedDatabaseSpecifier::Ambient,
1882                            schema_spec: pg_catalog_schema,
1883                        },
1884                        item: pgrepr_type.name().to_string(),
1885                    };
1886                    self.resolve_full_name(&name).to_string()
1887                };
1888                res
1889            }
1890        }
1891    }
1892
1893    fn column_names_for_id(&self, id: GlobalId) -> Option<Vec<String>> {
1894        let entry = self.state.try_get_entry_by_global_id(&id)?;
1895
1896        match entry.index() {
1897            Some(index) => {
1898                let on_desc = self.state.try_get_desc_by_global_id(&index.on)?;
1899                let mut on_names = on_desc
1900                    .iter_names()
1901                    .map(|col_name| col_name.to_string())
1902                    .collect::<Vec<_>>();
1903
1904                let (p, _) = mz_expr::permutation_for_arrangement(&index.keys, on_desc.arity());
1905
1906                // Init ix_names with unknown column names. Unknown columns are
1907                // represented as an empty String and rendered as `#c` by the
1908                // Display::fmt implementation for HumanizedExpr<'a, usize, M>.
1909                let ix_arity = p.iter().map(|x| *x + 1).max().unwrap_or(0);
1910                let mut ix_names = vec![String::new(); ix_arity];
1911
1912                // Apply the permutation by swapping on_names with ix_names.
1913                for (on_pos, ix_pos) in p.into_iter().enumerate() {
1914                    let on_name = on_names.get_mut(on_pos).expect("on_name");
1915                    let ix_name = ix_names.get_mut(ix_pos).expect("ix_name");
1916                    std::mem::swap(on_name, ix_name);
1917                }
1918
1919                Some(ix_names) // Return the updated ix_names vector.
1920            }
1921            None => {
1922                let desc = self.state.try_get_desc_by_global_id(&id)?;
1923                let column_names = desc
1924                    .iter_names()
1925                    .map(|col_name| col_name.to_string())
1926                    .collect();
1927
1928                Some(column_names)
1929            }
1930        }
1931    }
1932
1933    fn humanize_column(&self, id: GlobalId, column: usize) -> Option<String> {
1934        let desc = self.state.try_get_desc_by_global_id(&id)?;
1935        Some(desc.get_name(column).to_string())
1936    }
1937
1938    fn id_exists(&self, id: GlobalId) -> bool {
1939        self.state.entry_by_global_id.contains_key(&id)
1940    }
1941}
1942
1943impl SessionCatalog for ConnCatalog<'_> {
1944    fn active_role_id(&self) -> &RoleId {
1945        &self.role_id
1946    }
1947
1948    fn restrict_to_user_objects(&self) -> bool {
1949        self.restrict_to_user_objects
1950    }
1951
1952    fn get_prepared_statement_desc(&self, name: &str) -> Option<&StatementDesc> {
1953        self.prepared_statements
1954            .as_ref()
1955            .map(|ps| ps.get(name).map(|ps| ps.desc()))
1956            .flatten()
1957    }
1958
1959    fn get_portal_desc_unverified(&self, portal_name: &str) -> Option<&StatementDesc> {
1960        self.portals
1961            .and_then(|portals| portals.get(portal_name).map(|portal| &portal.desc))
1962    }
1963
1964    fn active_database(&self) -> Option<&DatabaseId> {
1965        self.database.as_ref()
1966    }
1967
1968    fn active_cluster(&self) -> &str {
1969        &self.cluster
1970    }
1971
1972    fn search_path(&self) -> &[(ResolvedDatabaseSpecifier, SchemaSpecifier)] {
1973        &self.search_path
1974    }
1975
1976    fn resolve_database(
1977        &self,
1978        database_name: &str,
1979    ) -> Result<&dyn mz_sql::catalog::CatalogDatabase, SqlCatalogError> {
1980        Ok(self.state.resolve_database(database_name)?)
1981    }
1982
1983    fn get_database(&self, id: &DatabaseId) -> &dyn mz_sql::catalog::CatalogDatabase {
1984        self.state
1985            .database_by_id
1986            .get(id)
1987            .expect("database doesn't exist")
1988    }
1989
1990    // `as` is ok to use to cast to a trait object.
1991    #[allow(clippy::as_conversions)]
1992    fn get_databases(&self) -> Vec<&dyn CatalogDatabase> {
1993        self.state
1994            .database_by_id
1995            .values()
1996            .map(|database| database as &dyn CatalogDatabase)
1997            .collect()
1998    }
1999
2000    fn resolve_schema(
2001        &self,
2002        database_name: Option<&str>,
2003        schema_name: &str,
2004    ) -> Result<&dyn mz_sql::catalog::CatalogSchema, SqlCatalogError> {
2005        Ok(self.state.resolve_schema(
2006            self.database.as_ref(),
2007            database_name,
2008            schema_name,
2009            &self.conn_id,
2010        )?)
2011    }
2012
2013    fn resolve_schema_in_database(
2014        &self,
2015        database_spec: &ResolvedDatabaseSpecifier,
2016        schema_name: &str,
2017    ) -> Result<&dyn mz_sql::catalog::CatalogSchema, SqlCatalogError> {
2018        Ok(self
2019            .state
2020            .resolve_schema_in_database(database_spec, schema_name, &self.conn_id)?)
2021    }
2022
2023    fn get_schema(
2024        &self,
2025        database_spec: &ResolvedDatabaseSpecifier,
2026        schema_spec: &SchemaSpecifier,
2027    ) -> &dyn CatalogSchema {
2028        self.state
2029            .get_schema(database_spec, schema_spec, &self.conn_id)
2030    }
2031
2032    // `as` is ok to use to cast to a trait object.
2033    #[allow(clippy::as_conversions)]
2034    fn get_schemas(&self) -> Vec<&dyn CatalogSchema> {
2035        self.get_databases()
2036            .into_iter()
2037            .flat_map(|database| database.schemas().into_iter())
2038            .chain(
2039                self.state
2040                    .ambient_schemas_by_id
2041                    .values()
2042                    .chain(self.state.temporary_namespaces.schemas())
2043                    .map(|schema| schema as &dyn CatalogSchema),
2044            )
2045            .collect()
2046    }
2047
2048    fn get_mz_internal_schema_id(&self) -> SchemaId {
2049        self.state().get_mz_internal_schema_id()
2050    }
2051
2052    fn get_mz_unsafe_schema_id(&self) -> SchemaId {
2053        self.state().get_mz_unsafe_schema_id()
2054    }
2055
2056    fn is_system_schema_specifier(&self, schema: SchemaSpecifier) -> bool {
2057        self.state.is_system_schema_specifier(schema)
2058    }
2059
2060    fn resolve_role(
2061        &self,
2062        role_name: &str,
2063    ) -> Result<&dyn mz_sql::catalog::CatalogRole, SqlCatalogError> {
2064        match self.state.try_get_role_by_name(role_name) {
2065            Some(role) => Ok(role),
2066            None => Err(SqlCatalogError::UnknownRole(role_name.into())),
2067        }
2068    }
2069
2070    fn resolve_network_policy(
2071        &self,
2072        policy_name: &str,
2073    ) -> Result<&dyn mz_sql::catalog::CatalogNetworkPolicy, SqlCatalogError> {
2074        match self.state.try_get_network_policy_by_name(policy_name) {
2075            Some(policy) => Ok(policy),
2076            None => Err(SqlCatalogError::UnknownNetworkPolicy(policy_name.into())),
2077        }
2078    }
2079
2080    fn try_get_role(&self, id: &RoleId) -> Option<&dyn CatalogRole> {
2081        Some(self.state.roles_by_id.get(id)?)
2082    }
2083
2084    fn get_role(&self, id: &RoleId) -> &dyn mz_sql::catalog::CatalogRole {
2085        self.state.get_role(id)
2086    }
2087
2088    fn get_roles(&self) -> Vec<&dyn CatalogRole> {
2089        // `as` is ok to use to cast to a trait object.
2090        #[allow(clippy::as_conversions)]
2091        self.state
2092            .roles_by_id
2093            .values()
2094            .map(|role| role as &dyn CatalogRole)
2095            .collect()
2096    }
2097
2098    fn mz_system_role_id(&self) -> RoleId {
2099        MZ_SYSTEM_ROLE_ID
2100    }
2101
2102    fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId> {
2103        self.state.collect_role_membership(id)
2104    }
2105
2106    fn get_network_policy(
2107        &self,
2108        id: &NetworkPolicyId,
2109    ) -> &dyn mz_sql::catalog::CatalogNetworkPolicy {
2110        self.state.get_network_policy(id)
2111    }
2112
2113    fn get_network_policies(&self) -> Vec<&dyn mz_sql::catalog::CatalogNetworkPolicy> {
2114        // `as` is ok to use to cast to a trait object.
2115        #[allow(clippy::as_conversions)]
2116        self.state
2117            .network_policies_by_id
2118            .values()
2119            .map(|policy| policy as &dyn CatalogNetworkPolicy)
2120            .collect()
2121    }
2122
2123    fn resolve_cluster(
2124        &self,
2125        cluster_name: Option<&str>,
2126    ) -> Result<&dyn mz_sql::catalog::CatalogCluster<'_>, SqlCatalogError> {
2127        Ok(self
2128            .state
2129            .resolve_cluster(cluster_name.unwrap_or_else(|| self.active_cluster()))?)
2130    }
2131
2132    fn resolve_cluster_replica(
2133        &self,
2134        cluster_replica_name: &QualifiedReplica,
2135    ) -> Result<&dyn CatalogClusterReplica<'_>, SqlCatalogError> {
2136        Ok(self.state.resolve_cluster_replica(cluster_replica_name)?)
2137    }
2138
2139    fn resolve_item(
2140        &self,
2141        name: &PartialItemName,
2142    ) -> Result<&dyn mz_sql::catalog::CatalogItem, SqlCatalogError> {
2143        let r = self.state.resolve_entry(
2144            self.database.as_ref(),
2145            &self.effective_search_path(true),
2146            name,
2147            &self.conn_id,
2148        )?;
2149        if self.unresolvable_ids.contains(&r.id()) {
2150            Err(SqlCatalogError::UnknownItem(name.to_string()))
2151        } else {
2152            Ok(r)
2153        }
2154    }
2155
2156    fn resolve_function(
2157        &self,
2158        name: &PartialItemName,
2159    ) -> Result<&dyn mz_sql::catalog::CatalogItem, SqlCatalogError> {
2160        let r = self.state.resolve_function(
2161            self.database.as_ref(),
2162            &self.effective_search_path(false),
2163            name,
2164            &self.conn_id,
2165        )?;
2166
2167        if self.unresolvable_ids.contains(&r.id()) {
2168            Err(SqlCatalogError::UnknownFunction {
2169                name: name.to_string(),
2170                alternative: None,
2171            })
2172        } else {
2173            Ok(r)
2174        }
2175    }
2176
2177    fn resolve_type(
2178        &self,
2179        name: &PartialItemName,
2180    ) -> Result<&dyn mz_sql::catalog::CatalogItem, SqlCatalogError> {
2181        let r = self.state.resolve_type(
2182            self.database.as_ref(),
2183            &self.effective_search_path(false),
2184            name,
2185            &self.conn_id,
2186        )?;
2187
2188        if self.unresolvable_ids.contains(&r.id()) {
2189            Err(SqlCatalogError::UnknownType {
2190                name: name.to_string(),
2191            })
2192        } else {
2193            Ok(r)
2194        }
2195    }
2196
2197    fn get_system_type(&self, name: &str) -> &dyn mz_sql::catalog::CatalogItem {
2198        self.state.get_system_type(name)
2199    }
2200
2201    fn try_get_item(&self, id: &CatalogItemId) -> Option<&dyn mz_sql::catalog::CatalogItem> {
2202        Some(self.state.try_get_entry(id)?)
2203    }
2204
2205    fn try_get_item_by_global_id(
2206        &self,
2207        id: &GlobalId,
2208    ) -> Option<Box<dyn mz_sql::catalog::CatalogCollectionItem>> {
2209        let entry = self.state.try_get_entry_by_global_id(id)?;
2210        let entry = match &entry.item {
2211            CatalogItem::Table(table) => {
2212                let (version, _gid) = table
2213                    .collections
2214                    .iter()
2215                    .find(|(_version, gid)| *gid == id)
2216                    .expect("catalog out of sync, mismatched GlobalId");
2217                entry.at_version(RelationVersionSelector::Specific(*version))
2218            }
2219            _ => entry.at_version(RelationVersionSelector::Latest),
2220        };
2221        Some(entry)
2222    }
2223
2224    fn get_item(&self, id: &CatalogItemId) -> &dyn mz_sql::catalog::CatalogItem {
2225        self.state.get_entry(id)
2226    }
2227
2228    fn get_item_by_global_id(
2229        &self,
2230        id: &GlobalId,
2231    ) -> Box<dyn mz_sql::catalog::CatalogCollectionItem> {
2232        let entry = self.state.get_entry_by_global_id(id);
2233        let entry = match &entry.item {
2234            CatalogItem::Table(table) => {
2235                let (version, _gid) = table
2236                    .collections
2237                    .iter()
2238                    .find(|(_version, gid)| *gid == id)
2239                    .expect("catalog out of sync, mismatched GlobalId");
2240                entry.at_version(RelationVersionSelector::Specific(*version))
2241            }
2242            _ => entry.at_version(RelationVersionSelector::Latest),
2243        };
2244        entry
2245    }
2246
2247    fn get_items(&self) -> Vec<&dyn mz_sql::catalog::CatalogItem> {
2248        self.get_schemas()
2249            .into_iter()
2250            .flat_map(|schema| schema.item_ids())
2251            .map(|id| self.get_item(&id))
2252            .collect()
2253    }
2254
2255    fn get_item_by_name(&self, name: &QualifiedItemName) -> Option<&dyn SqlCatalogItem> {
2256        self.state
2257            .get_item_by_name(name, &self.conn_id)
2258            .map(|item| convert::identity::<&dyn SqlCatalogItem>(item))
2259    }
2260
2261    fn get_type_by_name(&self, name: &QualifiedItemName) -> Option<&dyn SqlCatalogItem> {
2262        self.state
2263            .get_type_by_name(name, &self.conn_id)
2264            .map(|item| convert::identity::<&dyn SqlCatalogItem>(item))
2265    }
2266
2267    fn get_cluster(&self, id: ClusterId) -> &dyn mz_sql::catalog::CatalogCluster<'_> {
2268        &self.state.clusters_by_id[&id]
2269    }
2270
2271    fn get_clusters(&self) -> Vec<&dyn mz_sql::catalog::CatalogCluster<'_>> {
2272        self.state
2273            .clusters_by_id
2274            .values()
2275            .map(|cluster| convert::identity::<&dyn mz_sql::catalog::CatalogCluster>(cluster))
2276            .collect()
2277    }
2278
2279    fn get_cluster_replica(
2280        &self,
2281        cluster_id: ClusterId,
2282        replica_id: ReplicaId,
2283    ) -> &dyn mz_sql::catalog::CatalogClusterReplica<'_> {
2284        let cluster = self.get_cluster(cluster_id);
2285        cluster.replica(replica_id)
2286    }
2287
2288    fn get_cluster_replicas(&self) -> Vec<&dyn mz_sql::catalog::CatalogClusterReplica<'_>> {
2289        self.get_clusters()
2290            .into_iter()
2291            .flat_map(|cluster| cluster.replicas().into_iter())
2292            .collect()
2293    }
2294
2295    fn get_system_privileges(&self) -> &PrivilegeMap {
2296        &self.state.system_privileges
2297    }
2298
2299    fn get_default_privileges(
2300        &self,
2301    ) -> Vec<(&DefaultPrivilegeObject, Vec<&DefaultPrivilegeAclItem>)> {
2302        self.state
2303            .default_privileges
2304            .iter()
2305            .map(|(object, acl_items)| (object, acl_items.collect()))
2306            .collect()
2307    }
2308
2309    fn find_available_name(&self, name: QualifiedItemName) -> QualifiedItemName {
2310        self.state.find_available_name(name, &self.conn_id)
2311    }
2312
2313    fn resolve_full_name(&self, name: &QualifiedItemName) -> FullItemName {
2314        self.state.resolve_full_name(name, Some(&self.conn_id))
2315    }
2316
2317    fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName {
2318        self.state.resolve_full_schema_name(name)
2319    }
2320
2321    fn resolve_item_id(&self, global_id: &GlobalId) -> CatalogItemId {
2322        self.state.get_entry_by_global_id(global_id).id()
2323    }
2324
2325    fn resolve_global_id(
2326        &self,
2327        item_id: &CatalogItemId,
2328        version: RelationVersionSelector,
2329    ) -> GlobalId {
2330        self.state
2331            .get_entry(item_id)
2332            .at_version(version)
2333            .global_id()
2334    }
2335
2336    fn config(&self) -> &mz_sql::catalog::CatalogConfig {
2337        self.state.config()
2338    }
2339
2340    fn now(&self) -> EpochMillis {
2341        (self.state.config().now)()
2342    }
2343
2344    fn aws_privatelink_availability_zones(&self) -> Option<BTreeSet<String>> {
2345        self.state.aws_privatelink_availability_zones.clone()
2346    }
2347
2348    fn system_vars(&self) -> &SystemVars {
2349        &self.state.system_configuration
2350    }
2351
2352    fn system_vars_mut(&mut self) -> &mut SystemVars {
2353        Arc::make_mut(&mut self.state.to_mut().system_configuration)
2354    }
2355
2356    fn get_owner_id(&self, id: &ObjectId) -> Option<RoleId> {
2357        self.state().get_owner_id(id, self.conn_id())
2358    }
2359
2360    fn get_privileges(&self, id: &SystemObjectId) -> Option<&PrivilegeMap> {
2361        match id {
2362            SystemObjectId::System => Some(&self.state.system_privileges),
2363            SystemObjectId::Object(ObjectId::Cluster(id)) => {
2364                Some(self.get_cluster(*id).privileges())
2365            }
2366            SystemObjectId::Object(ObjectId::Database(id)) => {
2367                Some(self.get_database(id).privileges())
2368            }
2369            SystemObjectId::Object(ObjectId::Schema((database_spec, schema_spec))) => {
2370                // For temporary schemas that haven't been created yet (lazy creation),
2371                // we return None - the RBAC check will need to handle this case.
2372                self.state
2373                    .try_get_schema(database_spec, schema_spec, &self.conn_id)
2374                    .map(|schema| schema.privileges())
2375            }
2376            SystemObjectId::Object(ObjectId::Item(id)) => Some(self.get_item(id).privileges()),
2377            SystemObjectId::Object(ObjectId::NetworkPolicy(id)) => {
2378                Some(self.get_network_policy(id).privileges())
2379            }
2380            SystemObjectId::Object(ObjectId::ClusterReplica(_))
2381            | SystemObjectId::Object(ObjectId::Role(_)) => None,
2382        }
2383    }
2384
2385    fn object_dependents(&self, ids: &Vec<ObjectId>) -> Vec<ObjectId> {
2386        let mut seen = BTreeSet::new();
2387        self.state.object_dependents(ids, &self.conn_id, &mut seen)
2388    }
2389
2390    fn item_dependents(&self, id: CatalogItemId) -> Vec<ObjectId> {
2391        let mut seen = BTreeSet::new();
2392        self.state.item_dependents(id, &mut seen)
2393    }
2394
2395    fn all_object_privileges(&self, object_type: mz_sql::catalog::SystemObjectType) -> AclMode {
2396        rbac::all_object_privileges(object_type)
2397    }
2398
2399    fn get_object_type(&self, object_id: &ObjectId) -> mz_sql::catalog::ObjectType {
2400        self.state.get_object_type(object_id)
2401    }
2402
2403    fn get_system_object_type(&self, id: &SystemObjectId) -> mz_sql::catalog::SystemObjectType {
2404        self.state.get_system_object_type(id)
2405    }
2406
2407    /// Returns a [`PartialItemName`] with the minimum amount of qualifiers to unambiguously resolve
2408    /// the object.
2409    ///
2410    /// Warning: This is broken for temporary objects. Don't use this function for serious stuff,
2411    /// i.e., don't expect that what you get back is a thing you can resolve. Current usages are
2412    /// only for error msgs and other humanizations.
2413    fn minimal_qualification(&self, qualified_name: &QualifiedItemName) -> PartialItemName {
2414        if qualified_name.qualifiers.schema_spec.is_temporary() {
2415            // All bets are off. Just give up and return the qualified name as is.
2416            // TODO: Figure out what's going on with temporary objects.
2417
2418            // See e.g. `temporary_objects.slt` fail if you comment this out, which has the repro
2419            // from https://github.com/MaterializeInc/database-issues/issues/9973#issuecomment-3646382143
2420            // There is also https://github.com/MaterializeInc/database-issues/issues/9974, for
2421            // which we don't have a simple repro.
2422            return qualified_name.item.clone().into();
2423        }
2424
2425        let database_id = match &qualified_name.qualifiers.database_spec {
2426            ResolvedDatabaseSpecifier::Ambient => None,
2427            ResolvedDatabaseSpecifier::Id(id)
2428                if self.database.is_some() && self.database == Some(*id) =>
2429            {
2430                None
2431            }
2432            ResolvedDatabaseSpecifier::Id(id) => Some(id.clone()),
2433        };
2434
2435        let schema_spec = if database_id.is_none()
2436            && self.resolve_item_name(&PartialItemName {
2437                database: None,
2438                schema: None,
2439                item: qualified_name.item.clone(),
2440            }) == Ok(qualified_name)
2441            || self.resolve_function_name(&PartialItemName {
2442                database: None,
2443                schema: None,
2444                item: qualified_name.item.clone(),
2445            }) == Ok(qualified_name)
2446            || self.resolve_type_name(&PartialItemName {
2447                database: None,
2448                schema: None,
2449                item: qualified_name.item.clone(),
2450            }) == Ok(qualified_name)
2451        {
2452            None
2453        } else {
2454            // If `search_path` does not contain `full_name.schema`, the
2455            // `PartialName` must contain it.
2456            Some(qualified_name.qualifiers.schema_spec.clone())
2457        };
2458
2459        let res = PartialItemName {
2460            database: database_id.map(|id| self.get_database(&id).name().to_string()),
2461            schema: schema_spec.map(|spec| {
2462                self.get_schema(&qualified_name.qualifiers.database_spec, &spec)
2463                    .name()
2464                    .schema
2465                    .clone()
2466            }),
2467            item: qualified_name.item.clone(),
2468        };
2469        assert!(
2470            self.resolve_item_name(&res) == Ok(qualified_name)
2471                || self.resolve_function_name(&res) == Ok(qualified_name)
2472                || self.resolve_type_name(&res) == Ok(qualified_name)
2473        );
2474        res
2475    }
2476
2477    fn add_notice(&self, notice: PlanNotice) {
2478        let _ = self.notices_tx.send(notice.into());
2479    }
2480
2481    fn get_item_comments(&self, id: &CatalogItemId) -> Option<&BTreeMap<Option<usize>, String>> {
2482        let comment_id = self.state.get_comment_id(ObjectId::Item(*id));
2483        self.state.comments.get_object_comments(comment_id)
2484    }
2485
2486    fn is_cluster_size_cc(&self, size: &str) -> bool {
2487        self.state
2488            .cluster_replica_sizes
2489            .0
2490            .get(size)
2491            .map_or(false, |a| a.is_cc)
2492    }
2493}
2494
2495#[cfg(test)]
2496mod tests {
2497    use std::collections::{BTreeMap, BTreeSet};
2498    use std::sync::Arc;
2499    use std::{env, iter};
2500
2501    use itertools::Itertools;
2502    use mz_catalog::memory::objects::CatalogItem;
2503    use mz_postgres_util::{query, sql};
2504    use tokio_postgres::NoTls;
2505    use tokio_postgres::types::Type;
2506    use uuid::Uuid;
2507
2508    use mz_catalog::SYSTEM_CONN_ID;
2509    use mz_catalog::builtin::{BUILTINS, Builtin, BuiltinType};
2510    use mz_catalog::durable::{CatalogError, DurableCatalogError, FenceError, test_bootstrap_args};
2511    use mz_controller_types::{ClusterId, ReplicaId};
2512    use mz_expr::{Eval, MirScalarExpr};
2513    use mz_ore::now::to_datetime;
2514    use mz_ore::{assert_err, assert_ok, soft_assert_eq_or_log, task};
2515    use mz_persist_client::PersistClient;
2516    use mz_pgrepr::oid::{FIRST_MATERIALIZE_OID, FIRST_UNPINNED_OID, FIRST_USER_OID};
2517    use mz_repr::namespaces::{INFORMATION_SCHEMA, PG_CATALOG_SCHEMA};
2518    use mz_repr::role_id::RoleId;
2519    use mz_repr::{
2520        CatalogItemId, Datum, GlobalId, RelationVersionSelector, Row, RowArena, SqlRelationType,
2521        SqlScalarType, Timestamp,
2522    };
2523    use mz_sql::catalog::{CatalogSchema, CatalogType, SessionCatalog};
2524    use mz_sql::func::{Func, FuncImpl, OP_IMPLS, Operation};
2525    use mz_sql::names::{
2526        self, DatabaseId, ItemQualifiers, ObjectId, PartialItemName, QualifiedItemName,
2527        ResolvedDatabaseSpecifier, SchemaId, SchemaSpecifier, SystemObjectId,
2528    };
2529    use mz_sql::plan::{
2530        CoercibleScalarExpr, ExprContext, HirScalarExpr, HirToMirConfig, PlanContext, QueryContext,
2531        QueryLifetime, Scope, StatementContext,
2532    };
2533    use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
2534    use mz_sql::session::vars::{SystemVars, VarInput};
2535
2536    use crate::catalog::state::LocalExpressionCache;
2537    use crate::catalog::{Catalog, DebugAwsContext, Op};
2538    use crate::optimize::dataflows::{EvalTime, ExprPrep, ExprPrepOneShot};
2539    use crate::session::Session;
2540
2541    /// System sessions have an empty `search_path` so it's necessary to
2542    /// schema-qualify all referenced items.
2543    ///
2544    /// Dummy (and ostensibly client) sessions contain system schemas in their
2545    /// search paths, so do not require schema qualification on system objects such
2546    /// as types.
2547    #[mz_ore::test(tokio::test)]
2548    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2549    async fn test_minimal_qualification() {
2550        Catalog::with_debug(|catalog| async move {
2551            struct TestCase {
2552                input: QualifiedItemName,
2553                system_output: PartialItemName,
2554                normal_output: PartialItemName,
2555            }
2556
2557            let test_cases = vec![
2558                TestCase {
2559                    input: QualifiedItemName {
2560                        qualifiers: ItemQualifiers {
2561                            database_spec: ResolvedDatabaseSpecifier::Ambient,
2562                            schema_spec: SchemaSpecifier::Id(catalog.get_pg_catalog_schema_id()),
2563                        },
2564                        item: "numeric".to_string(),
2565                    },
2566                    system_output: PartialItemName {
2567                        database: None,
2568                        schema: None,
2569                        item: "numeric".to_string(),
2570                    },
2571                    normal_output: PartialItemName {
2572                        database: None,
2573                        schema: None,
2574                        item: "numeric".to_string(),
2575                    },
2576                },
2577                TestCase {
2578                    input: QualifiedItemName {
2579                        qualifiers: ItemQualifiers {
2580                            database_spec: ResolvedDatabaseSpecifier::Ambient,
2581                            schema_spec: SchemaSpecifier::Id(catalog.get_mz_catalog_schema_id()),
2582                        },
2583                        item: "mz_array_types".to_string(),
2584                    },
2585                    system_output: PartialItemName {
2586                        database: None,
2587                        schema: None,
2588                        item: "mz_array_types".to_string(),
2589                    },
2590                    normal_output: PartialItemName {
2591                        database: None,
2592                        schema: None,
2593                        item: "mz_array_types".to_string(),
2594                    },
2595                },
2596            ];
2597
2598            for tc in test_cases {
2599                assert_eq!(
2600                    catalog
2601                        .for_system_session()
2602                        .minimal_qualification(&tc.input),
2603                    tc.system_output
2604                );
2605                assert_eq!(
2606                    catalog
2607                        .for_session(&Session::dummy())
2608                        .minimal_qualification(&tc.input),
2609                    tc.normal_output
2610                );
2611            }
2612            catalog.expire().await;
2613        })
2614        .await
2615    }
2616
2617    #[mz_ore::test(tokio::test)]
2618    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2619    async fn test_catalog_revision() {
2620        let persist_client = PersistClient::new_for_tests().await;
2621        let organization_id = Uuid::new_v4();
2622        let bootstrap_args = test_bootstrap_args();
2623        {
2624            let mut catalog = Catalog::open_debug_catalog(
2625                persist_client.clone(),
2626                organization_id.clone(),
2627                &bootstrap_args,
2628            )
2629            .await
2630            .expect("unable to open debug catalog");
2631            assert_eq!(catalog.transient_revision(), 1);
2632            assert!(catalog.transient_revision_is_current());
2633            let snapshot = catalog.clone();
2634            let commit_ts = catalog.current_upper().await;
2635            catalog
2636                .transact(
2637                    None,
2638                    commit_ts,
2639                    None,
2640                    vec![Op::CreateDatabase {
2641                        name: "test".to_string(),
2642                        owner_id: MZ_SYSTEM_ROLE_ID,
2643                    }],
2644                )
2645                .await
2646                .expect("failed to transact");
2647            assert_eq!(catalog.transient_revision(), 2);
2648            assert!(catalog.transient_revision_is_current());
2649            // The pre-transaction snapshot detects its own staleness through
2650            // the shared latest revision.
2651            assert!(!snapshot.transient_revision_is_current());
2652            catalog.expire().await;
2653        }
2654        {
2655            let catalog =
2656                Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
2657                    .await
2658                    .expect("unable to open debug catalog");
2659            // Re-opening the same catalog resets the transient_revision to 1.
2660            assert_eq!(catalog.transient_revision(), 1);
2661            catalog.expire().await;
2662        }
2663    }
2664
2665    #[mz_ore::test(tokio::test)]
2666    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2667    async fn test_effective_search_path() {
2668        Catalog::with_debug(|catalog| async move {
2669            let mz_catalog_schema = (
2670                ResolvedDatabaseSpecifier::Ambient,
2671                SchemaSpecifier::Id(catalog.state().get_mz_catalog_schema_id()),
2672            );
2673            let pg_catalog_schema = (
2674                ResolvedDatabaseSpecifier::Ambient,
2675                SchemaSpecifier::Id(catalog.state().get_pg_catalog_schema_id()),
2676            );
2677            let mz_temp_schema = (
2678                ResolvedDatabaseSpecifier::Ambient,
2679                SchemaSpecifier::Temporary,
2680            );
2681
2682            // Behavior with the default search_schema (public)
2683            let session = Session::dummy();
2684            let conn_catalog = catalog.for_session(&session);
2685            assert_ne!(
2686                conn_catalog.effective_search_path(false),
2687                conn_catalog.search_path
2688            );
2689            assert_ne!(
2690                conn_catalog.effective_search_path(true),
2691                conn_catalog.search_path
2692            );
2693            assert_eq!(
2694                conn_catalog.effective_search_path(false),
2695                vec![
2696                    mz_catalog_schema.clone(),
2697                    pg_catalog_schema.clone(),
2698                    conn_catalog.search_path[0].clone()
2699                ]
2700            );
2701            assert_eq!(
2702                conn_catalog.effective_search_path(true),
2703                vec![
2704                    mz_temp_schema.clone(),
2705                    mz_catalog_schema.clone(),
2706                    pg_catalog_schema.clone(),
2707                    conn_catalog.search_path[0].clone()
2708                ]
2709            );
2710
2711            // missing schemas are added when missing
2712            let mut session = Session::dummy();
2713            session
2714                .vars_mut()
2715                .set(
2716                    &SystemVars::new(),
2717                    "search_path",
2718                    VarInput::Flat(mz_repr::namespaces::PG_CATALOG_SCHEMA),
2719                    false,
2720                )
2721                .expect("failed to set search_path");
2722            let conn_catalog = catalog.for_session(&session);
2723            assert_ne!(
2724                conn_catalog.effective_search_path(false),
2725                conn_catalog.search_path
2726            );
2727            assert_ne!(
2728                conn_catalog.effective_search_path(true),
2729                conn_catalog.search_path
2730            );
2731            assert_eq!(
2732                conn_catalog.effective_search_path(false),
2733                vec![mz_catalog_schema.clone(), pg_catalog_schema.clone()]
2734            );
2735            assert_eq!(
2736                conn_catalog.effective_search_path(true),
2737                vec![
2738                    mz_temp_schema.clone(),
2739                    mz_catalog_schema.clone(),
2740                    pg_catalog_schema.clone()
2741                ]
2742            );
2743
2744            let mut session = Session::dummy();
2745            session
2746                .vars_mut()
2747                .set(
2748                    &SystemVars::new(),
2749                    "search_path",
2750                    VarInput::Flat(mz_repr::namespaces::MZ_CATALOG_SCHEMA),
2751                    false,
2752                )
2753                .expect("failed to set search_path");
2754            let conn_catalog = catalog.for_session(&session);
2755            assert_ne!(
2756                conn_catalog.effective_search_path(false),
2757                conn_catalog.search_path
2758            );
2759            assert_ne!(
2760                conn_catalog.effective_search_path(true),
2761                conn_catalog.search_path
2762            );
2763            assert_eq!(
2764                conn_catalog.effective_search_path(false),
2765                vec![pg_catalog_schema.clone(), mz_catalog_schema.clone()]
2766            );
2767            assert_eq!(
2768                conn_catalog.effective_search_path(true),
2769                vec![
2770                    mz_temp_schema.clone(),
2771                    pg_catalog_schema.clone(),
2772                    mz_catalog_schema.clone()
2773                ]
2774            );
2775
2776            let mut session = Session::dummy();
2777            session
2778                .vars_mut()
2779                .set(
2780                    &SystemVars::new(),
2781                    "search_path",
2782                    VarInput::Flat(mz_repr::namespaces::MZ_TEMP_SCHEMA),
2783                    false,
2784                )
2785                .expect("failed to set search_path");
2786            let conn_catalog = catalog.for_session(&session);
2787            assert_ne!(
2788                conn_catalog.effective_search_path(false),
2789                conn_catalog.search_path
2790            );
2791            assert_ne!(
2792                conn_catalog.effective_search_path(true),
2793                conn_catalog.search_path
2794            );
2795            // Because we lazily initialize the `mz_temp` schema,
2796            // an explicit `mz_temp` search path before the first
2797            // temporary item creation gets filtered out in
2798            // `effective_search_path`.
2799            assert_eq!(
2800                conn_catalog.effective_search_path(false),
2801                vec![mz_catalog_schema.clone(), pg_catalog_schema.clone(),]
2802            );
2803            assert_eq!(
2804                conn_catalog.effective_search_path(true),
2805                vec![mz_temp_schema, mz_catalog_schema, pg_catalog_schema]
2806            );
2807            catalog.expire().await;
2808        })
2809        .await
2810    }
2811
2812    #[mz_ore::test(tokio::test)]
2813    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2814    async fn test_normalized_create() {
2815        use mz_ore::collections::CollectionExt;
2816        Catalog::with_debug(|catalog| async move {
2817            let conn_catalog = catalog.for_system_session();
2818            let scx = &mut StatementContext::new(None, &conn_catalog);
2819
2820            let parsed = mz_sql_parser::parser::parse_statements(
2821                "create view public.foo as select 1 as bar",
2822            )
2823            .expect("")
2824            .into_element()
2825            .ast;
2826
2827            let (stmt, _) = names::resolve(scx.catalog, parsed).expect("");
2828
2829            // Ensure that all identifiers are quoted.
2830            assert_eq!(
2831                r#"CREATE VIEW "materialize"."public"."foo" AS SELECT 1 AS "bar""#,
2832                mz_sql::normalize::create_statement(scx, stmt).expect(""),
2833            );
2834            catalog.expire().await;
2835        })
2836        .await;
2837    }
2838
2839    // Test that if a large catalog item is somehow committed, then we can still load the catalog.
2840    #[mz_ore::test(tokio::test)]
2841    #[cfg_attr(miri, ignore)] // slow
2842    async fn test_large_catalog_item() {
2843        let view_def = "CREATE VIEW \"materialize\".\"public\".\"v\" AS SELECT 1 FROM (SELECT 1";
2844        let column = ", 1";
2845        let view_def_size = view_def.bytes().count();
2846        let column_size = column.bytes().count();
2847        let column_count =
2848            (mz_sql_parser::parser::MAX_STATEMENT_BATCH_SIZE - view_def_size) / column_size + 1;
2849        let columns = iter::repeat(column).take(column_count).join("");
2850        let create_sql = format!("{view_def}{columns})");
2851        let create_sql_check = create_sql.clone();
2852        assert_ok!(mz_sql_parser::parser::parse_statements(&create_sql));
2853        assert_err!(mz_sql_parser::parser::parse_statements_with_limit(
2854            &create_sql
2855        ));
2856
2857        let persist_client = PersistClient::new_for_tests().await;
2858        let organization_id = Uuid::new_v4();
2859        let id = CatalogItemId::User(1);
2860        let gid = GlobalId::User(1);
2861        let bootstrap_args = test_bootstrap_args();
2862        {
2863            let mut catalog = Catalog::open_debug_catalog(
2864                persist_client.clone(),
2865                organization_id.clone(),
2866                &bootstrap_args,
2867            )
2868            .await
2869            .expect("unable to open debug catalog");
2870            let item = catalog
2871                .state()
2872                .deserialize_item(
2873                    gid,
2874                    &create_sql,
2875                    &BTreeMap::new(),
2876                    &mut LocalExpressionCache::Closed,
2877                    None,
2878                )
2879                .expect("unable to parse view");
2880            let commit_ts = catalog.current_upper().await;
2881            catalog
2882                .transact(
2883                    None,
2884                    commit_ts,
2885                    None,
2886                    vec![Op::CreateItem {
2887                        item,
2888                        name: QualifiedItemName {
2889                            qualifiers: ItemQualifiers {
2890                                database_spec: ResolvedDatabaseSpecifier::Id(DatabaseId::User(1)),
2891                                schema_spec: SchemaSpecifier::Id(SchemaId::User(3)),
2892                            },
2893                            item: "v".to_string(),
2894                        },
2895                        id,
2896                        owner_id: MZ_SYSTEM_ROLE_ID,
2897                    }],
2898                )
2899                .await
2900                .expect("failed to transact");
2901            catalog.expire().await;
2902        }
2903        {
2904            let catalog =
2905                Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
2906                    .await
2907                    .expect("unable to open debug catalog");
2908            let view = catalog.get_entry(&id);
2909            assert_eq!("v", view.name.item);
2910            match &view.item {
2911                CatalogItem::View(view) => assert_eq!(create_sql_check, view.create_sql),
2912                item => panic!("expected view, got {}", item.typ()),
2913            }
2914            catalog.expire().await;
2915        }
2916    }
2917
2918    #[mz_ore::test(tokio::test)]
2919    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2920    async fn test_object_type() {
2921        Catalog::with_debug(|catalog| async move {
2922            let conn_catalog = catalog.for_system_session();
2923
2924            assert_eq!(
2925                mz_sql::catalog::ObjectType::ClusterReplica,
2926                conn_catalog.get_object_type(&ObjectId::ClusterReplica((
2927                    ClusterId::user(1).expect("1 is a valid ID"),
2928                    ReplicaId::User(1)
2929                )))
2930            );
2931            assert_eq!(
2932                mz_sql::catalog::ObjectType::Role,
2933                conn_catalog.get_object_type(&ObjectId::Role(RoleId::User(1)))
2934            );
2935            catalog.expire().await;
2936        })
2937        .await;
2938    }
2939
2940    #[mz_ore::test(tokio::test)]
2941    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2942    async fn test_get_privileges() {
2943        Catalog::with_debug(|catalog| async move {
2944            let conn_catalog = catalog.for_system_session();
2945
2946            assert_eq!(
2947                None,
2948                conn_catalog.get_privileges(&SystemObjectId::Object(ObjectId::ClusterReplica((
2949                    ClusterId::user(1).expect("1 is a valid ID"),
2950                    ReplicaId::User(1),
2951                ))))
2952            );
2953            assert_eq!(
2954                None,
2955                conn_catalog
2956                    .get_privileges(&SystemObjectId::Object(ObjectId::Role(RoleId::User(1))))
2957            );
2958            catalog.expire().await;
2959        })
2960        .await;
2961    }
2962
2963    #[mz_ore::test(tokio::test)]
2964    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2965    async fn verify_builtin_descs() {
2966        // Provide a full AWS context so the builtin AWS connection views resolve
2967        // as they do in a cloud environment. Without an account id,
2968        // `mz_aws_privatelink_connections.principal` folds to NULL, its
2969        // `WHERE principal IS NOT NULL` filter is statically unsatisfiable, and
2970        // the optimizer infers an extra empty key that the declared desc, which
2971        // describes the cloud shape, does not carry.
2972        let aws_context = DebugAwsContext {
2973            aws_account_id: Some("123456789000".to_string()),
2974            aws_external_id_prefix: Some("eb5cb59b-e2fe-41f3-87ca-d2176a495345".to_string()),
2975            aws_connection_role_arn: Some(
2976                "arn:aws:iam::123456789000:role/MaterializeConnection".to_string(),
2977            ),
2978        };
2979        Catalog::with_debug_aws_context(aws_context, |catalog| async move {
2980            let conn_catalog = catalog.for_system_session();
2981
2982            for builtin in BUILTINS::iter() {
2983                let (schema, name, expected_desc) = match builtin {
2984                    Builtin::Table(t) => (&t.schema, &t.name, &t.desc),
2985                    Builtin::View(v) => (&v.schema, &v.name, &v.desc),
2986                    Builtin::MaterializedView(mv) => (&mv.schema, &mv.name, &mv.desc),
2987                    Builtin::Source(s) => (&s.schema, &s.name, &s.desc),
2988                    Builtin::Log(_)
2989                    | Builtin::Type(_)
2990                    | Builtin::Func(_)
2991                    | Builtin::Index(_)
2992                    | Builtin::Connection(_) => continue,
2993                };
2994                let item = conn_catalog
2995                    .resolve_item(&PartialItemName {
2996                        database: None,
2997                        schema: Some(schema.to_string()),
2998                        item: name.to_string(),
2999                    })
3000                    .expect("unable to resolve item")
3001                    .at_version(RelationVersionSelector::Latest);
3002
3003                let actual_desc = item.relation_desc().expect("invalid item type");
3004                for (index, ((actual_name, actual_typ), (expected_name, expected_typ))) in
3005                    actual_desc.iter().zip_eq(expected_desc.iter()).enumerate()
3006                {
3007                    assert_eq!(
3008                        actual_name, expected_name,
3009                        "item {schema}.{name} column {index} name did not match its expected name"
3010                    );
3011                    assert_eq!(
3012                        actual_typ, expected_typ,
3013                        "item {schema}.{name} column {index} ('{actual_name}') type did not match its expected type"
3014                    );
3015                }
3016                assert_eq!(
3017                    &*actual_desc, expected_desc,
3018                    "item {schema}.{name} did not match its expected RelationDesc"
3019                );
3020            }
3021            catalog.expire().await;
3022        })
3023        .await
3024    }
3025
3026    // Connect to a running Postgres server and verify that our builtin
3027    // types and functions match it, in addition to some other things.
3028    #[mz_ore::test(tokio::test)]
3029    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3030    async fn test_compare_builtins_postgres() {
3031        async fn inner(catalog: Catalog) {
3032            // Verify that all builtin functions:
3033            // - have a unique OID
3034            // - if they have a postgres counterpart (same oid) then they have matching name
3035            let (client, connection) = tokio_postgres::connect(
3036                &env::var("POSTGRES_URL").unwrap_or_else(|_| "host=localhost user=postgres".into()),
3037                NoTls,
3038            )
3039            .await
3040            .expect("failed to connect to Postgres");
3041
3042            task::spawn(|| "compare_builtin_postgres", async move {
3043                if let Err(e) = connection.await {
3044                    panic!("connection error: {}", e);
3045                }
3046            });
3047
3048            struct PgProc {
3049                name: String,
3050                arg_oids: Vec<u32>,
3051                ret_oid: Option<u32>,
3052                ret_set: bool,
3053            }
3054
3055            struct PgType {
3056                name: String,
3057                ty: String,
3058                elem: u32,
3059                array: u32,
3060                input: u32,
3061                receive: u32,
3062            }
3063
3064            struct PgOper {
3065                oprresult: u32,
3066                name: String,
3067            }
3068
3069            let pg_proc: BTreeMap<_, _> = query(
3070                &client,
3071                sql!(
3072                    "SELECT
3073                    p.oid,
3074                    proname,
3075                    proargtypes,
3076                    prorettype,
3077                    proretset
3078                FROM pg_proc p
3079                JOIN pg_namespace n ON p.pronamespace = n.oid"
3080                ),
3081                &[],
3082            )
3083            .await
3084            .expect("pg query failed")
3085            .into_iter()
3086            .map(|row| {
3087                let oid: u32 = row.get("oid");
3088                let pg_proc = PgProc {
3089                    name: row.get("proname"),
3090                    arg_oids: row.get("proargtypes"),
3091                    ret_oid: row.get("prorettype"),
3092                    ret_set: row.get("proretset"),
3093                };
3094                (oid, pg_proc)
3095            })
3096            .collect();
3097
3098            let pg_type: BTreeMap<_, _> = query(
3099                &client,
3100                sql!(
3101                    "SELECT oid, typname, typtype::text, typelem, typarray, typinput::oid, typreceive::oid as typreceive FROM pg_type"
3102                ),
3103                &[],
3104            )
3105            .await
3106                .expect("pg query failed")
3107                .into_iter()
3108                .map(|row| {
3109                    let oid: u32 = row.get("oid");
3110                    let pg_type = PgType {
3111                        name: row.get("typname"),
3112                        ty: row.get("typtype"),
3113                        elem: row.get("typelem"),
3114                        array: row.get("typarray"),
3115                        input: row.get("typinput"),
3116                        receive: row.get("typreceive"),
3117                    };
3118                    (oid, pg_type)
3119                })
3120                .collect();
3121
3122            let pg_oper: BTreeMap<_, _> = query(
3123                &client,
3124                sql!("SELECT oid, oprname, oprresult FROM pg_operator"),
3125                &[],
3126            )
3127            .await
3128            .expect("pg query failed")
3129            .into_iter()
3130            .map(|row| {
3131                let oid: u32 = row.get("oid");
3132                let pg_oper = PgOper {
3133                    name: row.get("oprname"),
3134                    oprresult: row.get("oprresult"),
3135                };
3136                (oid, pg_oper)
3137            })
3138            .collect();
3139
3140            let conn_catalog = catalog.for_system_session();
3141            let resolve_type_oid = |item: &str| {
3142                conn_catalog
3143                    .resolve_type(&PartialItemName {
3144                        database: None,
3145                        // All functions we check exist in PG, so the types must, as
3146                        // well
3147                        schema: Some(PG_CATALOG_SCHEMA.into()),
3148                        item: item.to_string(),
3149                    })
3150                    .expect("unable to resolve type")
3151                    .oid()
3152            };
3153
3154            let func_oids: BTreeSet<_> = BUILTINS::funcs()
3155                .flat_map(|f| f.inner.func_impls().into_iter().map(|f| f.oid))
3156                .collect();
3157
3158            let mut all_oids = BTreeSet::new();
3159
3160            // A function to determine if two oids are equivalent enough for these tests. We don't
3161            // support some types, so map exceptions here.
3162            let equivalent_types: BTreeSet<(Option<u32>, Option<u32>)> = BTreeSet::from_iter(
3163                [
3164                    // We don't support NAME.
3165                    (Type::NAME, Type::TEXT),
3166                    (Type::NAME_ARRAY, Type::TEXT_ARRAY),
3167                    // We don't support time with time zone.
3168                    (Type::TIME, Type::TIMETZ),
3169                    (Type::TIME_ARRAY, Type::TIMETZ_ARRAY),
3170                ]
3171                .map(|(a, b)| (Some(a.oid()), Some(b.oid()))),
3172            );
3173            let ignore_return_types: BTreeSet<u32> = BTreeSet::from([
3174                1619, // pg_typeof: TODO: We now have regtype and can correctly implement this.
3175            ]);
3176            let is_same_type = |fn_oid: u32, a: Option<u32>, b: Option<u32>| -> bool {
3177                if ignore_return_types.contains(&fn_oid) {
3178                    return true;
3179                }
3180                if equivalent_types.contains(&(a, b)) || equivalent_types.contains(&(b, a)) {
3181                    return true;
3182                }
3183                a == b
3184            };
3185
3186            for builtin in BUILTINS::iter() {
3187                match builtin {
3188                    Builtin::Type(ty) => {
3189                        assert!(all_oids.insert(ty.oid), "{} reused oid {}", ty.name, ty.oid);
3190
3191                        if ty.oid >= FIRST_MATERIALIZE_OID {
3192                            // High OIDs are reserved in Materialize and don't have
3193                            // PostgreSQL counterparts.
3194                            continue;
3195                        }
3196
3197                        // For types that have a PostgreSQL counterpart, verify that
3198                        // the name and oid match.
3199                        let pg_ty = pg_type.get(&ty.oid).unwrap_or_else(|| {
3200                            panic!("pg_proc missing type {}: oid {}", ty.name, ty.oid)
3201                        });
3202                        assert_eq!(
3203                            ty.name, pg_ty.name,
3204                            "oid {} has name {} in postgres; expected {}",
3205                            ty.oid, pg_ty.name, ty.name,
3206                        );
3207
3208                        let (typinput_oid, typreceive_oid) = match &ty.details.pg_metadata {
3209                            None => (0, 0),
3210                            Some(pgmeta) => (pgmeta.typinput_oid, pgmeta.typreceive_oid),
3211                        };
3212                        assert_eq!(
3213                            typinput_oid, pg_ty.input,
3214                            "type {} has typinput OID {:?} in mz but {:?} in pg",
3215                            ty.name, typinput_oid, pg_ty.input,
3216                        );
3217                        assert_eq!(
3218                            typreceive_oid, pg_ty.receive,
3219                            "type {} has typreceive OID {:?} in mz but {:?} in pg",
3220                            ty.name, typreceive_oid, pg_ty.receive,
3221                        );
3222                        if typinput_oid != 0 {
3223                            assert!(
3224                                func_oids.contains(&typinput_oid),
3225                                "type {} has typinput OID {} that does not exist in pg_proc",
3226                                ty.name,
3227                                typinput_oid,
3228                            );
3229                        }
3230                        if typreceive_oid != 0 {
3231                            assert!(
3232                                func_oids.contains(&typreceive_oid),
3233                                "type {} has typreceive OID {} that does not exist in pg_proc",
3234                                ty.name,
3235                                typreceive_oid,
3236                            );
3237                        }
3238
3239                        // Ensure the type matches.
3240                        match &ty.details.typ {
3241                            CatalogType::Array { element_reference } => {
3242                                let elem_ty = BUILTINS::iter()
3243                                    .filter_map(|builtin| match builtin {
3244                                        Builtin::Type(ty @ BuiltinType { name, .. })
3245                                            if element_reference == name =>
3246                                        {
3247                                            Some(ty)
3248                                        }
3249                                        _ => None,
3250                                    })
3251                                    .next();
3252                                let elem_ty = match elem_ty {
3253                                    Some(ty) => ty,
3254                                    None => {
3255                                        panic!("{} is unexpectedly not a type", element_reference)
3256                                    }
3257                                };
3258                                assert_eq!(
3259                                    pg_ty.elem, elem_ty.oid,
3260                                    "type {} has mismatched element OIDs",
3261                                    ty.name
3262                                )
3263                            }
3264                            CatalogType::Pseudo => {
3265                                assert_eq!(
3266                                    pg_ty.ty, "p",
3267                                    "type {} is not a pseudo type as expected",
3268                                    ty.name
3269                                )
3270                            }
3271                            CatalogType::Range { .. } => {
3272                                assert_eq!(
3273                                    pg_ty.ty, "r",
3274                                    "type {} is not a range type as expected",
3275                                    ty.name
3276                                );
3277                            }
3278                            _ => {
3279                                assert_eq!(
3280                                    pg_ty.ty, "b",
3281                                    "type {} is not a base type as expected",
3282                                    ty.name
3283                                )
3284                            }
3285                        }
3286
3287                        // Ensure the array type reference is correct.
3288                        let schema = catalog
3289                            .resolve_schema_in_database(
3290                                &ResolvedDatabaseSpecifier::Ambient,
3291                                ty.schema,
3292                                &SYSTEM_CONN_ID,
3293                            )
3294                            .expect("unable to resolve schema");
3295                        let allocated_type = catalog
3296                            .resolve_type(
3297                                None,
3298                                &vec![(ResolvedDatabaseSpecifier::Ambient, schema.id().clone())],
3299                                &PartialItemName {
3300                                    database: None,
3301                                    schema: Some(schema.name().schema.clone()),
3302                                    item: ty.name.to_string(),
3303                                },
3304                                &SYSTEM_CONN_ID,
3305                            )
3306                            .expect("unable to resolve type");
3307                        let ty = if let CatalogItem::Type(ty) = &allocated_type.item {
3308                            ty
3309                        } else {
3310                            panic!("unexpectedly not a type")
3311                        };
3312                        match ty.details.array_id {
3313                            Some(array_id) => {
3314                                let array_ty = catalog.get_entry(&array_id);
3315                                assert_eq!(
3316                                    pg_ty.array, array_ty.oid,
3317                                    "type {} has mismatched array OIDs",
3318                                    allocated_type.name.item,
3319                                );
3320                            }
3321                            None => assert_eq!(
3322                                pg_ty.array, 0,
3323                                "type {} does not have an array type in mz but does in pg",
3324                                allocated_type.name.item,
3325                            ),
3326                        }
3327                    }
3328                    Builtin::Func(func) => {
3329                        for imp in func.inner.func_impls() {
3330                            assert!(
3331                                all_oids.insert(imp.oid),
3332                                "{} reused oid {}",
3333                                func.name,
3334                                imp.oid
3335                            );
3336
3337                            assert!(
3338                                imp.oid < FIRST_USER_OID,
3339                                "built-in function {} erroneously has OID in user space ({})",
3340                                func.name,
3341                                imp.oid,
3342                            );
3343
3344                            // For functions that have a postgres counterpart, verify that the name and
3345                            // oid match.
3346                            let pg_fn = if imp.oid >= FIRST_UNPINNED_OID {
3347                                continue;
3348                            } else {
3349                                pg_proc.get(&imp.oid).unwrap_or_else(|| {
3350                                    panic!(
3351                                        "pg_proc missing function {}: oid {}",
3352                                        func.name, imp.oid
3353                                    )
3354                                })
3355                            };
3356                            assert_eq!(
3357                                func.name, pg_fn.name,
3358                                "funcs with oid {} don't match names: {} in mz, {} in pg",
3359                                imp.oid, func.name, pg_fn.name
3360                            );
3361
3362                            // Complain, but don't fail, if argument oids don't match.
3363                            // TODO: make these match.
3364                            let imp_arg_oids = imp
3365                                .arg_typs
3366                                .iter()
3367                                .map(|item| resolve_type_oid(item))
3368                                .collect::<Vec<_>>();
3369
3370                            if imp_arg_oids != pg_fn.arg_oids {
3371                                println!(
3372                                    "funcs with oid {} ({}) don't match arguments: {:?} in mz, {:?} in pg",
3373                                    imp.oid, func.name, imp_arg_oids, pg_fn.arg_oids
3374                                );
3375                            }
3376
3377                            let imp_return_oid = imp.return_typ.map(resolve_type_oid);
3378
3379                            assert!(
3380                                is_same_type(imp.oid, imp_return_oid, pg_fn.ret_oid),
3381                                "funcs with oid {} ({}) don't match return types: {:?} in mz, {:?} in pg",
3382                                imp.oid,
3383                                func.name,
3384                                imp_return_oid,
3385                                pg_fn.ret_oid
3386                            );
3387
3388                            assert_eq!(
3389                                imp.return_is_set, pg_fn.ret_set,
3390                                "funcs with oid {} ({}) don't match set-returning value: {:?} in mz, {:?} in pg",
3391                                imp.oid, func.name, imp.return_is_set, pg_fn.ret_set
3392                            );
3393                        }
3394                    }
3395                    _ => (),
3396                }
3397            }
3398
3399            for (op, func) in OP_IMPLS.iter() {
3400                for imp in func.func_impls() {
3401                    assert!(all_oids.insert(imp.oid), "{} reused oid {}", op, imp.oid);
3402
3403                    // For operators that have a postgres counterpart, verify that the name and oid match.
3404                    let pg_op = if imp.oid >= FIRST_UNPINNED_OID {
3405                        continue;
3406                    } else {
3407                        pg_oper.get(&imp.oid).unwrap_or_else(|| {
3408                            panic!("pg_operator missing operator {}: oid {}", op, imp.oid)
3409                        })
3410                    };
3411
3412                    assert_eq!(*op, pg_op.name);
3413
3414                    let imp_return_oid =
3415                        imp.return_typ.map(resolve_type_oid).expect("must have oid");
3416                    if imp_return_oid != pg_op.oprresult {
3417                        panic!(
3418                            "operators with oid {} ({}) don't match return typs: {} in mz, {} in pg",
3419                            imp.oid, op, imp_return_oid, pg_op.oprresult
3420                        );
3421                    }
3422                }
3423            }
3424            catalog.expire().await;
3425        }
3426
3427        Catalog::with_debug(inner).await
3428    }
3429
3430    // Execute all builtin functions with all combinations of arguments from interesting datums.
3431    #[mz_ore::test(tokio::test)]
3432    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3433    async fn test_smoketest_all_builtins() {
3434        fn inner(catalog: Catalog) -> Vec<mz_ore::task::JoinHandle<()>> {
3435            let catalog = Arc::new(catalog);
3436            let conn_catalog = catalog.for_system_session();
3437
3438            let resolve_type_oid = |item: &str| conn_catalog.state().get_system_type(item).oid();
3439            let mut handles = Vec::new();
3440
3441            // Extracted during planning; always panics when executed.
3442            let ignore_names = BTreeSet::from([
3443                "avg",
3444                "avg_internal_v1",
3445                "bool_and",
3446                "bool_or",
3447                "has_table_privilege", // > 3 s each
3448                "has_type_privilege",  // > 3 s each
3449                "mod",
3450                "mz_panic",
3451                "mz_sleep",
3452                "pow",
3453                "stddev_pop",
3454                "stddev_samp",
3455                "stddev",
3456                "var_pop",
3457                "var_samp",
3458                "variance",
3459            ]);
3460
3461            let fns = BUILTINS::funcs()
3462                .map(|func| (&func.name, func.inner))
3463                .chain(OP_IMPLS.iter());
3464
3465            for (name, func) in fns {
3466                if ignore_names.contains(name) {
3467                    continue;
3468                }
3469                let Func::Scalar(impls) = func else {
3470                    continue;
3471                };
3472
3473                'outer: for imp in impls {
3474                    let details = imp.details();
3475                    let mut styps = Vec::new();
3476                    for item in details.arg_typs.iter() {
3477                        let oid = resolve_type_oid(item);
3478                        let Ok(pgtyp) = mz_pgrepr::Type::from_oid(oid) else {
3479                            continue 'outer;
3480                        };
3481                        styps.push(SqlScalarType::try_from(&pgtyp).expect("must exist"));
3482                    }
3483                    let datums = styps
3484                        .iter()
3485                        .map(|styp| {
3486                            let mut datums = vec![Datum::Null];
3487                            datums.extend(styp.interesting_datums());
3488                            datums
3489                        })
3490                        .collect::<Vec<_>>();
3491                    // Skip nullary fns.
3492                    if datums.is_empty() {
3493                        continue;
3494                    }
3495
3496                    let return_oid = details
3497                        .return_typ
3498                        .map(resolve_type_oid)
3499                        .expect("must exist");
3500                    let return_styp = mz_pgrepr::Type::from_oid(return_oid)
3501                        .ok()
3502                        .map(|typ| SqlScalarType::try_from(&typ).expect("must exist"));
3503
3504                    let mut idxs = vec![0; datums.len()];
3505                    while idxs[0] < datums[0].len() {
3506                        let mut args = Vec::with_capacity(idxs.len());
3507                        for i in 0..(datums.len()) {
3508                            args.push(datums[i][idxs[i]]);
3509                        }
3510
3511                        let op = &imp.op;
3512                        let scalars = args
3513                            .iter()
3514                            .enumerate()
3515                            .map(|(i, datum)| {
3516                                CoercibleScalarExpr::Coerced(HirScalarExpr::literal(
3517                                    datum.clone(),
3518                                    styps[i].clone(),
3519                                ))
3520                            })
3521                            .collect();
3522
3523                        let call_name = format!(
3524                            "{name}({}) (oid: {})",
3525                            args.iter()
3526                                .map(|d| d.to_string())
3527                                .collect::<Vec<_>>()
3528                                .join(", "),
3529                            imp.oid
3530                        );
3531                        let catalog = Arc::clone(&catalog);
3532                        let call_name_fn = call_name.clone();
3533                        let return_styp = return_styp.clone();
3534                        let handle = task::spawn_blocking(
3535                            || call_name,
3536                            move || {
3537                                smoketest_fn(
3538                                    name,
3539                                    call_name_fn,
3540                                    op,
3541                                    imp,
3542                                    args,
3543                                    catalog,
3544                                    scalars,
3545                                    return_styp,
3546                                )
3547                            },
3548                        );
3549                        handles.push(handle);
3550
3551                        // Advance to the next datum combination.
3552                        for i in (0..datums.len()).rev() {
3553                            idxs[i] += 1;
3554                            if idxs[i] >= datums[i].len() {
3555                                if i == 0 {
3556                                    break;
3557                                }
3558                                idxs[i] = 0;
3559                                continue;
3560                            } else {
3561                                break;
3562                            }
3563                        }
3564                    }
3565                }
3566            }
3567            handles
3568        }
3569
3570        let handles = Catalog::with_debug(|catalog| async { inner(catalog) }).await;
3571        for handle in handles {
3572            handle.await;
3573        }
3574    }
3575
3576    fn smoketest_fn(
3577        name: &&str,
3578        call_name: String,
3579        op: &Operation<HirScalarExpr>,
3580        imp: &FuncImpl<HirScalarExpr>,
3581        args: Vec<Datum<'_>>,
3582        catalog: Arc<Catalog>,
3583        scalars: Vec<CoercibleScalarExpr>,
3584        return_styp: Option<SqlScalarType>,
3585    ) {
3586        let conn_catalog = catalog.for_system_session();
3587        let pcx = PlanContext::zero();
3588        let scx = StatementContext::new(Some(&pcx), &conn_catalog);
3589        let qcx = QueryContext::root(&scx, QueryLifetime::OneShot);
3590        let ecx = ExprContext {
3591            qcx: &qcx,
3592            name: "smoketest",
3593            scope: &Scope::empty(),
3594            relation_type: &SqlRelationType::empty(),
3595            allow_aggregates: false,
3596            allow_subqueries: false,
3597            allow_parameters: false,
3598            allow_windows: false,
3599        };
3600        let arena = RowArena::new();
3601        let mut session = Session::dummy();
3602        session
3603            .start_transaction(to_datetime(0), None, None)
3604            .expect("must succeed");
3605        let prep_style = ExprPrepOneShot {
3606            logical_time: EvalTime::Time(Timestamp::MIN),
3607            session: &session,
3608            catalog_state: &catalog.state,
3609        };
3610
3611        // Execute the function as much as possible, ensuring no panics occur, but
3612        // otherwise ignoring eval errors. We also do various other checks.
3613        let res = (op.0)(&ecx, scalars, &imp.params, vec![]);
3614        if let Ok(hir) = res {
3615            let uneliminated_result_row = {
3616                if let HirScalarExpr::CallUnary { func, .. } = &hir
3617                    && func.is_eliminable_cast()
3618                {
3619                    let mut uneliminated_mir = hir
3620                        .clone()
3621                        .lower_uncorrelated(HirToMirConfig {
3622                            enable_cast_elimination: false,
3623                            ..catalog.system_config().into()
3624                        })
3625                        .expect("lowering eliminable cast should always succeed");
3626                    prep_style
3627                        .prep_scalar_expr(&mut uneliminated_mir)
3628                        .expect("must succeed");
3629
3630                    // Pack the row, to avoid lifetime issues with the MIR we lowered here
3631                    uneliminated_mir
3632                        .eval(&[], &arena)
3633                        .ok()
3634                        .map(|datum| Row::pack([datum]))
3635                } else {
3636                    None
3637                }
3638            };
3639
3640            if let Ok(mut mir) = hir.lower_uncorrelated(catalog.system_config()) {
3641                // Populate unmaterialized functions.
3642                prep_style.prep_scalar_expr(&mut mir).expect("must succeed");
3643
3644                if let Ok(eval_result_datum) = mir.eval(&[], &arena) {
3645                    if let Some(return_styp) = return_styp {
3646                        let mir_typ = mir.typ(&[]);
3647                        // MIR type inference should be consistent with the type
3648                        // we get from the catalog.
3649                        soft_assert_eq_or_log!(
3650                            mir_typ.scalar_type,
3651                            (&return_styp).into(),
3652                            "MIR type did not match the catalog type (cast elimination/repr type error)"
3653                        );
3654                        // The following will check not just that the scalar type
3655                        // is ok, but also catches if the function returned a null
3656                        // but the MIR type inference said "non-nullable".
3657                        if !eval_result_datum.is_instance_of(&mir_typ) {
3658                            panic!(
3659                                "{call_name}: expected return type of {return_styp:?}, got {eval_result_datum}"
3660                            );
3661                        }
3662                        // Check the consistency of `is_eliminable_cast`---we should get the same datum either way.
3663                        if let Some(row) = uneliminated_result_row {
3664                            let uneliminated_result_datum = row.unpack_first();
3665                            assert_eq!(
3666                                uneliminated_result_datum, eval_result_datum,
3667                                "datums should not change if cast is eliminable"
3668                            );
3669                        }
3670                        // Check the consistency of `introduces_nulls` and
3671                        // `propagates_nulls` with `MirScalarExpr::typ`.
3672                        if let Some((introduces_nulls, propagates_nulls)) =
3673                            call_introduces_propagates_nulls(&mir)
3674                        {
3675                            if introduces_nulls {
3676                                // If the function introduces_nulls, then the return
3677                                // type should always be nullable, regardless of
3678                                // the nullability of the input types.
3679                                assert!(
3680                                    mir_typ.nullable,
3681                                    "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3682                                    name, args, mir, mir_typ.nullable
3683                                );
3684                            } else {
3685                                let any_input_null = args.iter().any(|arg| arg.is_null());
3686                                if !any_input_null {
3687                                    assert!(
3688                                        !mir_typ.nullable,
3689                                        "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3690                                        name, args, mir, mir_typ.nullable
3691                                    );
3692                                } else if propagates_nulls {
3693                                    // propagates_nulls means the optimizer short-circuits
3694                                    // all-null inputs, so the output must be nullable.
3695                                    assert!(
3696                                        mir_typ.nullable,
3697                                        "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3698                                        name, args, mir, mir_typ.nullable
3699                                    );
3700                                }
3701                                // When propagates_nulls is false, the output may still
3702                                // be nullable if a non-nullable parameter received a null
3703                                // input (per-position null rejection). The is_instance_of
3704                                // check above ensures type consistency.
3705                            }
3706                        }
3707                        // Check that `MirScalarExpr::reduce` yields the same result
3708                        // as the real evaluation.
3709                        let mut reduced = mir.clone();
3710                        reduced.reduce(&[]);
3711                        match reduced {
3712                            MirScalarExpr::Literal(reduce_result, ctyp) => {
3713                                match reduce_result {
3714                                    Ok(reduce_result_row) => {
3715                                        let reduce_result_datum = reduce_result_row.unpack_first();
3716                                        assert_eq!(
3717                                            reduce_result_datum,
3718                                            eval_result_datum,
3719                                            "eval/reduce datum mismatch: fn named `{}` called on args `{:?}` (lowered to `{}`) evaluated to `{}` with typ `{:?}`, but reduced to `{}` with typ `{:?}`",
3720                                            name,
3721                                            args,
3722                                            mir,
3723                                            eval_result_datum,
3724                                            mir_typ.scalar_type,
3725                                            reduce_result_datum,
3726                                            ctyp.scalar_type
3727                                        );
3728                                        // Let's check that the types also match.
3729                                        // (We are not checking nullability here,
3730                                        // because it's ok when we know a more
3731                                        // precise nullability after actually
3732                                        // evaluating a function than before.)
3733                                        assert_eq!(
3734                                            ctyp.scalar_type,
3735                                            mir_typ.scalar_type,
3736                                            "eval/reduce type mismatch: fn named `{}` called on args `{:?}` (lowered to `{}`) evaluated to `{}` with typ `{:?}`, but reduced to `{}` with typ `{:?}`",
3737                                            name,
3738                                            args,
3739                                            mir,
3740                                            eval_result_datum,
3741                                            mir_typ.scalar_type,
3742                                            reduce_result_datum,
3743                                            ctyp.scalar_type
3744                                        );
3745                                    }
3746                                    Err(..) => {} // It's ok, we might have given invalid args to the function
3747                                }
3748                            }
3749                            _ => unreachable!(
3750                                "all args are literals, so should have reduced to a literal"
3751                            ),
3752                        }
3753                    }
3754                }
3755            }
3756        }
3757    }
3758
3759    /// If the given MirScalarExpr
3760    ///  - is a function call, and
3761    ///  - all arguments are literals
3762    /// then it returns whether the called function (introduces_nulls, propagates_nulls).
3763    fn call_introduces_propagates_nulls(mir_func_call: &MirScalarExpr) -> Option<(bool, bool)> {
3764        match mir_func_call {
3765            MirScalarExpr::CallUnary { func, expr } => {
3766                if expr.is_literal() {
3767                    Some((func.introduces_nulls(), func.propagates_nulls()))
3768                } else {
3769                    None
3770                }
3771            }
3772            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
3773                if expr1.is_literal() && expr2.is_literal() {
3774                    Some((func.introduces_nulls(), func.propagates_nulls()))
3775                } else {
3776                    None
3777                }
3778            }
3779            MirScalarExpr::CallVariadic { func, exprs } => {
3780                if exprs.iter().all(|arg| arg.is_literal()) {
3781                    Some((func.introduces_nulls(), func.propagates_nulls()))
3782                } else {
3783                    None
3784                }
3785            }
3786            _ => None,
3787        }
3788    }
3789
3790    // Make sure pg views don't use types that only exist in Materialize.
3791    #[mz_ore::test(tokio::test)]
3792    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3793    async fn test_pg_views_forbidden_types() {
3794        Catalog::with_debug(|catalog| async move {
3795            let conn_catalog = catalog.for_system_session();
3796
3797            for view in BUILTINS::views().filter(|view| {
3798                view.schema == PG_CATALOG_SCHEMA || view.schema == INFORMATION_SCHEMA
3799            }) {
3800                let item = conn_catalog
3801                    .resolve_item(&PartialItemName {
3802                        database: None,
3803                        schema: Some(view.schema.to_string()),
3804                        item: view.name.to_string(),
3805                    })
3806                    .expect("unable to resolve view")
3807                    // TODO(alter_table)
3808                    .at_version(RelationVersionSelector::Latest);
3809                let full_name = conn_catalog.resolve_full_name(item.name());
3810                let desc = item.relation_desc().expect("invalid item type");
3811                for col_type in desc.iter_types() {
3812                    match &col_type.scalar_type {
3813                        typ @ SqlScalarType::UInt16
3814                        | typ @ SqlScalarType::UInt32
3815                        | typ @ SqlScalarType::UInt64
3816                        | typ @ SqlScalarType::MzTimestamp
3817                        | typ @ SqlScalarType::List { .. }
3818                        | typ @ SqlScalarType::Map { .. }
3819                        | typ @ SqlScalarType::MzAclItem => {
3820                            panic!("{typ:?} type found in {full_name}");
3821                        }
3822                        SqlScalarType::AclItem
3823                        | SqlScalarType::Bool
3824                        | SqlScalarType::Int16
3825                        | SqlScalarType::Int32
3826                        | SqlScalarType::Int64
3827                        | SqlScalarType::Float32
3828                        | SqlScalarType::Float64
3829                        | SqlScalarType::Numeric { .. }
3830                        | SqlScalarType::Date
3831                        | SqlScalarType::Time
3832                        | SqlScalarType::Timestamp { .. }
3833                        | SqlScalarType::TimestampTz { .. }
3834                        | SqlScalarType::Interval
3835                        | SqlScalarType::PgLegacyChar
3836                        | SqlScalarType::Bytes
3837                        | SqlScalarType::String
3838                        | SqlScalarType::Char { .. }
3839                        | SqlScalarType::VarChar { .. }
3840                        | SqlScalarType::Jsonb
3841                        | SqlScalarType::Uuid
3842                        | SqlScalarType::Array(_)
3843                        | SqlScalarType::Record { .. }
3844                        | SqlScalarType::Oid
3845                        | SqlScalarType::RegProc
3846                        | SqlScalarType::RegType
3847                        | SqlScalarType::RegClass
3848                        | SqlScalarType::Int2Vector
3849                        | SqlScalarType::Range { .. }
3850                        | SqlScalarType::PgLegacyName => {}
3851                    }
3852                }
3853            }
3854            catalog.expire().await;
3855        })
3856        .await
3857    }
3858
3859    // Make sure objects reside in the `mz_introspection` schema iff they depend on per-replica
3860    // introspection relations.
3861    #[mz_ore::test(tokio::test)]
3862    #[cfg_attr(miri, ignore)] // unsupported operation: returning ready events from epoll_wait is not yet implemented
3863    async fn test_mz_introspection_builtins() {
3864        Catalog::with_debug(|catalog| async move {
3865            let conn_catalog = catalog.for_system_session();
3866
3867            let introspection_schema_id = catalog.get_mz_introspection_schema_id();
3868            let introspection_schema_spec = SchemaSpecifier::Id(introspection_schema_id);
3869
3870            for entry in catalog.entries() {
3871                let schema_spec = entry.name().qualifiers.schema_spec;
3872                let introspection_deps = catalog.introspection_dependencies(entry.id);
3873                if introspection_deps.is_empty() {
3874                    assert!(
3875                        schema_spec != introspection_schema_spec,
3876                        "entry does not depend on introspection sources but is in \
3877                         `mz_introspection`: {}",
3878                        conn_catalog.resolve_full_name(entry.name()),
3879                    );
3880                } else {
3881                    assert!(
3882                        schema_spec == introspection_schema_spec,
3883                        "entry depends on introspection sources but is not in \
3884                         `mz_introspection`: {}",
3885                        conn_catalog.resolve_full_name(entry.name()),
3886                    );
3887                }
3888            }
3889        })
3890        .await
3891    }
3892
3893    #[mz_ore::test(tokio::test)]
3894    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3895    async fn test_multi_subscriber_catalog() {
3896        let persist_client = PersistClient::new_for_tests().await;
3897        let bootstrap_args = test_bootstrap_args();
3898        let organization_id = Uuid::new_v4();
3899        let db_name = "DB";
3900
3901        let mut writer_catalog = Catalog::open_debug_catalog(
3902            persist_client.clone(),
3903            organization_id.clone(),
3904            &bootstrap_args,
3905        )
3906        .await
3907        .expect("open_debug_catalog");
3908        let mut read_only_catalog = Catalog::open_debug_read_only_catalog(
3909            persist_client.clone(),
3910            organization_id.clone(),
3911            &bootstrap_args,
3912        )
3913        .await
3914        .expect("open_debug_read_only_catalog");
3915        assert_err!(writer_catalog.resolve_database(db_name));
3916        assert_err!(read_only_catalog.resolve_database(db_name));
3917
3918        let commit_ts = writer_catalog.current_upper().await;
3919        writer_catalog
3920            .transact(
3921                None,
3922                commit_ts,
3923                None,
3924                vec![Op::CreateDatabase {
3925                    name: db_name.to_string(),
3926                    owner_id: MZ_SYSTEM_ROLE_ID,
3927                }],
3928            )
3929            .await
3930            .expect("failed to transact");
3931
3932        let write_db = writer_catalog
3933            .resolve_database(db_name)
3934            .expect("resolve_database");
3935        read_only_catalog
3936            .sync_to_current_updates()
3937            .await
3938            .expect("sync_to_current_updates");
3939        let read_db = read_only_catalog
3940            .resolve_database(db_name)
3941            .expect("resolve_database");
3942
3943        assert_eq!(write_db, read_db);
3944
3945        let writer_catalog_fencer =
3946            Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
3947                .await
3948                .expect("open_debug_catalog for fencer");
3949        let fencer_db = writer_catalog_fencer
3950            .resolve_database(db_name)
3951            .expect("resolve_database for fencer");
3952        assert_eq!(fencer_db, read_db);
3953
3954        let write_fence_err = writer_catalog
3955            .sync_to_current_updates()
3956            .await
3957            .expect_err("sync_to_current_updates for fencer");
3958        assert!(matches!(
3959            write_fence_err,
3960            CatalogError::Durable(DurableCatalogError::Fence(FenceError::Epoch { .. }))
3961        ));
3962        let read_fence_err = read_only_catalog
3963            .sync_to_current_updates()
3964            .await
3965            .expect_err("sync_to_current_updates after fencer");
3966        assert!(matches!(
3967            read_fence_err,
3968            CatalogError::Durable(DurableCatalogError::Fence(FenceError::Epoch { .. }))
3969        ));
3970
3971        writer_catalog.expire().await;
3972        read_only_catalog.expire().await;
3973        writer_catalog_fencer.expire().await;
3974    }
3975}