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