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