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