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