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: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
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<&DataflowDescription<mz_compute_types::plan::LirRelationExpr>> {
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: DataflowDescription<mz_compute_types::plan::LirRelationExpr>,
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 tests {
2502    use std::collections::{BTreeMap, BTreeSet};
2503    use std::sync::Arc;
2504    use std::{env, iter};
2505
2506    use itertools::Itertools;
2507    use mz_catalog::memory::objects::CatalogItem;
2508    use mz_postgres_util::{query, sql};
2509    use tokio_postgres::NoTls;
2510    use tokio_postgres::types::Type;
2511    use uuid::Uuid;
2512
2513    use mz_catalog::SYSTEM_CONN_ID;
2514    use mz_catalog::builtin::{BUILTINS, Builtin, BuiltinType};
2515    use mz_catalog::durable::{CatalogError, DurableCatalogError, FenceError, test_bootstrap_args};
2516    use mz_controller_types::{ClusterId, ReplicaId};
2517    use mz_expr::{Eval, MirScalarExpr};
2518    use mz_ore::now::to_datetime;
2519    use mz_ore::{assert_err, assert_ok, soft_assert_eq_or_log, task};
2520    use mz_persist_client::PersistClient;
2521    use mz_pgrepr::oid::{FIRST_MATERIALIZE_OID, FIRST_UNPINNED_OID, FIRST_USER_OID};
2522    use mz_repr::namespaces::{INFORMATION_SCHEMA, PG_CATALOG_SCHEMA};
2523    use mz_repr::role_id::RoleId;
2524    use mz_repr::{
2525        CatalogItemId, Datum, GlobalId, RelationVersionSelector, Row, RowArena, SqlRelationType,
2526        SqlScalarType, Timestamp,
2527    };
2528    use mz_sql::catalog::{CatalogSchema, CatalogType, SessionCatalog};
2529    use mz_sql::func::{Func, FuncImpl, OP_IMPLS, Operation};
2530    use mz_sql::names::{
2531        self, DatabaseId, ItemQualifiers, ObjectId, PartialItemName, QualifiedItemName,
2532        ResolvedDatabaseSpecifier, SchemaId, SchemaSpecifier, SystemObjectId,
2533    };
2534    use mz_sql::plan::{
2535        CoercibleScalarExpr, ExprContext, HirScalarExpr, HirToMirConfig, PlanContext, QueryContext,
2536        QueryLifetime, Scope, StatementContext,
2537    };
2538    use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
2539    use mz_sql::session::vars::{SystemVars, VarInput};
2540
2541    use crate::catalog::state::LocalExpressionCache;
2542    use crate::catalog::{Catalog, DebugAwsContext, Op};
2543    use crate::optimize::dataflows::{EvalTime, ExprPrep, ExprPrepOneShot};
2544    use crate::session::Session;
2545
2546    /// System sessions have an empty `search_path` so it's necessary to
2547    /// schema-qualify all referenced items.
2548    ///
2549    /// Dummy (and ostensibly client) sessions contain system schemas in their
2550    /// search paths, so do not require schema qualification on system objects such
2551    /// as types.
2552    #[mz_ore::test(tokio::test)]
2553    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2554    async fn test_minimal_qualification() {
2555        Catalog::with_debug(|catalog| async move {
2556            struct TestCase {
2557                input: QualifiedItemName,
2558                system_output: PartialItemName,
2559                normal_output: PartialItemName,
2560            }
2561
2562            let test_cases = vec![
2563                TestCase {
2564                    input: QualifiedItemName {
2565                        qualifiers: ItemQualifiers {
2566                            database_spec: ResolvedDatabaseSpecifier::Ambient,
2567                            schema_spec: SchemaSpecifier::Id(catalog.get_pg_catalog_schema_id()),
2568                        },
2569                        item: "numeric".to_string(),
2570                    },
2571                    system_output: PartialItemName {
2572                        database: None,
2573                        schema: None,
2574                        item: "numeric".to_string(),
2575                    },
2576                    normal_output: PartialItemName {
2577                        database: None,
2578                        schema: None,
2579                        item: "numeric".to_string(),
2580                    },
2581                },
2582                TestCase {
2583                    input: QualifiedItemName {
2584                        qualifiers: ItemQualifiers {
2585                            database_spec: ResolvedDatabaseSpecifier::Ambient,
2586                            schema_spec: SchemaSpecifier::Id(catalog.get_mz_catalog_schema_id()),
2587                        },
2588                        item: "mz_array_types".to_string(),
2589                    },
2590                    system_output: PartialItemName {
2591                        database: None,
2592                        schema: None,
2593                        item: "mz_array_types".to_string(),
2594                    },
2595                    normal_output: PartialItemName {
2596                        database: None,
2597                        schema: None,
2598                        item: "mz_array_types".to_string(),
2599                    },
2600                },
2601            ];
2602
2603            for tc in test_cases {
2604                assert_eq!(
2605                    catalog
2606                        .for_system_session()
2607                        .minimal_qualification(&tc.input),
2608                    tc.system_output
2609                );
2610                assert_eq!(
2611                    catalog
2612                        .for_session(&Session::dummy())
2613                        .minimal_qualification(&tc.input),
2614                    tc.normal_output
2615                );
2616            }
2617            catalog.expire().await;
2618        })
2619        .await
2620    }
2621
2622    #[mz_ore::test(tokio::test)]
2623    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2624    async fn test_catalog_revision() {
2625        let persist_client = PersistClient::new_for_tests().await;
2626        let organization_id = Uuid::new_v4();
2627        let bootstrap_args = test_bootstrap_args();
2628        {
2629            let mut catalog = Catalog::open_debug_catalog(
2630                persist_client.clone(),
2631                organization_id.clone(),
2632                &bootstrap_args,
2633            )
2634            .await
2635            .expect("unable to open debug catalog");
2636            assert_eq!(catalog.transient_revision(), 1);
2637            assert!(catalog.transient_revision_is_current());
2638            let snapshot = catalog.clone();
2639            let commit_ts = catalog.current_upper().await;
2640            catalog
2641                .transact(
2642                    None,
2643                    commit_ts,
2644                    None,
2645                    vec![Op::CreateDatabase {
2646                        name: "test".to_string(),
2647                        owner_id: MZ_SYSTEM_ROLE_ID,
2648                    }],
2649                )
2650                .await
2651                .expect("failed to transact");
2652            assert_eq!(catalog.transient_revision(), 2);
2653            assert!(catalog.transient_revision_is_current());
2654            // The pre-transaction snapshot detects its own staleness through
2655            // the shared latest revision.
2656            assert!(!snapshot.transient_revision_is_current());
2657            catalog.expire().await;
2658        }
2659        {
2660            let catalog =
2661                Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
2662                    .await
2663                    .expect("unable to open debug catalog");
2664            // Re-opening the same catalog resets the transient_revision to 1.
2665            assert_eq!(catalog.transient_revision(), 1);
2666            catalog.expire().await;
2667        }
2668    }
2669
2670    #[mz_ore::test(tokio::test)]
2671    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2672    async fn test_effective_search_path() {
2673        Catalog::with_debug(|catalog| async move {
2674            let mz_catalog_schema = (
2675                ResolvedDatabaseSpecifier::Ambient,
2676                SchemaSpecifier::Id(catalog.state().get_mz_catalog_schema_id()),
2677            );
2678            let pg_catalog_schema = (
2679                ResolvedDatabaseSpecifier::Ambient,
2680                SchemaSpecifier::Id(catalog.state().get_pg_catalog_schema_id()),
2681            );
2682            let mz_temp_schema = (
2683                ResolvedDatabaseSpecifier::Ambient,
2684                SchemaSpecifier::Temporary,
2685            );
2686
2687            // Behavior with the default search_schema (public)
2688            let session = Session::dummy();
2689            let conn_catalog = catalog.for_session(&session);
2690            assert_ne!(
2691                conn_catalog.effective_search_path(false),
2692                conn_catalog.search_path
2693            );
2694            assert_ne!(
2695                conn_catalog.effective_search_path(true),
2696                conn_catalog.search_path
2697            );
2698            assert_eq!(
2699                conn_catalog.effective_search_path(false),
2700                vec![
2701                    mz_catalog_schema.clone(),
2702                    pg_catalog_schema.clone(),
2703                    conn_catalog.search_path[0].clone()
2704                ]
2705            );
2706            assert_eq!(
2707                conn_catalog.effective_search_path(true),
2708                vec![
2709                    mz_temp_schema.clone(),
2710                    mz_catalog_schema.clone(),
2711                    pg_catalog_schema.clone(),
2712                    conn_catalog.search_path[0].clone()
2713                ]
2714            );
2715
2716            // missing schemas are added when missing
2717            let mut session = Session::dummy();
2718            session
2719                .vars_mut()
2720                .set(
2721                    &SystemVars::new(),
2722                    "search_path",
2723                    VarInput::Flat(mz_repr::namespaces::PG_CATALOG_SCHEMA),
2724                    false,
2725                )
2726                .expect("failed to set search_path");
2727            let conn_catalog = catalog.for_session(&session);
2728            assert_ne!(
2729                conn_catalog.effective_search_path(false),
2730                conn_catalog.search_path
2731            );
2732            assert_ne!(
2733                conn_catalog.effective_search_path(true),
2734                conn_catalog.search_path
2735            );
2736            assert_eq!(
2737                conn_catalog.effective_search_path(false),
2738                vec![mz_catalog_schema.clone(), pg_catalog_schema.clone()]
2739            );
2740            assert_eq!(
2741                conn_catalog.effective_search_path(true),
2742                vec![
2743                    mz_temp_schema.clone(),
2744                    mz_catalog_schema.clone(),
2745                    pg_catalog_schema.clone()
2746                ]
2747            );
2748
2749            let mut session = Session::dummy();
2750            session
2751                .vars_mut()
2752                .set(
2753                    &SystemVars::new(),
2754                    "search_path",
2755                    VarInput::Flat(mz_repr::namespaces::MZ_CATALOG_SCHEMA),
2756                    false,
2757                )
2758                .expect("failed to set search_path");
2759            let conn_catalog = catalog.for_session(&session);
2760            assert_ne!(
2761                conn_catalog.effective_search_path(false),
2762                conn_catalog.search_path
2763            );
2764            assert_ne!(
2765                conn_catalog.effective_search_path(true),
2766                conn_catalog.search_path
2767            );
2768            assert_eq!(
2769                conn_catalog.effective_search_path(false),
2770                vec![pg_catalog_schema.clone(), mz_catalog_schema.clone()]
2771            );
2772            assert_eq!(
2773                conn_catalog.effective_search_path(true),
2774                vec![
2775                    mz_temp_schema.clone(),
2776                    pg_catalog_schema.clone(),
2777                    mz_catalog_schema.clone()
2778                ]
2779            );
2780
2781            let mut session = Session::dummy();
2782            session
2783                .vars_mut()
2784                .set(
2785                    &SystemVars::new(),
2786                    "search_path",
2787                    VarInput::Flat(mz_repr::namespaces::MZ_TEMP_SCHEMA),
2788                    false,
2789                )
2790                .expect("failed to set search_path");
2791            let conn_catalog = catalog.for_session(&session);
2792            assert_ne!(
2793                conn_catalog.effective_search_path(false),
2794                conn_catalog.search_path
2795            );
2796            assert_ne!(
2797                conn_catalog.effective_search_path(true),
2798                conn_catalog.search_path
2799            );
2800            // Because we lazily initialize the `mz_temp` schema,
2801            // an explicit `mz_temp` search path before the first
2802            // temporary item creation gets filtered out in
2803            // `effective_search_path`.
2804            assert_eq!(
2805                conn_catalog.effective_search_path(false),
2806                vec![mz_catalog_schema.clone(), pg_catalog_schema.clone(),]
2807            );
2808            assert_eq!(
2809                conn_catalog.effective_search_path(true),
2810                vec![mz_temp_schema, mz_catalog_schema, pg_catalog_schema]
2811            );
2812            catalog.expire().await;
2813        })
2814        .await
2815    }
2816
2817    #[mz_ore::test(tokio::test)]
2818    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2819    async fn test_normalized_create() {
2820        use mz_ore::collections::CollectionExt;
2821        Catalog::with_debug(|catalog| async move {
2822            let conn_catalog = catalog.for_system_session();
2823            let scx = &mut StatementContext::new(None, &conn_catalog);
2824
2825            let parsed = mz_sql_parser::parser::parse_statements(
2826                "create view public.foo as select 1 as bar",
2827            )
2828            .expect("")
2829            .into_element()
2830            .ast;
2831
2832            let (stmt, _) = names::resolve(scx.catalog, parsed).expect("");
2833
2834            // Ensure that all identifiers are quoted.
2835            assert_eq!(
2836                r#"CREATE VIEW "materialize"."public"."foo" AS SELECT 1 AS "bar""#,
2837                mz_sql::normalize::create_statement(scx, stmt).expect(""),
2838            );
2839            catalog.expire().await;
2840        })
2841        .await;
2842    }
2843
2844    /// Resolving a statement and resolving its normalized `create_sql` must
2845    /// yield the same `ResolvedIds`.
2846    ///
2847    /// The in-memory catalog records the ids from the first resolution, while
2848    /// a catalog reload re-derives them from the stored `create_sql`. Any
2849    /// disagreement makes the reloaded catalog differ from the in-memory one.
2850    #[mz_ore::test(tokio::test)]
2851    #[cfg_attr(miri, ignore)] // slow
2852    async fn test_resolved_ids_survive_create_sql_round_trip() {
2853        use mz_ore::collections::CollectionExt;
2854        Catalog::with_debug(|catalog| async move {
2855            let conn_catalog = catalog.for_system_session();
2856            let scx = &mut StatementContext::new(None, &conn_catalog);
2857
2858            let resolve_and_normalize = |scx: &mut StatementContext, sql: &str| {
2859                let parsed = mz_sql_parser::parser::parse_statements(sql)
2860                    .expect("parses")
2861                    .into_element()
2862                    .ast;
2863                let (stmt, ids) = names::resolve(scx.catalog, parsed).expect("resolves");
2864                let normalized =
2865                    mz_sql::normalize::create_statement(scx, stmt).expect("normalizes");
2866                (ids, normalized)
2867            };
2868
2869            const ARRAY_SUFFIX: &str = "create table public.t (a pg_catalog.int4[])";
2870            const ARRAY_TYPE: &str = "create table public.t (a pg_catalog._int4)";
2871            const ELEMENT_TYPE: &str = "create table public.t (a pg_catalog.int4)";
2872
2873            let mut ids_by_spelling: BTreeMap<&str, Vec<CatalogItemId>> = BTreeMap::new();
2874            for sql in [
2875                ARRAY_SUFFIX,
2876                ARRAY_TYPE,
2877                ELEMENT_TYPE,
2878                "create table public.t (a pg_catalog.int4 list)",
2879                "create view public.v as select null::pg_catalog.text[]",
2880            ] {
2881                let (ids, normalized) = resolve_and_normalize(scx, sql);
2882                let (round_tripped_ids, _) = resolve_and_normalize(scx, &normalized);
2883                assert_eq!(
2884                    ids.items().collect::<Vec<_>>(),
2885                    round_tripped_ids.items().collect::<Vec<_>>(),
2886                    "resolving {normalized:?} produced different ids than {sql:?}",
2887                );
2888                ids_by_spelling.insert(sql, ids.items().copied().collect());
2889            }
2890
2891            // `int4[]` and `_int4` name the same type, so both spellings must
2892            // record the same ids. `int4[]` is only ever stored as `_int4`,
2893            // so this is what keeps the reloaded catalog identical to the
2894            // in-memory one.
2895            assert_eq!(
2896                ids_by_spelling[ARRAY_SUFFIX], ids_by_spelling[ARRAY_TYPE],
2897                "{ARRAY_SUFFIX:?} and {ARRAY_TYPE:?} must resolve to the same ids",
2898            );
2899
2900            // Neither spelling records the element type: an array reference
2901            // names the array type alone.
2902            let element_ids = &ids_by_spelling[ELEMENT_TYPE];
2903            assert!(
2904                ids_by_spelling[ARRAY_SUFFIX]
2905                    .iter()
2906                    .all(|id| !element_ids.contains(id)),
2907                "{ARRAY_SUFFIX:?} recorded an element type id from {ELEMENT_TYPE:?}",
2908            );
2909
2910            catalog.expire().await;
2911        })
2912        .await;
2913    }
2914
2915    // Test that if a large catalog item is somehow committed, then we can still load the catalog.
2916    #[mz_ore::test(tokio::test)]
2917    #[cfg_attr(miri, ignore)] // slow
2918    async fn test_large_catalog_item() {
2919        let view_def = "CREATE VIEW \"materialize\".\"public\".\"v\" AS SELECT 1 FROM (SELECT 1";
2920        let column = ", 1";
2921        let view_def_size = view_def.bytes().count();
2922        let column_size = column.bytes().count();
2923        let column_count =
2924            (mz_sql_parser::parser::MAX_STATEMENT_BATCH_SIZE - view_def_size) / column_size + 1;
2925        let columns = iter::repeat(column).take(column_count).join("");
2926        let create_sql = format!("{view_def}{columns})");
2927        let create_sql_check = create_sql.clone();
2928        assert_ok!(mz_sql_parser::parser::parse_statements(&create_sql));
2929        assert_err!(mz_sql_parser::parser::parse_statements_with_limit(
2930            &create_sql
2931        ));
2932
2933        let persist_client = PersistClient::new_for_tests().await;
2934        let organization_id = Uuid::new_v4();
2935        let id = CatalogItemId::User(1);
2936        let gid = GlobalId::User(1);
2937        let bootstrap_args = test_bootstrap_args();
2938        {
2939            let mut catalog = Catalog::open_debug_catalog(
2940                persist_client.clone(),
2941                organization_id.clone(),
2942                &bootstrap_args,
2943            )
2944            .await
2945            .expect("unable to open debug catalog");
2946            let item = catalog
2947                .state()
2948                .deserialize_item(
2949                    gid,
2950                    &create_sql,
2951                    &BTreeMap::new(),
2952                    &mut LocalExpressionCache::Closed,
2953                    None,
2954                )
2955                .expect("unable to parse view");
2956            let commit_ts = catalog.current_upper().await;
2957            catalog
2958                .transact(
2959                    None,
2960                    commit_ts,
2961                    None,
2962                    vec![Op::CreateItem {
2963                        item,
2964                        name: QualifiedItemName {
2965                            qualifiers: ItemQualifiers {
2966                                database_spec: ResolvedDatabaseSpecifier::Id(DatabaseId::User(1)),
2967                                schema_spec: SchemaSpecifier::Id(SchemaId::User(3)),
2968                            },
2969                            item: "v".to_string(),
2970                        },
2971                        id,
2972                        owner_id: MZ_SYSTEM_ROLE_ID,
2973                    }],
2974                )
2975                .await
2976                .expect("failed to transact");
2977            catalog.expire().await;
2978        }
2979        {
2980            let catalog =
2981                Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
2982                    .await
2983                    .expect("unable to open debug catalog");
2984            let view = catalog.get_entry(&id);
2985            assert_eq!("v", view.name.item);
2986            match &view.item {
2987                CatalogItem::View(view) => assert_eq!(create_sql_check, view.create_sql),
2988                item => panic!("expected view, got {}", item.typ()),
2989            }
2990            catalog.expire().await;
2991        }
2992    }
2993
2994    #[mz_ore::test(tokio::test)]
2995    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
2996    async fn test_object_type() {
2997        Catalog::with_debug(|catalog| async move {
2998            let conn_catalog = catalog.for_system_session();
2999
3000            assert_eq!(
3001                mz_sql::catalog::ObjectType::ClusterReplica,
3002                conn_catalog.get_object_type(&ObjectId::ClusterReplica((
3003                    ClusterId::user(1).expect("1 is a valid ID"),
3004                    ReplicaId::User(1)
3005                )))
3006            );
3007            assert_eq!(
3008                mz_sql::catalog::ObjectType::Role,
3009                conn_catalog.get_object_type(&ObjectId::Role(RoleId::User(1)))
3010            );
3011            catalog.expire().await;
3012        })
3013        .await;
3014    }
3015
3016    #[mz_ore::test(tokio::test)]
3017    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3018    async fn test_get_privileges() {
3019        Catalog::with_debug(|catalog| async move {
3020            let conn_catalog = catalog.for_system_session();
3021
3022            assert_eq!(
3023                None,
3024                conn_catalog.get_privileges(&SystemObjectId::Object(ObjectId::ClusterReplica((
3025                    ClusterId::user(1).expect("1 is a valid ID"),
3026                    ReplicaId::User(1),
3027                ))))
3028            );
3029            assert_eq!(
3030                None,
3031                conn_catalog
3032                    .get_privileges(&SystemObjectId::Object(ObjectId::Role(RoleId::User(1))))
3033            );
3034            catalog.expire().await;
3035        })
3036        .await;
3037    }
3038
3039    #[mz_ore::test(tokio::test)]
3040    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3041    async fn verify_builtin_descs() {
3042        // Provide a full AWS context so the builtin AWS connection views resolve
3043        // as they do in a cloud environment. Without an account id,
3044        // `mz_aws_privatelink_connections.principal` folds to NULL, its
3045        // `WHERE principal IS NOT NULL` filter is statically unsatisfiable, and
3046        // the optimizer infers an extra empty key that the declared desc, which
3047        // describes the cloud shape, does not carry.
3048        let aws_context = DebugAwsContext {
3049            aws_account_id: Some("123456789000".to_string()),
3050            aws_external_id_prefix: Some("eb5cb59b-e2fe-41f3-87ca-d2176a495345".to_string()),
3051            aws_connection_role_arn: Some(
3052                "arn:aws:iam::123456789000:role/MaterializeConnection".to_string(),
3053            ),
3054        };
3055        Catalog::with_debug_aws_context(aws_context, |catalog| async move {
3056            let conn_catalog = catalog.for_system_session();
3057
3058            for builtin in BUILTINS::iter() {
3059                let (schema, name, expected_desc) = match builtin {
3060                    Builtin::Table(t) => (&t.schema, &t.name, &t.desc),
3061                    Builtin::View(v) => (&v.schema, &v.name, &v.desc),
3062                    Builtin::MaterializedView(mv) => (&mv.schema, &mv.name, &mv.desc),
3063                    Builtin::Source(s) => (&s.schema, &s.name, &s.desc),
3064                    Builtin::Log(_)
3065                    | Builtin::Type(_)
3066                    | Builtin::Func(_)
3067                    | Builtin::Index(_)
3068                    | Builtin::Connection(_) => continue,
3069                };
3070                let item = conn_catalog
3071                    .resolve_item(&PartialItemName {
3072                        database: None,
3073                        schema: Some(schema.to_string()),
3074                        item: name.to_string(),
3075                    })
3076                    .expect("unable to resolve item")
3077                    .at_version(RelationVersionSelector::Latest);
3078
3079                let actual_desc = item.relation_desc().expect("invalid item type");
3080                for (index, ((actual_name, actual_typ), (expected_name, expected_typ))) in
3081                    actual_desc.iter().zip_eq(expected_desc.iter()).enumerate()
3082                {
3083                    assert_eq!(
3084                        actual_name, expected_name,
3085                        "item {schema}.{name} column {index} name did not match its expected name"
3086                    );
3087                    assert_eq!(
3088                        actual_typ, expected_typ,
3089                        "item {schema}.{name} column {index} ('{actual_name}') type did not match its expected type"
3090                    );
3091                }
3092                assert_eq!(
3093                    &*actual_desc, expected_desc,
3094                    "item {schema}.{name} did not match its expected RelationDesc"
3095                );
3096            }
3097            catalog.expire().await;
3098        })
3099        .await
3100    }
3101
3102    // Connect to a running Postgres server and verify that our builtin
3103    // types and functions match it, in addition to some other things.
3104    #[mz_ore::test(tokio::test)]
3105    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3106    async fn test_compare_builtins_postgres() {
3107        async fn inner(catalog: Catalog) {
3108            // Verify that all builtin functions:
3109            // - have a unique OID
3110            // - if they have a postgres counterpart (same oid) then they have matching name
3111            let (client, connection) = tokio_postgres::connect(
3112                &env::var("POSTGRES_URL").unwrap_or_else(|_| "host=localhost user=postgres".into()),
3113                NoTls,
3114            )
3115            .await
3116            .expect("failed to connect to Postgres");
3117
3118            task::spawn(|| "compare_builtin_postgres", async move {
3119                if let Err(e) = connection.await {
3120                    panic!("connection error: {}", e);
3121                }
3122            });
3123
3124            struct PgProc {
3125                name: String,
3126                arg_oids: Vec<u32>,
3127                ret_oid: Option<u32>,
3128                ret_set: bool,
3129            }
3130
3131            struct PgType {
3132                name: String,
3133                ty: String,
3134                elem: u32,
3135                array: u32,
3136                input: u32,
3137                receive: u32,
3138                send: u32,
3139            }
3140
3141            struct PgOper {
3142                oprresult: u32,
3143                name: String,
3144            }
3145
3146            let pg_proc: BTreeMap<_, _> = query(
3147                &client,
3148                sql!(
3149                    "SELECT
3150                    p.oid,
3151                    proname,
3152                    proargtypes,
3153                    prorettype,
3154                    proretset
3155                FROM pg_proc p
3156                JOIN pg_namespace n ON p.pronamespace = n.oid"
3157                ),
3158                &[],
3159            )
3160            .await
3161            .expect("pg query failed")
3162            .into_iter()
3163            .map(|row| {
3164                let oid: u32 = row.get("oid");
3165                let pg_proc = PgProc {
3166                    name: row.get("proname"),
3167                    arg_oids: row.get("proargtypes"),
3168                    ret_oid: row.get("prorettype"),
3169                    ret_set: row.get("proretset"),
3170                };
3171                (oid, pg_proc)
3172            })
3173            .collect();
3174
3175            let pg_type: BTreeMap<_, _> = query(
3176                &client,
3177                sql!(
3178                    "SELECT oid, typname, typtype::text, typelem, typarray, typinput::oid, typreceive::oid as typreceive, typsend::oid as typsend FROM pg_type"
3179                ),
3180                &[],
3181            )
3182            .await
3183                .expect("pg query failed")
3184                .into_iter()
3185                .map(|row| {
3186                    let oid: u32 = row.get("oid");
3187                    let pg_type = PgType {
3188                        name: row.get("typname"),
3189                        ty: row.get("typtype"),
3190                        elem: row.get("typelem"),
3191                        array: row.get("typarray"),
3192                        input: row.get("typinput"),
3193                        receive: row.get("typreceive"),
3194                        send: row.get("typsend"),
3195                    };
3196                    (oid, pg_type)
3197                })
3198                .collect();
3199
3200            let pg_oper: BTreeMap<_, _> = query(
3201                &client,
3202                sql!("SELECT oid, oprname, oprresult FROM pg_operator"),
3203                &[],
3204            )
3205            .await
3206            .expect("pg query failed")
3207            .into_iter()
3208            .map(|row| {
3209                let oid: u32 = row.get("oid");
3210                let pg_oper = PgOper {
3211                    name: row.get("oprname"),
3212                    oprresult: row.get("oprresult"),
3213                };
3214                (oid, pg_oper)
3215            })
3216            .collect();
3217
3218            let conn_catalog = catalog.for_system_session();
3219            let resolve_type_oid = |item: &str| {
3220                conn_catalog
3221                    .resolve_type(&PartialItemName {
3222                        database: None,
3223                        // All functions we check exist in PG, so the types must, as
3224                        // well
3225                        schema: Some(PG_CATALOG_SCHEMA.into()),
3226                        item: item.to_string(),
3227                    })
3228                    .expect("unable to resolve type")
3229                    .oid()
3230            };
3231
3232            let func_oids: BTreeSet<_> = BUILTINS::funcs()
3233                .flat_map(|f| f.inner.func_impls().into_iter().map(|f| f.oid))
3234                .collect();
3235
3236            let mut all_oids = BTreeSet::new();
3237
3238            // A function to determine if two oids are equivalent enough for these tests. We don't
3239            // support some types, so map exceptions here.
3240            let equivalent_types: BTreeSet<(Option<u32>, Option<u32>)> = BTreeSet::from_iter(
3241                [
3242                    // We don't support NAME.
3243                    (Type::NAME, Type::TEXT),
3244                    (Type::NAME_ARRAY, Type::TEXT_ARRAY),
3245                    // We don't support time with time zone.
3246                    (Type::TIME, Type::TIMETZ),
3247                    (Type::TIME_ARRAY, Type::TIMETZ_ARRAY),
3248                ]
3249                .map(|(a, b)| (Some(a.oid()), Some(b.oid()))),
3250            );
3251            let ignore_return_types: BTreeSet<u32> = BTreeSet::from([
3252                1619, // pg_typeof: TODO: We now have regtype and can correctly implement this.
3253            ]);
3254            let is_same_type = |fn_oid: u32, a: Option<u32>, b: Option<u32>| -> bool {
3255                if ignore_return_types.contains(&fn_oid) {
3256                    return true;
3257                }
3258                if equivalent_types.contains(&(a, b)) || equivalent_types.contains(&(b, a)) {
3259                    return true;
3260                }
3261                a == b
3262            };
3263
3264            for builtin in BUILTINS::iter() {
3265                match builtin {
3266                    Builtin::Type(ty) => {
3267                        assert!(all_oids.insert(ty.oid), "{} reused oid {}", ty.name, ty.oid);
3268
3269                        if ty.oid >= FIRST_MATERIALIZE_OID {
3270                            // High OIDs are reserved in Materialize and don't have
3271                            // PostgreSQL counterparts.
3272                            continue;
3273                        }
3274
3275                        // For types that have a PostgreSQL counterpart, verify that
3276                        // the name and oid match.
3277                        let pg_ty = pg_type.get(&ty.oid).unwrap_or_else(|| {
3278                            panic!("pg_proc missing type {}: oid {}", ty.name, ty.oid)
3279                        });
3280                        assert_eq!(
3281                            ty.name, pg_ty.name,
3282                            "oid {} has name {} in postgres; expected {}",
3283                            ty.oid, pg_ty.name, ty.name,
3284                        );
3285
3286                        let (typinput_oid, typreceive_oid, typsend_oid) =
3287                            match &ty.details.pg_metadata {
3288                                None => (0, 0, 0),
3289                                Some(pgmeta) => (
3290                                    pgmeta.typinput_oid,
3291                                    pgmeta.typreceive_oid,
3292                                    pgmeta.typsend_oid,
3293                                ),
3294                            };
3295                        assert_eq!(
3296                            typinput_oid, pg_ty.input,
3297                            "type {} has typinput OID {:?} in mz but {:?} in pg",
3298                            ty.name, typinput_oid, pg_ty.input,
3299                        );
3300                        assert_eq!(
3301                            typreceive_oid, pg_ty.receive,
3302                            "type {} has typreceive OID {:?} in mz but {:?} in pg",
3303                            ty.name, typreceive_oid, pg_ty.receive,
3304                        );
3305                        // Unlike typinput and typreceive below, typsend is not also
3306                        // checked against `func_oids`. Nothing resolves a typsend OID
3307                        // to a name, so the corresponding `*send` functions are
3308                        // deliberately not registered as builtins.
3309                        assert_eq!(
3310                            typsend_oid, pg_ty.send,
3311                            "type {} has typsend OID {:?} in mz but {:?} in pg",
3312                            ty.name, typsend_oid, pg_ty.send,
3313                        );
3314                        if typinput_oid != 0 {
3315                            assert!(
3316                                func_oids.contains(&typinput_oid),
3317                                "type {} has typinput OID {} that does not exist in pg_proc",
3318                                ty.name,
3319                                typinput_oid,
3320                            );
3321                        }
3322                        if typreceive_oid != 0 {
3323                            assert!(
3324                                func_oids.contains(&typreceive_oid),
3325                                "type {} has typreceive OID {} that does not exist in pg_proc",
3326                                ty.name,
3327                                typreceive_oid,
3328                            );
3329                        }
3330
3331                        // Ensure the type matches.
3332                        match &ty.details.typ {
3333                            CatalogType::Array { element_reference } => {
3334                                let elem_ty = BUILTINS::iter()
3335                                    .filter_map(|builtin| match builtin {
3336                                        Builtin::Type(ty @ BuiltinType { name, .. })
3337                                            if element_reference == name =>
3338                                        {
3339                                            Some(ty)
3340                                        }
3341                                        _ => None,
3342                                    })
3343                                    .next();
3344                                let elem_ty = match elem_ty {
3345                                    Some(ty) => ty,
3346                                    None => {
3347                                        panic!("{} is unexpectedly not a type", element_reference)
3348                                    }
3349                                };
3350                                assert_eq!(
3351                                    pg_ty.elem, elem_ty.oid,
3352                                    "type {} has mismatched element OIDs",
3353                                    ty.name
3354                                )
3355                            }
3356                            CatalogType::Pseudo => {
3357                                assert_eq!(
3358                                    pg_ty.ty, "p",
3359                                    "type {} is not a pseudo type as expected",
3360                                    ty.name
3361                                )
3362                            }
3363                            CatalogType::Range { .. } => {
3364                                assert_eq!(
3365                                    pg_ty.ty, "r",
3366                                    "type {} is not a range type as expected",
3367                                    ty.name
3368                                );
3369                            }
3370                            _ => {
3371                                assert_eq!(
3372                                    pg_ty.ty, "b",
3373                                    "type {} is not a base type as expected",
3374                                    ty.name
3375                                )
3376                            }
3377                        }
3378
3379                        // Ensure the array type reference is correct.
3380                        let schema = catalog
3381                            .resolve_schema_in_database(
3382                                &ResolvedDatabaseSpecifier::Ambient,
3383                                ty.schema,
3384                                &SYSTEM_CONN_ID,
3385                            )
3386                            .expect("unable to resolve schema");
3387                        let allocated_type = catalog
3388                            .resolve_type(
3389                                None,
3390                                &vec![(ResolvedDatabaseSpecifier::Ambient, schema.id().clone())],
3391                                &PartialItemName {
3392                                    database: None,
3393                                    schema: Some(schema.name().schema.clone()),
3394                                    item: ty.name.to_string(),
3395                                },
3396                                &SYSTEM_CONN_ID,
3397                            )
3398                            .expect("unable to resolve type");
3399                        let ty = if let CatalogItem::Type(ty) = &allocated_type.item {
3400                            ty
3401                        } else {
3402                            panic!("unexpectedly not a type")
3403                        };
3404                        match ty.details.array_id {
3405                            Some(array_id) => {
3406                                let array_ty = catalog.get_entry(&array_id);
3407                                assert_eq!(
3408                                    pg_ty.array, array_ty.oid,
3409                                    "type {} has mismatched array OIDs",
3410                                    allocated_type.name.item,
3411                                );
3412                            }
3413                            None => assert_eq!(
3414                                pg_ty.array, 0,
3415                                "type {} does not have an array type in mz but does in pg",
3416                                allocated_type.name.item,
3417                            ),
3418                        }
3419                    }
3420                    Builtin::Func(func) => {
3421                        for imp in func.inner.func_impls() {
3422                            assert!(
3423                                all_oids.insert(imp.oid),
3424                                "{} reused oid {}",
3425                                func.name,
3426                                imp.oid
3427                            );
3428
3429                            assert!(
3430                                imp.oid < FIRST_USER_OID,
3431                                "built-in function {} erroneously has OID in user space ({})",
3432                                func.name,
3433                                imp.oid,
3434                            );
3435
3436                            // For functions that have a postgres counterpart, verify that the name and
3437                            // oid match.
3438                            let pg_fn = if imp.oid >= FIRST_UNPINNED_OID {
3439                                continue;
3440                            } else {
3441                                pg_proc.get(&imp.oid).unwrap_or_else(|| {
3442                                    panic!(
3443                                        "pg_proc missing function {}: oid {}",
3444                                        func.name, imp.oid
3445                                    )
3446                                })
3447                            };
3448                            assert_eq!(
3449                                func.name, pg_fn.name,
3450                                "funcs with oid {} don't match names: {} in mz, {} in pg",
3451                                imp.oid, func.name, pg_fn.name
3452                            );
3453
3454                            // Complain, but don't fail, if argument oids don't match.
3455                            // TODO: make these match.
3456                            let imp_arg_oids = imp
3457                                .arg_typs
3458                                .iter()
3459                                .map(|item| resolve_type_oid(item))
3460                                .collect::<Vec<_>>();
3461
3462                            if imp_arg_oids != pg_fn.arg_oids {
3463                                println!(
3464                                    "funcs with oid {} ({}) don't match arguments: {:?} in mz, {:?} in pg",
3465                                    imp.oid, func.name, imp_arg_oids, pg_fn.arg_oids
3466                                );
3467                            }
3468
3469                            let imp_return_oid = imp.return_typ.map(resolve_type_oid);
3470
3471                            assert!(
3472                                is_same_type(imp.oid, imp_return_oid, pg_fn.ret_oid),
3473                                "funcs with oid {} ({}) don't match return types: {:?} in mz, {:?} in pg",
3474                                imp.oid,
3475                                func.name,
3476                                imp_return_oid,
3477                                pg_fn.ret_oid
3478                            );
3479
3480                            assert_eq!(
3481                                imp.return_is_set, pg_fn.ret_set,
3482                                "funcs with oid {} ({}) don't match set-returning value: {:?} in mz, {:?} in pg",
3483                                imp.oid, func.name, imp.return_is_set, pg_fn.ret_set
3484                            );
3485                        }
3486                    }
3487                    _ => (),
3488                }
3489            }
3490
3491            for (op, func) in OP_IMPLS.iter() {
3492                for imp in func.func_impls() {
3493                    assert!(all_oids.insert(imp.oid), "{} reused oid {}", op, imp.oid);
3494
3495                    // For operators that have a postgres counterpart, verify that the name and oid match.
3496                    let pg_op = if imp.oid >= FIRST_UNPINNED_OID {
3497                        continue;
3498                    } else {
3499                        pg_oper.get(&imp.oid).unwrap_or_else(|| {
3500                            panic!("pg_operator missing operator {}: oid {}", op, imp.oid)
3501                        })
3502                    };
3503
3504                    assert_eq!(*op, pg_op.name);
3505
3506                    let imp_return_oid =
3507                        imp.return_typ.map(resolve_type_oid).expect("must have oid");
3508                    if imp_return_oid != pg_op.oprresult {
3509                        panic!(
3510                            "operators with oid {} ({}) don't match return typs: {} in mz, {} in pg",
3511                            imp.oid, op, imp_return_oid, pg_op.oprresult
3512                        );
3513                    }
3514                }
3515            }
3516            catalog.expire().await;
3517        }
3518
3519        Catalog::with_debug(inner).await
3520    }
3521
3522    // Execute all builtin functions with all combinations of arguments from interesting datums.
3523    #[mz_ore::test(tokio::test)]
3524    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3525    async fn test_smoketest_all_builtins() {
3526        fn inner(catalog: Catalog) -> Vec<mz_ore::task::JoinHandle<()>> {
3527            let catalog = Arc::new(catalog);
3528            let conn_catalog = catalog.for_system_session();
3529
3530            let resolve_type_oid = |item: &str| conn_catalog.state().get_system_type(item).oid();
3531            let mut handles = Vec::new();
3532
3533            // Extracted during planning; always panics when executed.
3534            let ignore_names = BTreeSet::from([
3535                "avg",
3536                "avg_internal_v1",
3537                "bool_and",
3538                "bool_or",
3539                "has_table_privilege", // > 3 s each
3540                "has_type_privilege",  // > 3 s each
3541                "mod",
3542                "mz_panic",
3543                "mz_sleep",
3544                "pow",
3545                "stddev_pop",
3546                "stddev_samp",
3547                "stddev",
3548                "var_pop",
3549                "var_samp",
3550                "variance",
3551            ]);
3552
3553            let fns = BUILTINS::funcs()
3554                .map(|func| (&func.name, func.inner))
3555                .chain(OP_IMPLS.iter());
3556
3557            for (name, func) in fns {
3558                if ignore_names.contains(name) {
3559                    continue;
3560                }
3561                let Func::Scalar(impls) = func else {
3562                    continue;
3563                };
3564
3565                'outer: for imp in impls {
3566                    let details = imp.details();
3567                    let mut styps = Vec::new();
3568                    for item in details.arg_typs.iter() {
3569                        let oid = resolve_type_oid(item);
3570                        let Ok(pgtyp) = mz_pgrepr::Type::from_oid(oid) else {
3571                            continue 'outer;
3572                        };
3573                        styps.push(SqlScalarType::try_from(&pgtyp).expect("must exist"));
3574                    }
3575                    let datums = styps
3576                        .iter()
3577                        .map(|styp| {
3578                            let mut datums = vec![Datum::Null];
3579                            datums.extend(styp.interesting_datums());
3580                            datums
3581                        })
3582                        .collect::<Vec<_>>();
3583                    // Skip nullary fns.
3584                    if datums.is_empty() {
3585                        continue;
3586                    }
3587
3588                    let return_oid = details
3589                        .return_typ
3590                        .map(resolve_type_oid)
3591                        .expect("must exist");
3592                    let return_styp = mz_pgrepr::Type::from_oid(return_oid)
3593                        .ok()
3594                        .map(|typ| SqlScalarType::try_from(&typ).expect("must exist"));
3595
3596                    let mut idxs = vec![0; datums.len()];
3597                    while idxs[0] < datums[0].len() {
3598                        let mut args = Vec::with_capacity(idxs.len());
3599                        for i in 0..(datums.len()) {
3600                            args.push(datums[i][idxs[i]]);
3601                        }
3602
3603                        let op = &imp.op;
3604                        let scalars = args
3605                            .iter()
3606                            .enumerate()
3607                            .map(|(i, datum)| {
3608                                CoercibleScalarExpr::Coerced(HirScalarExpr::literal(
3609                                    datum.clone(),
3610                                    styps[i].clone(),
3611                                ))
3612                            })
3613                            .collect();
3614
3615                        let call_name = format!(
3616                            "{name}({}) (oid: {})",
3617                            args.iter()
3618                                .map(|d| d.to_string())
3619                                .collect::<Vec<_>>()
3620                                .join(", "),
3621                            imp.oid
3622                        );
3623                        let catalog = Arc::clone(&catalog);
3624                        let call_name_fn = call_name.clone();
3625                        let return_styp = return_styp.clone();
3626                        let handle = task::spawn_blocking(
3627                            || call_name,
3628                            move || {
3629                                smoketest_fn(
3630                                    name,
3631                                    call_name_fn,
3632                                    op,
3633                                    imp,
3634                                    args,
3635                                    catalog,
3636                                    scalars,
3637                                    return_styp,
3638                                )
3639                            },
3640                        );
3641                        handles.push(handle);
3642
3643                        // Advance to the next datum combination.
3644                        for i in (0..datums.len()).rev() {
3645                            idxs[i] += 1;
3646                            if idxs[i] >= datums[i].len() {
3647                                if i == 0 {
3648                                    break;
3649                                }
3650                                idxs[i] = 0;
3651                                continue;
3652                            } else {
3653                                break;
3654                            }
3655                        }
3656                    }
3657                }
3658            }
3659            handles
3660        }
3661
3662        let handles = Catalog::with_debug(|catalog| async { inner(catalog) }).await;
3663        for handle in handles {
3664            handle.await;
3665        }
3666    }
3667
3668    fn smoketest_fn(
3669        name: &&str,
3670        call_name: String,
3671        op: &Operation<HirScalarExpr>,
3672        imp: &FuncImpl<HirScalarExpr>,
3673        args: Vec<Datum<'_>>,
3674        catalog: Arc<Catalog>,
3675        scalars: Vec<CoercibleScalarExpr>,
3676        return_styp: Option<SqlScalarType>,
3677    ) {
3678        let conn_catalog = catalog.for_system_session();
3679        let pcx = PlanContext::zero();
3680        let scx = StatementContext::new(Some(&pcx), &conn_catalog);
3681        let qcx = QueryContext::root(&scx, QueryLifetime::OneShot);
3682        let ecx = ExprContext {
3683            qcx: &qcx,
3684            name: "smoketest",
3685            scope: &Scope::empty(),
3686            relation_type: &SqlRelationType::empty(),
3687            allow_aggregates: false,
3688            allow_subqueries: false,
3689            allow_parameters: false,
3690            allow_windows: false,
3691        };
3692        let arena = RowArena::new();
3693        let mut session = Session::dummy();
3694        session
3695            .start_transaction(to_datetime(0), None, None)
3696            .expect("must succeed");
3697        let prep_style = ExprPrepOneShot {
3698            logical_time: EvalTime::Time(Timestamp::MIN),
3699            session: &session,
3700            catalog_state: &catalog.state,
3701        };
3702
3703        // Execute the function as much as possible, ensuring no panics occur, but
3704        // otherwise ignoring eval errors. We also do various other checks.
3705        let res = (op.0)(&ecx, scalars, &imp.params, vec![]);
3706        if let Ok(hir) = res {
3707            let uneliminated_result_row = {
3708                if let HirScalarExpr::CallUnary { func, .. } = &hir
3709                    && func.is_eliminable_cast()
3710                {
3711                    let mut uneliminated_mir = hir
3712                        .clone()
3713                        .lower_uncorrelated(HirToMirConfig {
3714                            enable_cast_elimination: false,
3715                            ..catalog.system_config().into()
3716                        })
3717                        .expect("lowering eliminable cast should always succeed");
3718                    prep_style
3719                        .prep_scalar_expr(&mut uneliminated_mir)
3720                        .expect("must succeed");
3721
3722                    // Pack the row, to avoid lifetime issues with the MIR we lowered here
3723                    uneliminated_mir
3724                        .eval(&[], &arena)
3725                        .ok()
3726                        .map(|datum| Row::pack([datum]))
3727                } else {
3728                    None
3729                }
3730            };
3731
3732            if let Ok(mut mir) = hir.lower_uncorrelated(catalog.system_config()) {
3733                // Populate unmaterialized functions.
3734                prep_style.prep_scalar_expr(&mut mir).expect("must succeed");
3735
3736                if let Ok(eval_result_datum) = mir.eval(&[], &arena) {
3737                    if let Some(return_styp) = return_styp {
3738                        let mir_typ = mir.typ(&[]);
3739                        // MIR type inference should be consistent with the type
3740                        // we get from the catalog.
3741                        soft_assert_eq_or_log!(
3742                            mir_typ.scalar_type,
3743                            (&return_styp).into(),
3744                            "MIR type did not match the catalog type (cast elimination/repr type error)"
3745                        );
3746                        // The following will check not just that the scalar type
3747                        // is ok, but also catches if the function returned a null
3748                        // but the MIR type inference said "non-nullable".
3749                        if !eval_result_datum.is_instance_of(&mir_typ) {
3750                            panic!(
3751                                "{call_name}: expected return type of {return_styp:?}, got {eval_result_datum}"
3752                            );
3753                        }
3754                        // Check the consistency of `is_eliminable_cast`---we should get the same datum either way.
3755                        if let Some(row) = uneliminated_result_row {
3756                            let uneliminated_result_datum = row.unpack_first();
3757                            assert_eq!(
3758                                uneliminated_result_datum, eval_result_datum,
3759                                "datums should not change if cast is eliminable"
3760                            );
3761                        }
3762                        // Check the consistency of `introduces_nulls` and
3763                        // `propagates_nulls` with `MirScalarExpr::typ`.
3764                        if let Some((introduces_nulls, propagates_nulls)) =
3765                            call_introduces_propagates_nulls(&mir)
3766                        {
3767                            if introduces_nulls {
3768                                // If the function introduces_nulls, then the return
3769                                // type should always be nullable, regardless of
3770                                // the nullability of the input types.
3771                                assert!(
3772                                    mir_typ.nullable,
3773                                    "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3774                                    name, args, mir, mir_typ.nullable
3775                                );
3776                            } else {
3777                                let any_input_null = args.iter().any(|arg| arg.is_null());
3778                                if !any_input_null {
3779                                    assert!(
3780                                        !mir_typ.nullable,
3781                                        "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3782                                        name, args, mir, mir_typ.nullable
3783                                    );
3784                                } else if propagates_nulls {
3785                                    // propagates_nulls means the optimizer short-circuits
3786                                    // all-null inputs, so the output must be nullable.
3787                                    assert!(
3788                                        mir_typ.nullable,
3789                                        "fn named `{}` called on args `{:?}` (lowered to `{}`) yielded mir_typ.nullable: {}",
3790                                        name, args, mir, mir_typ.nullable
3791                                    );
3792                                }
3793                                // When propagates_nulls is false, the output may still
3794                                // be nullable if a non-nullable parameter received a null
3795                                // input (per-position null rejection). The is_instance_of
3796                                // check above ensures type consistency.
3797                            }
3798                        }
3799                        // Check that `MirScalarExpr::reduce` yields the same result
3800                        // as the real evaluation.
3801                        let mut reduced = mir.clone();
3802                        reduced.reduce(&[]);
3803                        match reduced {
3804                            MirScalarExpr::Literal(reduce_result, ctyp) => {
3805                                match reduce_result {
3806                                    Ok(reduce_result_row) => {
3807                                        let reduce_result_datum = reduce_result_row.unpack_first();
3808                                        assert_eq!(
3809                                            reduce_result_datum,
3810                                            eval_result_datum,
3811                                            "eval/reduce datum mismatch: fn named `{}` called on args `{:?}` (lowered to `{}`) evaluated to `{}` with typ `{:?}`, but reduced to `{}` with typ `{:?}`",
3812                                            name,
3813                                            args,
3814                                            mir,
3815                                            eval_result_datum,
3816                                            mir_typ.scalar_type,
3817                                            reduce_result_datum,
3818                                            ctyp.scalar_type
3819                                        );
3820                                        // Let's check that the types also match.
3821                                        // (We are not checking nullability here,
3822                                        // because it's ok when we know a more
3823                                        // precise nullability after actually
3824                                        // evaluating a function than before.)
3825                                        assert_eq!(
3826                                            ctyp.scalar_type,
3827                                            mir_typ.scalar_type,
3828                                            "eval/reduce type mismatch: fn named `{}` called on args `{:?}` (lowered to `{}`) evaluated to `{}` with typ `{:?}`, but reduced to `{}` with typ `{:?}`",
3829                                            name,
3830                                            args,
3831                                            mir,
3832                                            eval_result_datum,
3833                                            mir_typ.scalar_type,
3834                                            reduce_result_datum,
3835                                            ctyp.scalar_type
3836                                        );
3837                                    }
3838                                    Err(..) => {} // It's ok, we might have given invalid args to the function
3839                                }
3840                            }
3841                            _ => unreachable!(
3842                                "all args are literals, so should have reduced to a literal"
3843                            ),
3844                        }
3845                    }
3846                }
3847            }
3848        }
3849    }
3850
3851    /// If the given MirScalarExpr
3852    ///  - is a function call, and
3853    ///  - all arguments are literals
3854    /// then it returns whether the called function (introduces_nulls, propagates_nulls).
3855    fn call_introduces_propagates_nulls(mir_func_call: &MirScalarExpr) -> Option<(bool, bool)> {
3856        match mir_func_call {
3857            MirScalarExpr::CallUnary { func, expr } => {
3858                if expr.is_literal() {
3859                    Some((func.introduces_nulls(), func.propagates_nulls()))
3860                } else {
3861                    None
3862                }
3863            }
3864            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
3865                if expr1.is_literal() && expr2.is_literal() {
3866                    Some((func.introduces_nulls(), func.propagates_nulls()))
3867                } else {
3868                    None
3869                }
3870            }
3871            MirScalarExpr::CallVariadic { func, exprs } => {
3872                if exprs.iter().all(|arg| arg.is_literal()) {
3873                    Some((func.introduces_nulls(), func.propagates_nulls()))
3874                } else {
3875                    None
3876                }
3877            }
3878            _ => None,
3879        }
3880    }
3881
3882    // Make sure pg views don't use types that only exist in Materialize.
3883    #[mz_ore::test(tokio::test)]
3884    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3885    async fn test_pg_views_forbidden_types() {
3886        Catalog::with_debug(|catalog| async move {
3887            let conn_catalog = catalog.for_system_session();
3888
3889            for view in BUILTINS::views().filter(|view| {
3890                view.schema == PG_CATALOG_SCHEMA || view.schema == INFORMATION_SCHEMA
3891            }) {
3892                let item = conn_catalog
3893                    .resolve_item(&PartialItemName {
3894                        database: None,
3895                        schema: Some(view.schema.to_string()),
3896                        item: view.name.to_string(),
3897                    })
3898                    .expect("unable to resolve view")
3899                    // TODO(alter_table)
3900                    .at_version(RelationVersionSelector::Latest);
3901                let full_name = conn_catalog.resolve_full_name(item.name());
3902                let desc = item.relation_desc().expect("invalid item type");
3903                for col_type in desc.iter_types() {
3904                    match &col_type.scalar_type {
3905                        typ @ SqlScalarType::UInt16
3906                        | typ @ SqlScalarType::UInt32
3907                        | typ @ SqlScalarType::UInt64
3908                        | typ @ SqlScalarType::MzTimestamp
3909                        | typ @ SqlScalarType::List { .. }
3910                        | typ @ SqlScalarType::Map { .. }
3911                        | typ @ SqlScalarType::MzAclItem => {
3912                            panic!("{typ:?} type found in {full_name}");
3913                        }
3914                        SqlScalarType::AclItem
3915                        | SqlScalarType::Bool
3916                        | SqlScalarType::Int16
3917                        | SqlScalarType::Int32
3918                        | SqlScalarType::Int64
3919                        | SqlScalarType::Float32
3920                        | SqlScalarType::Float64
3921                        | SqlScalarType::Numeric { .. }
3922                        | SqlScalarType::Date
3923                        | SqlScalarType::Time
3924                        | SqlScalarType::Timestamp { .. }
3925                        | SqlScalarType::TimestampTz { .. }
3926                        | SqlScalarType::Interval
3927                        | SqlScalarType::PgLegacyChar
3928                        | SqlScalarType::Bytes
3929                        | SqlScalarType::String
3930                        | SqlScalarType::Char { .. }
3931                        | SqlScalarType::VarChar { .. }
3932                        | SqlScalarType::Jsonb
3933                        | SqlScalarType::Uuid
3934                        | SqlScalarType::Array(_)
3935                        | SqlScalarType::Record { .. }
3936                        | SqlScalarType::Oid
3937                        | SqlScalarType::RegProc
3938                        | SqlScalarType::RegType
3939                        | SqlScalarType::RegClass
3940                        | SqlScalarType::Int2Vector
3941                        | SqlScalarType::Range { .. }
3942                        | SqlScalarType::PgLegacyName => {}
3943                    }
3944                }
3945            }
3946            catalog.expire().await;
3947        })
3948        .await
3949    }
3950
3951    // Make sure objects reside in the `mz_introspection` schema iff they depend on per-replica
3952    // introspection relations.
3953    #[mz_ore::test(tokio::test)]
3954    #[cfg_attr(miri, ignore)] // unsupported operation: returning ready events from epoll_wait is not yet implemented
3955    async fn test_mz_introspection_builtins() {
3956        Catalog::with_debug(|catalog| async move {
3957            let conn_catalog = catalog.for_system_session();
3958
3959            let introspection_schema_id = catalog.get_mz_introspection_schema_id();
3960            let introspection_schema_spec = SchemaSpecifier::Id(introspection_schema_id);
3961
3962            for entry in catalog.entries() {
3963                let schema_spec = entry.name().qualifiers.schema_spec;
3964                let introspection_deps = catalog.introspection_dependencies(entry.id);
3965                if introspection_deps.is_empty() {
3966                    assert!(
3967                        schema_spec != introspection_schema_spec,
3968                        "entry does not depend on introspection sources but is in \
3969                         `mz_introspection`: {}",
3970                        conn_catalog.resolve_full_name(entry.name()),
3971                    );
3972                } else {
3973                    assert!(
3974                        schema_spec == introspection_schema_spec,
3975                        "entry depends on introspection sources but is not in \
3976                         `mz_introspection`: {}",
3977                        conn_catalog.resolve_full_name(entry.name()),
3978                    );
3979                }
3980            }
3981        })
3982        .await
3983    }
3984
3985    #[mz_ore::test(tokio::test)]
3986    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
3987    async fn test_multi_subscriber_catalog() {
3988        let persist_client = PersistClient::new_for_tests().await;
3989        let bootstrap_args = test_bootstrap_args();
3990        let organization_id = Uuid::new_v4();
3991        let db_name = "DB";
3992
3993        let mut writer_catalog = Catalog::open_debug_catalog(
3994            persist_client.clone(),
3995            organization_id.clone(),
3996            &bootstrap_args,
3997        )
3998        .await
3999        .expect("open_debug_catalog");
4000        let mut read_only_catalog = Catalog::open_debug_read_only_catalog(
4001            persist_client.clone(),
4002            organization_id.clone(),
4003            &bootstrap_args,
4004        )
4005        .await
4006        .expect("open_debug_read_only_catalog");
4007        assert_err!(writer_catalog.resolve_database(db_name));
4008        assert_err!(read_only_catalog.resolve_database(db_name));
4009
4010        let commit_ts = writer_catalog.current_upper().await;
4011        writer_catalog
4012            .transact(
4013                None,
4014                commit_ts,
4015                None,
4016                vec![Op::CreateDatabase {
4017                    name: db_name.to_string(),
4018                    owner_id: MZ_SYSTEM_ROLE_ID,
4019                }],
4020            )
4021            .await
4022            .expect("failed to transact");
4023
4024        let write_db = writer_catalog
4025            .resolve_database(db_name)
4026            .expect("resolve_database");
4027        read_only_catalog
4028            .sync_to_current_updates()
4029            .await
4030            .expect("sync_to_current_updates");
4031        let read_db = read_only_catalog
4032            .resolve_database(db_name)
4033            .expect("resolve_database");
4034
4035        assert_eq!(write_db, read_db);
4036
4037        let writer_catalog_fencer =
4038            Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args)
4039                .await
4040                .expect("open_debug_catalog for fencer");
4041        let fencer_db = writer_catalog_fencer
4042            .resolve_database(db_name)
4043            .expect("resolve_database for fencer");
4044        assert_eq!(fencer_db, read_db);
4045
4046        let write_fence_err = writer_catalog
4047            .sync_to_current_updates()
4048            .await
4049            .expect_err("sync_to_current_updates for fencer");
4050        assert!(matches!(
4051            write_fence_err,
4052            CatalogError::Durable(DurableCatalogError::Fence(FenceError::Epoch { .. }))
4053        ));
4054        let read_fence_err = read_only_catalog
4055            .sync_to_current_updates()
4056            .await
4057            .expect_err("sync_to_current_updates after fencer");
4058        assert!(matches!(
4059            read_fence_err,
4060            CatalogError::Durable(DurableCatalogError::Fence(FenceError::Epoch { .. }))
4061        ));
4062
4063        writer_catalog.expire().await;
4064        read_only_catalog.expire().await;
4065        writer_catalog_fencer.expire().await;
4066    }
4067}