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