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