1use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet, VecDeque};
14use std::fmt::Debug;
15use std::sync::Arc;
16use std::sync::LazyLock;
17use std::time::Instant;
18
19use ipnet::IpNet;
20use itertools::Itertools;
21use mz_adapter_types::compaction::CompactionWindow;
22use mz_adapter_types::connection::ConnectionId;
23use mz_audit_log::{EventDetails, EventType, ObjectType, VersionedEvent};
24use mz_build_info::DUMMY_BUILD_INFO;
25use mz_catalog::SYSTEM_CONN_ID;
26use mz_catalog::builtin::{
27 BUILTINS, Builtin, BuiltinCluster, BuiltinLog, BuiltinSource, BuiltinTable, BuiltinType,
28};
29use mz_catalog::config::{AwsPrincipalContext, ClusterReplicaSizeMap};
30use mz_catalog::expr_cache::{LocalExpressions, latest_item_version};
31use mz_catalog::memory::error::{Error, ErrorKind};
32use mz_catalog::memory::objects::{
33 CatalogCollectionEntry, CatalogEntry, CatalogItem, Cluster, ClusterReplica, CommentsMap,
34 Connection, DataSourceDesc, Database, DefaultPrivileges, Index, MaterializedView, MetricSink,
35 NetworkPolicy, Role, RoleAuth, Schema, Secret, Sink, Source, SourceReferences, Table,
36 TableDataSource, Type, View,
37};
38use mz_controller::clusters::{
39 ManagedReplicaLocation, ReplicaAllocation, ReplicaLocation, UnmanagedReplicaLocation,
40};
41use mz_controller_types::{ClusterId, ReplicaId};
42use mz_expr::{CollectionPlan, OptimizedMirRelationExpr};
43use mz_license_keys::ValidatedLicenseKey;
44use mz_ore::collections::CollectionExt;
45use mz_ore::now::NOW_ZERO;
46use mz_ore::soft_assert_no_log;
47use mz_ore::str::StrExt;
48use mz_pgrepr::oid::INVALID_OID;
49use mz_repr::adt::mz_acl_item::PrivilegeMap;
50use mz_repr::namespaces::{
51 INFORMATION_SCHEMA, MZ_CATALOG_SCHEMA, MZ_CATALOG_UNSTABLE_SCHEMA, MZ_INTERNAL_SCHEMA,
52 MZ_INTROSPECTION_SCHEMA, MZ_TEMP_SCHEMA, MZ_UNSAFE_SCHEMA, PG_CATALOG_SCHEMA, SYSTEM_SCHEMAS,
53 UNSTABLE_SCHEMAS,
54};
55use mz_repr::network_policy_id::NetworkPolicyId;
56use mz_repr::optimize::{OptimizerFeatureOverrides, OptimizerFeatures, OverrideFrom};
57use mz_repr::role_id::RoleId;
58use mz_repr::{
59 CatalogItemId, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector,
60 VersionedRelationDesc,
61};
62use mz_secrets::InMemorySecretsController;
63use mz_sql::ast::Ident;
64use mz_sql::catalog::{
65 CatalogCluster, CatalogClusterReplica, CatalogDatabase, CatalogError as SqlCatalogError,
66 CatalogItem as SqlCatalogItem, CatalogItemType, CatalogRecordField, CatalogRole, CatalogSchema,
67 CatalogType, CatalogTypeDetails, IdReference, NameReference, SessionCatalog, SystemObjectType,
68 TypeReference,
69};
70use mz_sql::catalog::{CatalogConfig, EnvironmentId};
71use mz_sql::names::{
72 CommentObjectId, DatabaseId, DependencyIds, FullItemName, FullSchemaName, ObjectId,
73 PartialItemName, QualifiedItemName, QualifiedSchemaName, RawDatabaseSpecifier,
74 ResolvedDatabaseSpecifier, ResolvedIds, SchemaId, SchemaSpecifier, SystemObjectId,
75};
76use mz_sql::plan::{
77 CreateConnectionPlan, CreateIndexPlan, CreateMaterializedViewPlan, CreateMetricSinkPlan,
78 CreateSecretPlan, CreateSinkPlan, CreateSourcePlan, CreateTablePlan, CreateTypePlan,
79 CreateViewPlan, Params, Plan, PlanContext,
80};
81use mz_sql::rbac;
82use mz_sql::session::metadata::SessionMetadata;
83use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
84use mz_sql::session::vars::{DEFAULT_DATABASE_NAME, SystemVars, Var, VarInput};
85use mz_sql_parser::ast::QualifiedReplica;
86use mz_storage_client::controller::StorageMetadata;
87use mz_storage_types::connections::ConnectionContext;
88use mz_storage_types::connections::inline::{
89 ConnectionResolver, InlinedConnection, IntoInlineConnection,
90};
91use mz_transform::notice::OptimizerNotice;
92use serde::Serialize;
93use timely::progress::Antichain;
94use tokio::sync::mpsc;
95use tracing::{debug, warn};
96use uuid::Uuid;
97
98use crate::AdapterError;
100use crate::catalog::{Catalog, ConnCatalog};
101use crate::config::ScopedParameters;
102use crate::coord::{ConnMeta, infer_sql_type_for_catalog};
103use crate::optimize::{self, Optimize, OptimizerCatalog};
104use crate::session::Session;
105
106#[derive(Debug, Clone, Serialize)]
113pub struct CatalogState {
114 pub(super) database_by_name: imbl::OrdMap<String, DatabaseId>,
120 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
121 pub(super) database_by_id: imbl::OrdMap<DatabaseId, Database>,
122 #[serde(serialize_with = "skip_temp_items")]
123 pub(super) entry_by_id: imbl::OrdMap<CatalogItemId, CatalogEntry>,
124 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
125 pub(super) entry_by_global_id: imbl::OrdMap<GlobalId, CatalogItemId>,
126 pub(super) ambient_schemas_by_name: imbl::OrdMap<String, SchemaId>,
127 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
128 pub(super) ambient_schemas_by_id: imbl::OrdMap<SchemaId, Schema>,
129 pub(super) clusters_by_name: imbl::OrdMap<String, ClusterId>,
130 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
131 pub(super) clusters_by_id: imbl::OrdMap<ClusterId, Cluster>,
132 pub(super) roles_by_name: imbl::OrdMap<String, RoleId>,
133 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
134 pub(super) roles_by_id: imbl::OrdMap<RoleId, Role>,
135 pub(super) network_policies_by_name: imbl::OrdMap<String, NetworkPolicyId>,
136 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
137 pub(super) network_policies_by_id: imbl::OrdMap<NetworkPolicyId, NetworkPolicy>,
138 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
139 pub(super) role_auth_by_id: imbl::OrdMap<RoleId, RoleAuth>,
140
141 #[serde(skip)]
142 pub(super) system_configuration: Arc<SystemVars>,
143 #[serde(skip)]
151 pub(super) scoped_system_parameters: ScopedParameters,
152 pub(super) default_privileges: Arc<DefaultPrivileges>,
153 pub(super) system_privileges: Arc<PrivilegeMap>,
154 pub(super) comments: Arc<CommentsMap>,
155 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
156 pub(super) source_references: imbl::OrdMap<CatalogItemId, SourceReferences>,
157 pub(super) storage_metadata: Arc<StorageMetadata>,
158 pub(super) mock_authentication_nonce: Option<String>,
159
160 #[serde(skip)]
165 pub(super) notices_by_dep_id: imbl::OrdMap<GlobalId, Vec<Arc<OptimizerNotice>>>,
166
167 #[serde(skip)]
171 pub(super) temporary_namespaces: TemporaryNamespaces,
172
173 #[serde(skip)]
175 pub(super) config: mz_sql::catalog::CatalogConfig,
176 pub(super) cluster_replica_sizes: ClusterReplicaSizeMap,
177 #[serde(skip)]
178 pub(crate) availability_zones: Vec<String>,
179
180 #[serde(skip)]
182 pub(super) egress_addresses: Vec<IpNet>,
183 pub(super) aws_principal_context: Option<AwsPrincipalContext>,
184 pub(super) aws_privatelink_availability_zones: Option<BTreeSet<String>>,
185 pub(super) http_host_name: Option<String>,
186
187 #[serde(skip)]
189 pub(super) license_key: ValidatedLicenseKey,
190}
191
192#[derive(Debug, Clone, Default)]
201pub(super) struct TemporaryNamespaces {
202 by_conn: imbl::OrdMap<ConnectionId, TemporaryNamespace>,
203 conns_by_uuid: imbl::OrdMap<Uuid, ConnectionId>,
206}
207
208#[derive(Debug, Clone)]
209struct TemporaryNamespace {
210 uuid: Uuid,
211 schema: Option<Schema>,
213}
214
215impl TemporaryNamespaces {
216 pub(super) fn register(&mut self, conn_id: ConnectionId, uuid: Uuid) {
221 let prev_conn = self.conns_by_uuid.insert(uuid, conn_id.clone());
222 mz_ore::soft_assert_or_log!(
223 prev_conn.is_none(),
224 "duplicate temporary namespace registration for {uuid}"
225 );
226 let prev_ns = self
227 .by_conn
228 .insert(conn_id, TemporaryNamespace { uuid, schema: None });
229 mz_ore::soft_assert_or_log!(
230 prev_ns.is_none(),
231 "duplicate temporary namespace registration for the connection of {uuid}"
232 );
233 }
234
235 pub(super) fn ensure_schema(
238 &mut self,
239 conn_id: &ConnectionId,
240 owner_id: RoleId,
241 ) -> &mut Schema {
242 let namespace = self
243 .by_conn
244 .get_mut(conn_id)
245 .expect("temporary namespace must be registered before items are applied");
246 namespace.schema.get_or_insert_with(|| {
247 let oid = INVALID_OID;
252 Schema {
253 name: QualifiedSchemaName {
254 database: ResolvedDatabaseSpecifier::Ambient,
255 schema: MZ_TEMP_SCHEMA.into(),
256 },
257 id: SchemaSpecifier::Temporary,
258 oid,
259 items: BTreeMap::new(),
260 functions: BTreeMap::new(),
261 types: BTreeMap::new(),
262 owner_id,
263 privileges: PrivilegeMap::from_mz_acl_items(vec![rbac::owner_privilege(
264 mz_sql::catalog::ObjectType::Schema,
265 owner_id,
266 )]),
267 }
268 })
269 }
270
271 pub(super) fn unregister(&mut self, conn_id: &ConnectionId) {
273 let Some(namespace) = self.by_conn.get(conn_id) else {
274 return;
275 };
276 if namespace
280 .schema
281 .as_ref()
282 .is_some_and(|schema| !schema.items.is_empty())
283 {
284 mz_ore::soft_panic_or_log!(
285 "temporary namespace for connection {conn_id} still has items at unregistration"
286 );
287 return;
288 }
289 let uuid = namespace.uuid;
290 self.by_conn.remove(conn_id);
291 self.conns_by_uuid.remove(&uuid);
292 }
293
294 pub(super) fn schema(&self, conn_id: &ConnectionId) -> Option<&Schema> {
295 self.by_conn
296 .get(conn_id)
297 .and_then(|namespace| namespace.schema.as_ref())
298 }
299
300 pub(super) fn schema_mut(&mut self, conn_id: &ConnectionId) -> Option<&mut Schema> {
301 self.by_conn
302 .get_mut(conn_id)
303 .and_then(|namespace| namespace.schema.as_mut())
304 }
305
306 pub(super) fn schemas(&self) -> impl Iterator<Item = &Schema> {
307 self.by_conn
308 .values()
309 .filter_map(|namespace| namespace.schema.as_ref())
310 }
311
312 pub(super) fn conn_for_uuid(&self, uuid: &Uuid) -> Option<&ConnectionId> {
313 self.conns_by_uuid.get(uuid)
314 }
315
316 pub(super) fn uuid_for_conn(&self, conn_id: &ConnectionId) -> Option<Uuid> {
317 self.by_conn.get(conn_id).map(|namespace| namespace.uuid)
318 }
319
320 pub(super) fn contains_conn(&self, conn_id: &ConnectionId) -> bool {
321 self.by_conn.contains_key(conn_id)
322 }
323
324 pub(super) fn contains_uuid(&self, uuid: &Uuid) -> bool {
325 self.conns_by_uuid.contains_key(uuid)
326 }
327}
328
329#[derive(Debug, Clone, Serialize)]
334pub(crate) enum LocalExpressionCache {
335 Open {
337 cached_exprs: BTreeMap<GlobalId, LocalExpressions>,
339 uncached_exprs: BTreeMap<GlobalId, LocalExpressions>,
341 },
342 Closed,
344}
345
346impl LocalExpressionCache {
347 pub(super) fn new(cached_exprs: BTreeMap<GlobalId, LocalExpressions>) -> Self {
348 Self::Open {
349 cached_exprs,
350 uncached_exprs: BTreeMap::new(),
351 }
352 }
353
354 pub(super) fn remove_cached_expression(&mut self, id: &GlobalId) -> Option<LocalExpressions> {
355 match self {
356 LocalExpressionCache::Open { cached_exprs, .. } => cached_exprs.remove(id),
357 LocalExpressionCache::Closed => None,
358 }
359 }
360
361 pub(super) fn insert_cached_expression(
364 &mut self,
365 id: GlobalId,
366 local_expressions: LocalExpressions,
367 ) {
368 match self {
369 LocalExpressionCache::Open { cached_exprs, .. } => {
370 cached_exprs.insert(id, local_expressions);
371 }
372 LocalExpressionCache::Closed => {}
373 }
374 }
375
376 pub(super) fn insert_uncached_expression(
379 &mut self,
380 id: GlobalId,
381 local_mir: OptimizedMirRelationExpr,
382 optimizer_features: OptimizerFeatures,
383 item_version: RelationVersion,
384 ) {
385 match self {
386 LocalExpressionCache::Open { uncached_exprs, .. } => {
387 let local_expr = LocalExpressions {
388 local_mir,
389 optimizer_features,
390 item_version,
391 };
392 let prev = uncached_exprs.remove(&id);
399 match prev {
400 Some(prev) if prev == local_expr => {
401 uncached_exprs.insert(id, local_expr);
402 }
403 None => {
404 uncached_exprs.insert(id, local_expr);
405 }
406 Some(_) => {}
407 }
408 }
409 LocalExpressionCache::Closed => {}
410 }
411 }
412
413 pub(super) fn into_uncached_exprs(self) -> BTreeMap<GlobalId, LocalExpressions> {
414 match self {
415 LocalExpressionCache::Open { uncached_exprs, .. } => uncached_exprs,
416 LocalExpressionCache::Closed => BTreeMap::new(),
417 }
418 }
419}
420
421fn skip_temp_items<S>(
422 entries: &imbl::OrdMap<CatalogItemId, CatalogEntry>,
423 serializer: S,
424) -> Result<S::Ok, S::Error>
425where
426 S: serde::Serializer,
427{
428 mz_ore::serde::map_key_to_string(
429 entries.iter().filter(|(_k, v)| v.conn_id().is_none()),
430 serializer,
431 )
432}
433
434impl CatalogState {
435 pub fn empty_test() -> Self {
439 CatalogState {
440 database_by_name: Default::default(),
441 database_by_id: Default::default(),
442 entry_by_id: Default::default(),
443 entry_by_global_id: Default::default(),
444 notices_by_dep_id: Default::default(),
445 ambient_schemas_by_name: Default::default(),
446 ambient_schemas_by_id: Default::default(),
447 temporary_namespaces: Default::default(),
448 clusters_by_id: Default::default(),
449 clusters_by_name: Default::default(),
450 network_policies_by_name: Default::default(),
451 roles_by_name: Default::default(),
452 roles_by_id: Default::default(),
453 network_policies_by_id: Default::default(),
454 role_auth_by_id: Default::default(),
455 config: CatalogConfig {
456 start_time: Default::default(),
457 start_instant: Instant::now(),
458 nonce: Default::default(),
459 environment_id: EnvironmentId::for_tests(),
460 session_id: Default::default(),
461 build_info: &DUMMY_BUILD_INFO,
462 now: NOW_ZERO.clone(),
463 connection_context: ConnectionContext::for_tests(Arc::new(
464 InMemorySecretsController::new(),
465 )),
466 aws_account_id: None,
467 helm_chart_version: None,
468 },
469 cluster_replica_sizes: ClusterReplicaSizeMap::for_tests(),
470 availability_zones: Default::default(),
471 system_configuration: Arc::new(SystemVars::default()),
472 scoped_system_parameters: Default::default(),
473 egress_addresses: Default::default(),
474 aws_principal_context: Default::default(),
475 aws_privatelink_availability_zones: Default::default(),
476 http_host_name: Default::default(),
477 default_privileges: Arc::new(DefaultPrivileges::default()),
478 system_privileges: Arc::new(PrivilegeMap::default()),
479 comments: Arc::new(CommentsMap::default()),
480 source_references: Default::default(),
481 storage_metadata: Arc::new(StorageMetadata::default()),
482 license_key: ValidatedLicenseKey::for_tests(),
483 mock_authentication_nonce: Default::default(),
484 }
485 }
486
487 pub fn for_session<'a>(&'a self, session: &'a Session) -> ConnCatalog<'a> {
488 let search_path = self.resolve_search_path(session);
489 let database = self
490 .database_by_name
491 .get(session.vars().database())
492 .map(|id| id.clone());
493 let state = match session.transaction().catalog_state() {
494 Some(txn_catalog_state) => Cow::Borrowed(txn_catalog_state),
495 None => Cow::Borrowed(self),
496 };
497 ConnCatalog {
498 state,
499 unresolvable_ids: BTreeSet::new(),
500 conn_id: session.conn_id().clone(),
501 cluster: session.vars().cluster().into(),
502 database,
503 search_path,
504 role_id: session.current_role_id().clone(),
505 prepared_statements: Some(session.prepared_statements()),
506 portals: Some(session.portals()),
507 notices_tx: session.retain_notice_transmitter(),
508 restrict_to_user_objects: session.vars().restrict_to_user_objects(),
509 }
510 }
511
512 pub fn for_sessionless_user(&self, role_id: RoleId) -> ConnCatalog<'_> {
513 let (notices_tx, _notices_rx) = mpsc::unbounded_channel();
514 let cluster = self.system_configuration.default_cluster();
515
516 ConnCatalog {
517 state: Cow::Borrowed(self),
518 unresolvable_ids: BTreeSet::new(),
519 conn_id: SYSTEM_CONN_ID.clone(),
520 cluster,
521 database: self
522 .resolve_database(DEFAULT_DATABASE_NAME)
523 .ok()
524 .map(|db| db.id()),
525 search_path: Vec::new(),
528 role_id,
529 prepared_statements: None,
530 portals: None,
531 notices_tx,
532 restrict_to_user_objects: false,
533 }
534 }
535
536 pub fn for_system_session(&self) -> ConnCatalog<'_> {
537 self.for_sessionless_user(MZ_SYSTEM_ROLE_ID)
538 }
539
540 pub fn transitive_uses(&self, id: CatalogItemId) -> impl Iterator<Item = CatalogItemId> + '_ {
545 struct I<'a> {
546 queue: VecDeque<CatalogItemId>,
547 seen: BTreeSet<CatalogItemId>,
548 this: &'a CatalogState,
549 }
550 impl<'a> Iterator for I<'a> {
551 type Item = CatalogItemId;
552 fn next(&mut self) -> Option<Self::Item> {
553 if let Some(next) = self.queue.pop_front() {
554 for child in self.this.get_entry(&next).item().uses() {
555 if !self.seen.contains(&child) {
556 self.queue.push_back(child);
557 self.seen.insert(child);
558 }
559 }
560 Some(next)
561 } else {
562 None
563 }
564 }
565 }
566
567 I {
568 queue: [id].into_iter().collect(),
569 seen: [id].into_iter().collect(),
570 this: self,
571 }
572 }
573
574 pub fn introspection_dependencies(&self, id: CatalogItemId) -> Vec<CatalogItemId> {
577 let mut out = Vec::new();
578
579 let mut queue: VecDeque<CatalogItemId> = [id].into_iter().collect();
582 let mut seen: BTreeSet<CatalogItemId> = [id].into_iter().collect();
583 while let Some(id) = queue.pop_front() {
584 match self.get_entry(&id).item() {
585 CatalogItem::Log(_) => out.push(id),
586 item @ (CatalogItem::View(_)
587 | CatalogItem::MaterializedView(_)
588 | CatalogItem::Connection(_)) => {
589 for item_id in item.references().items() {
591 if seen.insert(*item_id) {
592 queue.push_back(*item_id);
593 }
594 }
595 }
596 CatalogItem::Sink(sink) => {
597 let from_item_id = self.get_entry_by_global_id(&sink.from).id();
598 if seen.insert(from_item_id) {
599 queue.push_back(from_item_id);
600 }
601 }
602 CatalogItem::MetricSink(metric_sink) => {
603 let from_item_id = self.get_entry_by_global_id(&metric_sink.from).id();
604 if seen.insert(from_item_id) {
605 queue.push_back(from_item_id);
606 }
607 }
608 CatalogItem::Index(idx) => {
609 let on_item_id = self.get_entry_by_global_id(&idx.on).id();
610 if seen.insert(on_item_id) {
611 queue.push_back(on_item_id);
612 }
613 }
614 CatalogItem::Table(_)
615 | CatalogItem::Source(_)
616 | CatalogItem::Type(_)
617 | CatalogItem::Func(_)
618 | CatalogItem::Secret(_) => (),
619 }
620 }
621
622 out
623 }
624
625 pub(super) fn object_dependents(
631 &self,
632 object_ids: &Vec<ObjectId>,
633 conn_id: &ConnectionId,
634 seen: &mut BTreeSet<ObjectId>,
635 ) -> Vec<ObjectId> {
636 let mut dependents = Vec::new();
637 for object_id in object_ids {
638 match object_id {
639 ObjectId::Cluster(id) => {
640 dependents.extend_from_slice(&self.cluster_dependents(*id, seen));
641 }
642 ObjectId::ClusterReplica((cluster_id, replica_id)) => dependents.extend_from_slice(
643 &self.cluster_replica_dependents(*cluster_id, *replica_id, seen),
644 ),
645 ObjectId::Database(id) => {
646 dependents.extend_from_slice(&self.database_dependents(*id, conn_id, seen))
647 }
648 ObjectId::Schema((database_spec, schema_spec)) => {
649 dependents.extend_from_slice(&self.schema_dependents(
650 database_spec.clone(),
651 schema_spec.clone(),
652 conn_id,
653 seen,
654 ));
655 }
656 ObjectId::NetworkPolicy(id) => {
657 dependents.extend_from_slice(&self.network_policy_dependents(*id, seen));
658 }
659 id @ ObjectId::Role(_) => {
660 let unseen = seen.insert(id.clone());
661 if unseen {
662 dependents.push(id.clone());
663 }
664 }
665 ObjectId::Item(id) => {
666 dependents.extend_from_slice(&self.item_dependents(*id, seen))
667 }
668 }
669 }
670 dependents
671 }
672
673 fn cluster_dependents(
680 &self,
681 cluster_id: ClusterId,
682 seen: &mut BTreeSet<ObjectId>,
683 ) -> Vec<ObjectId> {
684 let mut dependents = Vec::new();
685 let object_id = ObjectId::Cluster(cluster_id);
686 if !seen.contains(&object_id) {
687 seen.insert(object_id.clone());
688 let cluster = self.get_cluster(cluster_id);
689 for item_id in cluster.bound_objects() {
690 dependents.extend_from_slice(&self.item_dependents(*item_id, seen));
691 }
692 for replica_id in cluster.replica_ids().values() {
693 dependents.extend_from_slice(&self.cluster_replica_dependents(
694 cluster_id,
695 *replica_id,
696 seen,
697 ));
698 }
699 dependents.push(object_id);
700 }
701 dependents
702 }
703
704 pub(super) fn cluster_replica_dependents(
711 &self,
712 cluster_id: ClusterId,
713 replica_id: ReplicaId,
714 seen: &mut BTreeSet<ObjectId>,
715 ) -> Vec<ObjectId> {
716 let mut dependents = Vec::new();
717 let object_id = ObjectId::ClusterReplica((cluster_id, replica_id));
718 if !seen.contains(&object_id) {
719 seen.insert(object_id.clone());
720 let cluster = self.get_cluster(cluster_id);
724 for item_id in cluster.bound_objects() {
725 if let CatalogItem::MaterializedView(mv) = self.get_entry(item_id).item()
726 && mv.target_replica == Some(replica_id)
727 {
728 dependents.extend_from_slice(&self.item_dependents(*item_id, seen));
729 }
730 }
731 dependents.push(object_id);
732 }
733 dependents
734 }
735
736 fn database_dependents(
743 &self,
744 database_id: DatabaseId,
745 conn_id: &ConnectionId,
746 seen: &mut BTreeSet<ObjectId>,
747 ) -> Vec<ObjectId> {
748 let mut dependents = Vec::new();
749 let object_id = ObjectId::Database(database_id);
750 if !seen.contains(&object_id) {
751 seen.insert(object_id.clone());
752 let database = self.get_database(&database_id);
753 for schema_id in database.schema_ids().values() {
754 dependents.extend_from_slice(&self.schema_dependents(
755 ResolvedDatabaseSpecifier::Id(database_id),
756 SchemaSpecifier::Id(*schema_id),
757 conn_id,
758 seen,
759 ));
760 }
761 dependents.push(object_id);
762 }
763 dependents
764 }
765
766 fn schema_dependents(
773 &self,
774 database_spec: ResolvedDatabaseSpecifier,
775 schema_spec: SchemaSpecifier,
776 conn_id: &ConnectionId,
777 seen: &mut BTreeSet<ObjectId>,
778 ) -> Vec<ObjectId> {
779 let mut dependents = Vec::new();
780 let object_id = ObjectId::Schema((database_spec, schema_spec.clone()));
781 if !seen.contains(&object_id) {
782 seen.insert(object_id.clone());
783 let schema = self.get_schema(&database_spec, &schema_spec, conn_id);
784 for item_id in schema.item_ids() {
785 dependents.extend_from_slice(&self.item_dependents(item_id, seen));
786 }
787 dependents.push(object_id)
788 }
789 dependents
790 }
791
792 pub(super) fn item_dependents(
799 &self,
800 item_id: CatalogItemId,
801 seen: &mut BTreeSet<ObjectId>,
802 ) -> Vec<ObjectId> {
803 let mut dependents = Vec::new();
804
805 enum Work {
809 Enter(CatalogItemId),
810 Emit(ObjectId),
811 }
812 let mut stack = vec![Work::Enter(item_id)];
813 while let Some(work) = stack.pop() {
814 match work {
815 Work::Enter(item_id) => {
816 let object_id = ObjectId::Item(item_id);
817 if !seen.insert(object_id.clone()) {
818 continue;
819 }
820 let entry = self.get_entry(&item_id);
821 if let Some(progress_id) = entry.progress_id() {
828 stack.push(Work::Enter(progress_id));
829 }
830 stack.push(Work::Emit(object_id));
831 for dependent_id in entry.used_by().iter().rev() {
832 stack.push(Work::Enter(*dependent_id));
833 }
834 }
835 Work::Emit(object_id) => dependents.push(object_id),
836 }
837 }
838
839 dependents
840 }
841
842 pub(super) fn network_policy_dependents(
849 &self,
850 network_policy_id: NetworkPolicyId,
851 _seen: &mut BTreeSet<ObjectId>,
852 ) -> Vec<ObjectId> {
853 let object_id = ObjectId::NetworkPolicy(network_policy_id);
854 vec![object_id]
858 }
859
860 fn is_stable(&self, id: CatalogItemId) -> bool {
864 let spec = self.get_entry(&id).name().qualifiers.schema_spec;
865 !self.is_unstable_schema_specifier(spec)
866 }
867
868 pub(super) fn check_unstable_dependencies(&self, item: &CatalogItem) -> Result<(), Error> {
869 if self.system_config().unsafe_enable_unstable_dependencies() {
870 return Ok(());
871 }
872
873 let unstable_dependencies: Vec<_> = item
874 .references()
875 .items()
876 .filter(|id| !self.is_stable(**id))
877 .map(|id| self.get_entry(id).name().item.clone())
878 .collect();
879
880 if unstable_dependencies.is_empty() || item.is_temporary() {
884 Ok(())
885 } else {
886 let object_type = item.typ().to_string();
887 Err(Error {
888 kind: ErrorKind::UnstableDependency {
889 object_type,
890 unstable_dependencies,
891 },
892 })
893 }
894 }
895
896 pub fn resolve_full_name(
897 &self,
898 name: &QualifiedItemName,
899 conn_id: Option<&ConnectionId>,
900 ) -> FullItemName {
901 let conn_id = conn_id.unwrap_or(&SYSTEM_CONN_ID);
902
903 let database = match &name.qualifiers.database_spec {
904 ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
905 ResolvedDatabaseSpecifier::Id(id) => {
906 RawDatabaseSpecifier::Name(self.get_database(id).name().to_string())
907 }
908 };
909 let schema = match &name.qualifiers.schema_spec {
912 SchemaSpecifier::Temporary => MZ_TEMP_SCHEMA.to_string(),
913 SchemaSpecifier::Id(_) => self
914 .get_schema(
915 &name.qualifiers.database_spec,
916 &name.qualifiers.schema_spec,
917 conn_id,
918 )
919 .name()
920 .schema
921 .clone(),
922 };
923 FullItemName {
924 database,
925 schema,
926 item: name.item.clone(),
927 }
928 }
929
930 pub(super) fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName {
931 let database = match &name.database {
932 ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
933 ResolvedDatabaseSpecifier::Id(id) => {
934 RawDatabaseSpecifier::Name(self.get_database(id).name().to_string())
935 }
936 };
937 FullSchemaName {
938 database,
939 schema: name.schema.clone(),
940 }
941 }
942
943 pub fn get_entry(&self, id: &CatalogItemId) -> &CatalogEntry {
944 self.entry_by_id
945 .get(id)
946 .unwrap_or_else(|| panic!("catalog out of sync, missing id {id:?}"))
947 }
948
949 pub fn get_entry_by_global_id(&self, id: &GlobalId) -> CatalogCollectionEntry {
950 let item_id = self
951 .entry_by_global_id
952 .get(id)
953 .unwrap_or_else(|| panic!("catalog out of sync, missing id {id:?}"));
954
955 let entry = self.get_entry(item_id).clone();
956 let version = match entry.item() {
957 CatalogItem::Table(table) => {
958 let (version, _) = table
959 .collections
960 .iter()
961 .find(|(_verison, gid)| *gid == id)
962 .expect("version to exist");
963 RelationVersionSelector::Specific(*version)
964 }
965 _ => RelationVersionSelector::Latest,
966 };
967 CatalogCollectionEntry { entry, version }
968 }
969
970 pub fn get_entries(&self) -> impl Iterator<Item = (&CatalogItemId, &CatalogEntry)> + '_ {
971 self.entry_by_id.iter()
972 }
973
974 pub fn get_temp_items(&self, conn: &ConnectionId) -> impl Iterator<Item = ObjectId> + '_ {
975 self.temporary_namespaces
978 .schema(conn)
979 .into_iter()
980 .flat_map(|schema| schema.items.values().copied().map(ObjectId::from))
981 }
982
983 pub fn has_temporary_namespace(&self, conn: &ConnectionId) -> bool {
990 self.temporary_namespaces.contains_conn(conn)
991 }
992
993 pub(super) fn durable_item(
999 &self,
1000 entry: CatalogEntry,
1001 ) -> Result<mz_catalog::durable::Item, AdapterError> {
1002 let ephemeral_owner_session = entry
1003 .conn_id()
1004 .map(|conn_id| {
1005 self.temporary_namespaces
1006 .uuid_for_conn(conn_id)
1007 .ok_or_else(|| {
1008 AdapterError::Internal(format!(
1009 "no session record for connection {conn_id} owning temporary item"
1010 ))
1011 })
1012 })
1013 .transpose()?;
1014 let (create_sql, global_id, extra_versions) = entry.item.into_serialized();
1015 Ok(mz_catalog::durable::Item {
1016 id: entry.id,
1017 oid: entry.oid,
1018 global_id,
1019 schema_id: entry.name.qualifiers.schema_spec.into(),
1020 name: entry.name.item,
1021 create_sql,
1022 owner_id: entry.owner_id,
1023 privileges: entry.privileges.into_all_values().collect(),
1024 extra_versions,
1025 ephemeral_owner_session,
1026 })
1027 }
1028
1029 pub(super) fn get_system_type(&self, name: &str) -> &CatalogEntry {
1035 let mut res = None;
1036 for schema_id in self.system_schema_ids() {
1037 let schema = &self.ambient_schemas_by_id[&schema_id];
1038 if let Some(global_id) = schema.types.get(name) {
1039 match res {
1040 None => res = Some(self.get_entry(global_id)),
1041 Some(_) => panic!(
1042 "only call get_system_type on objects uniquely identifiable in one system schema"
1043 ),
1044 }
1045 }
1046 }
1047
1048 res.unwrap_or_else(|| panic!("cannot find type {} in system schema", name))
1049 }
1050
1051 pub fn get_item_by_name(
1052 &self,
1053 name: &QualifiedItemName,
1054 conn_id: &ConnectionId,
1055 ) -> Option<&CatalogEntry> {
1056 self.get_schema(
1057 &name.qualifiers.database_spec,
1058 &name.qualifiers.schema_spec,
1059 conn_id,
1060 )
1061 .items
1062 .get(&name.item)
1063 .and_then(|id| self.try_get_entry(id))
1064 }
1065
1066 pub fn get_type_by_name(
1067 &self,
1068 name: &QualifiedItemName,
1069 conn_id: &ConnectionId,
1070 ) -> Option<&CatalogEntry> {
1071 self.get_schema(
1072 &name.qualifiers.database_spec,
1073 &name.qualifiers.schema_spec,
1074 conn_id,
1075 )
1076 .types
1077 .get(&name.item)
1078 .and_then(|id| self.try_get_entry(id))
1079 }
1080
1081 pub(super) fn find_available_name(
1082 &self,
1083 mut name: QualifiedItemName,
1084 conn_id: &ConnectionId,
1085 ) -> QualifiedItemName {
1086 let mut i = 0;
1087 let orig_item_name = name.item.clone();
1088 while self.get_item_by_name(&name, conn_id).is_some() {
1089 i += 1;
1090 name.item = format!("{}{}", orig_item_name, i);
1091 }
1092 name
1093 }
1094
1095 pub fn try_get_entry(&self, id: &CatalogItemId) -> Option<&CatalogEntry> {
1096 self.entry_by_id.get(id)
1097 }
1098
1099 pub fn try_get_entry_by_global_id(&self, id: &GlobalId) -> Option<&CatalogEntry> {
1100 let item_id = self.entry_by_global_id.get(id)?;
1101 self.try_get_entry(item_id)
1102 }
1103
1104 pub fn try_get_desc_by_global_id(&self, id: &GlobalId) -> Option<Cow<'_, RelationDesc>> {
1107 let entry = self.try_get_entry_by_global_id(id)?;
1108 let desc = match entry.item() {
1109 CatalogItem::Table(table) => Cow::Owned(table.desc_for(id)),
1110 other => other.relation_desc(RelationVersionSelector::Latest)?,
1112 };
1113 Some(desc)
1114 }
1115
1116 pub(crate) fn get_cluster(&self, cluster_id: ClusterId) -> &Cluster {
1117 self.try_get_cluster(cluster_id)
1118 .unwrap_or_else(|| panic!("unknown cluster {cluster_id}"))
1119 }
1120
1121 pub(super) fn try_get_cluster(&self, cluster_id: ClusterId) -> Option<&Cluster> {
1122 self.clusters_by_id.get(&cluster_id)
1123 }
1124
1125 pub(super) fn try_get_role(&self, id: &RoleId) -> Option<&Role> {
1126 self.roles_by_id.get(id)
1127 }
1128
1129 pub fn get_role(&self, id: &RoleId) -> &Role {
1130 self.roles_by_id.get(id).expect("catalog out of sync")
1131 }
1132
1133 pub fn get_roles(&self) -> impl Iterator<Item = &RoleId> {
1134 self.roles_by_id.keys()
1135 }
1136
1137 pub(super) fn try_get_role_by_name(&self, role_name: &str) -> Option<&Role> {
1138 self.roles_by_name
1139 .get(role_name)
1140 .map(|id| &self.roles_by_id[id])
1141 }
1142
1143 pub(super) fn get_role_auth(&self, id: &RoleId) -> &RoleAuth {
1144 self.role_auth_by_id
1145 .get(id)
1146 .unwrap_or_else(|| panic!("catalog out of sync, missing role auth for {id}"))
1147 }
1148
1149 pub(super) fn try_get_role_auth_by_id(&self, id: &RoleId) -> Option<&RoleAuth> {
1150 self.role_auth_by_id.get(id)
1151 }
1152
1153 pub(super) fn try_get_network_policy_by_name(
1154 &self,
1155 policy_name: &str,
1156 ) -> Option<&NetworkPolicy> {
1157 self.network_policies_by_name
1158 .get(policy_name)
1159 .map(|id| &self.network_policies_by_id[id])
1160 }
1161
1162 pub(crate) fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId> {
1163 let mut membership = BTreeSet::new();
1164 let mut queue = VecDeque::from(vec![id]);
1165 while let Some(cur_id) = queue.pop_front() {
1166 if !membership.contains(cur_id) {
1167 membership.insert(cur_id.clone());
1168 let role = self.get_role(cur_id);
1169 soft_assert_no_log!(
1170 !role.membership().keys().contains(id),
1171 "circular membership exists in the catalog"
1172 );
1173 queue.extend(role.membership().keys());
1174 }
1175 }
1176 membership.insert(RoleId::Public);
1177 membership
1178 }
1179
1180 pub fn get_network_policy(&self, id: &NetworkPolicyId) -> &NetworkPolicy {
1181 self.network_policies_by_id
1182 .get(id)
1183 .expect("catalog out of sync")
1184 }
1185
1186 pub fn get_network_policies(&self) -> impl Iterator<Item = &NetworkPolicyId> {
1187 self.network_policies_by_id.keys()
1188 }
1189
1190 pub fn try_get_webhook_url(&self, id: &CatalogItemId) -> Option<url::Url> {
1195 let entry = self.try_get_entry(id)?;
1196 let name = self.resolve_full_name(entry.name(), None);
1198 let host_name = self
1199 .http_host_name
1200 .as_ref()
1201 .map(|x| x.as_str())
1202 .unwrap_or_else(|| "HOST");
1203
1204 let RawDatabaseSpecifier::Name(database) = name.database else {
1205 return None;
1206 };
1207
1208 let mut url = url::Url::parse(&format!("https://{host_name}/api/webhook")).ok()?;
1209 url.path_segments_mut()
1210 .ok()?
1211 .push(&database)
1212 .push(&name.schema)
1213 .push(&name.item);
1214
1215 Some(url)
1216 }
1217
1218 pub(crate) fn deserialize_plan_with_enable_for_item_parsing(
1226 &mut self,
1229 create_sql: &str,
1230 force_if_exists_skip: bool,
1231 ) -> Result<(Plan, ResolvedIds), AdapterError> {
1232 self.with_enable_for_item_parsing(|state| {
1233 let pcx = PlanContext::zero().with_ignore_if_exists_errors(force_if_exists_skip);
1234 let pcx = Some(&pcx);
1235 let session_catalog = state.for_system_session();
1236
1237 let stmt = mz_sql::parse::parse(create_sql)?.into_element().ast;
1238 let (stmt, resolved_ids) = mz_sql::names::resolve(&session_catalog, stmt)?;
1239 let (plan, _sql_impl_ids) =
1240 mz_sql::plan::plan(pcx, &session_catalog, stmt, &Params::empty(), &resolved_ids)?;
1241
1242 Ok((plan, resolved_ids))
1243 })
1244 }
1245
1246 #[mz_ore::instrument]
1248 pub(crate) fn parse_plan(
1249 create_sql: &str,
1250 pcx: Option<&PlanContext>,
1251 catalog: &ConnCatalog,
1252 ) -> Result<(Plan, ResolvedIds), AdapterError> {
1253 let stmt = mz_sql::parse::parse(create_sql)?.into_element().ast;
1254 let (stmt, resolved_ids) = mz_sql::names::resolve(catalog, stmt)?;
1255 let (plan, _sql_impl_ids) =
1256 mz_sql::plan::plan(pcx, catalog, stmt, &Params::empty(), &resolved_ids)?;
1257
1258 Ok((plan, resolved_ids))
1259 }
1260
1261 pub(crate) fn deserialize_item(
1263 &self,
1264 global_id: GlobalId,
1265 create_sql: &str,
1266 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1267 local_expression_cache: &mut LocalExpressionCache,
1268 previous_item: Option<CatalogItem>,
1269 ) -> Result<CatalogItem, AdapterError> {
1270 self.parse_item(
1271 global_id,
1272 create_sql,
1273 extra_versions,
1274 None,
1275 false,
1276 None,
1277 local_expression_cache,
1278 previous_item,
1279 )
1280 }
1281
1282 #[mz_ore::instrument]
1284 pub(crate) fn parse_item(
1285 &self,
1286 global_id: GlobalId,
1287 create_sql: &str,
1288 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1289 pcx: Option<&PlanContext>,
1290 is_retained_metrics_object: bool,
1291 custom_logical_compaction_window: Option<CompactionWindow>,
1292 local_expression_cache: &mut LocalExpressionCache,
1293 previous_item: Option<CatalogItem>,
1294 ) -> Result<CatalogItem, AdapterError> {
1295 let cached_expr = local_expression_cache.remove_cached_expression(&global_id);
1296 match self.parse_item_inner(
1297 global_id,
1298 create_sql,
1299 extra_versions,
1300 pcx,
1301 is_retained_metrics_object,
1302 custom_logical_compaction_window,
1303 cached_expr,
1304 previous_item,
1305 ) {
1306 Ok((item, uncached_expr)) => {
1307 if let Some((uncached_expr, optimizer_features)) = uncached_expr {
1308 local_expression_cache.insert_uncached_expression(
1309 global_id,
1310 uncached_expr,
1311 optimizer_features,
1312 latest_item_version(extra_versions),
1313 );
1314 }
1315 Ok(item)
1316 }
1317 Err((err, cached_expr)) => {
1318 if let Some(local_expr) = cached_expr {
1319 local_expression_cache.insert_cached_expression(global_id, local_expr);
1320 }
1321 Err(err)
1322 }
1323 }
1324 }
1325
1326 #[mz_ore::instrument]
1333 pub(crate) fn parse_item_inner(
1334 &self,
1335 global_id: GlobalId,
1336 create_sql: &str,
1337 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1338 pcx: Option<&PlanContext>,
1339 is_retained_metrics_object: bool,
1340 custom_logical_compaction_window: Option<CompactionWindow>,
1341 cached_expr: Option<LocalExpressions>,
1342 previous_item: Option<CatalogItem>,
1343 ) -> Result<
1344 (
1345 CatalogItem,
1346 Option<(OptimizedMirRelationExpr, OptimizerFeatures)>,
1347 ),
1348 (AdapterError, Option<LocalExpressions>),
1349 > {
1350 let session_catalog = self.for_system_session();
1351
1352 let (plan, resolved_ids) = match Self::parse_plan(create_sql, pcx, &session_catalog) {
1353 Ok((plan, resolved_ids)) => (plan, resolved_ids),
1354 Err(err) => return Err((err, cached_expr)),
1355 };
1356
1357 let mut uncached_expr = None;
1358
1359 let previous_plans = previous_item.as_ref().map(|item| {
1369 (
1370 item.optimized_plan().cloned(),
1371 item.physical_plan().cloned(),
1372 item.dataflow_metainfo().cloned(),
1373 )
1374 });
1375
1376 let mut item = match plan {
1377 Plan::CreateTable(CreateTablePlan { table, .. }) => {
1378 let collections = extra_versions
1379 .iter()
1380 .map(|(version, gid)| (*version, *gid))
1381 .chain([(RelationVersion::root(), global_id)].into_iter())
1382 .collect();
1383
1384 CatalogItem::Table(Table {
1385 create_sql: Some(table.create_sql),
1386 desc: table.desc,
1387 collections,
1388 conn_id: None,
1389 resolved_ids,
1390 custom_logical_compaction_window: custom_logical_compaction_window
1391 .or(table.compaction_window),
1392 is_retained_metrics_object,
1393 data_source: match table.data_source {
1394 mz_sql::plan::TableDataSource::TableWrites { defaults } => {
1395 TableDataSource::TableWrites { defaults }
1396 }
1397 mz_sql::plan::TableDataSource::DataSource {
1398 desc: data_source_desc,
1399 timeline,
1400 } => match data_source_desc {
1401 mz_sql::plan::DataSourceDesc::IngestionExport {
1402 ingestion_id,
1403 external_reference,
1404 details,
1405 data_config,
1406 } => TableDataSource::DataSource {
1407 desc: DataSourceDesc::IngestionExport {
1408 ingestion_id,
1409 external_reference,
1410 details,
1411 data_config,
1412 },
1413 timeline,
1414 },
1415 mz_sql::plan::DataSourceDesc::Webhook {
1416 validate_using,
1417 body_format,
1418 headers,
1419 cluster_id,
1420 } => TableDataSource::DataSource {
1421 desc: DataSourceDesc::Webhook {
1422 validate_using,
1423 body_format,
1424 headers,
1425 cluster_id: cluster_id
1426 .expect("Webhook Tables must have a cluster_id set"),
1427 },
1428 timeline,
1429 },
1430 _ => {
1431 return Err((
1432 AdapterError::Unstructured(anyhow::anyhow!(
1433 "unsupported data source for table"
1434 )),
1435 cached_expr,
1436 ));
1437 }
1438 },
1439 },
1440 })
1441 }
1442 Plan::CreateSource(CreateSourcePlan {
1443 source,
1444 timeline,
1445 in_cluster,
1446 ..
1447 }) => CatalogItem::Source(Source {
1448 create_sql: Some(source.create_sql),
1449 data_source: match source.data_source {
1450 mz_sql::plan::DataSourceDesc::Ingestion(desc) => DataSourceDesc::Ingestion {
1451 desc,
1452 cluster_id: match in_cluster {
1453 Some(id) => id,
1454 None => {
1455 return Err((
1456 AdapterError::Unstructured(anyhow::anyhow!(
1457 "ingestion-based sources must have cluster specified"
1458 )),
1459 cached_expr,
1460 ));
1461 }
1462 },
1463 },
1464 mz_sql::plan::DataSourceDesc::OldSyntaxIngestion {
1465 desc,
1466 progress_subsource,
1467 data_config,
1468 details,
1469 } => DataSourceDesc::OldSyntaxIngestion {
1470 desc,
1471 progress_subsource,
1472 data_config,
1473 details,
1474 cluster_id: match in_cluster {
1475 Some(id) => id,
1476 None => {
1477 return Err((
1478 AdapterError::Unstructured(anyhow::anyhow!(
1479 "ingestion-based sources must have cluster specified"
1480 )),
1481 cached_expr,
1482 ));
1483 }
1484 },
1485 },
1486 mz_sql::plan::DataSourceDesc::IngestionExport {
1487 ingestion_id,
1488 external_reference,
1489 details,
1490 data_config,
1491 } => DataSourceDesc::IngestionExport {
1492 ingestion_id,
1493 external_reference,
1494 details,
1495 data_config,
1496 },
1497 mz_sql::plan::DataSourceDesc::Progress => DataSourceDesc::Progress,
1498 mz_sql::plan::DataSourceDesc::Webhook {
1499 validate_using,
1500 body_format,
1501 headers,
1502 cluster_id,
1503 } => {
1504 mz_ore::soft_assert_or_log!(
1505 cluster_id.is_none(),
1506 "cluster_id set at Source level for Webhooks"
1507 );
1508 DataSourceDesc::Webhook {
1509 validate_using,
1510 body_format,
1511 headers,
1512 cluster_id: in_cluster
1513 .expect("webhook sources must use an existing cluster"),
1514 }
1515 }
1516 },
1517 desc: source.desc,
1518 global_id,
1519 timeline,
1520 resolved_ids,
1521 custom_logical_compaction_window: source
1522 .compaction_window
1523 .or(custom_logical_compaction_window),
1524 is_retained_metrics_object,
1525 }),
1526 Plan::CreateView(CreateViewPlan { view, .. }) => {
1527 let optimizer_config =
1529 optimize::OptimizerConfig::from(session_catalog.system_vars());
1530 let previous_exprs = previous_item.map(|item| match item {
1531 CatalogItem::View(view) => Some((view.raw_expr, view.locally_optimized_expr)),
1532 _ => None,
1533 });
1534
1535 let (raw_expr, optimized_expr) = match (cached_expr, previous_exprs) {
1536 (Some(local_expr), _)
1537 if local_expr.optimizer_features == optimizer_config.features =>
1538 {
1539 debug!("local expression cache hit for {global_id:?}");
1540 (Arc::new(view.expr), Arc::new(local_expr.local_mir))
1541 }
1542 (_, Some(Some((raw_expr, optimized_expr)))) if *raw_expr == view.expr => {
1544 (Arc::clone(&raw_expr), Arc::clone(&optimized_expr))
1545 }
1546 (cached_expr, _) => {
1547 let optimizer_features = optimizer_config.features.clone();
1548 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
1550
1551 let raw_expr = view.expr;
1553 let optimized_expr = match optimizer.optimize(raw_expr.clone()) {
1554 Ok(optimzed_expr) => optimzed_expr,
1555 Err(err) => return Err((err.into(), cached_expr)),
1556 };
1557
1558 uncached_expr = Some((optimized_expr.clone(), optimizer_features));
1559
1560 (Arc::new(raw_expr), Arc::new(optimized_expr))
1561 }
1562 };
1563
1564 let dependencies: BTreeSet<_> = raw_expr
1566 .depends_on()
1567 .into_iter()
1568 .map(|gid| self.get_entry_by_global_id(&gid).id())
1569 .collect();
1570
1571 let typ = infer_sql_type_for_catalog(&raw_expr, &optimized_expr);
1572 CatalogItem::View(View {
1573 create_sql: view.create_sql,
1574 global_id,
1575 raw_expr,
1576 desc: RelationDesc::new(typ, view.column_names),
1577 locally_optimized_expr: optimized_expr,
1578 conn_id: None,
1579 resolved_ids,
1580 dependencies: DependencyIds(dependencies),
1581 })
1582 }
1583 Plan::CreateMaterializedView(CreateMaterializedViewPlan {
1584 materialized_view, ..
1585 }) => {
1586 let collections = extra_versions
1587 .iter()
1588 .map(|(version, gid)| (*version, *gid))
1589 .chain([(RelationVersion::root(), global_id)].into_iter())
1590 .collect();
1591
1592 let system_vars = session_catalog.system_vars();
1594 let overrides = self
1595 .get_cluster(materialized_view.cluster_id)
1596 .config
1597 .features();
1598 let optimizer_config =
1599 optimize::OptimizerConfig::from(system_vars).override_from(&overrides);
1600 let previous_exprs = previous_item.map(|item| match item {
1601 CatalogItem::MaterializedView(materialized_view) => (
1602 materialized_view.raw_expr,
1603 materialized_view.locally_optimized_expr,
1604 ),
1605 item => unreachable!("expected materialized view, found: {item:#?}"),
1606 });
1607
1608 let (raw_expr, optimized_expr) = match (cached_expr, previous_exprs) {
1609 (Some(local_expr), _)
1610 if local_expr.optimizer_features == optimizer_config.features =>
1611 {
1612 debug!("local expression cache hit for {global_id:?}");
1613 (
1614 Arc::new(materialized_view.expr),
1615 Arc::new(local_expr.local_mir),
1616 )
1617 }
1618 (_, Some((raw_expr, optimized_expr)))
1620 if *raw_expr == materialized_view.expr =>
1621 {
1622 (Arc::clone(&raw_expr), Arc::clone(&optimized_expr))
1623 }
1624 (cached_expr, _) => {
1625 let optimizer_features = optimizer_config.features.clone();
1626 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
1628
1629 let raw_expr = materialized_view.expr;
1630 let optimized_expr = match optimizer.optimize(raw_expr.clone()) {
1631 Ok(optimized_expr) => optimized_expr,
1632 Err(err) => return Err((err.into(), cached_expr)),
1633 };
1634
1635 uncached_expr = Some((optimized_expr.clone(), optimizer_features));
1636
1637 (Arc::new(raw_expr), Arc::new(optimized_expr))
1638 }
1639 };
1640 let mut typ = infer_sql_type_for_catalog(&raw_expr, &optimized_expr);
1641
1642 for &i in &materialized_view.non_null_assertions {
1643 typ.column_types[i].nullable = false;
1644 }
1645 let desc = RelationDesc::new(typ, materialized_view.column_names);
1646 let desc = VersionedRelationDesc::new(desc);
1647
1648 let initial_as_of = materialized_view.as_of.map(Antichain::from_elem);
1649
1650 let dependencies = raw_expr
1652 .depends_on()
1653 .into_iter()
1654 .map(|gid| self.get_entry_by_global_id(&gid).id())
1655 .collect();
1656
1657 CatalogItem::MaterializedView(MaterializedView {
1658 create_sql: materialized_view.create_sql,
1659 collections,
1660 raw_expr,
1661 locally_optimized_expr: optimized_expr,
1662 desc,
1663 resolved_ids,
1664 dependencies,
1665 replacement_target: materialized_view.replacement_target,
1666 cluster_id: materialized_view.cluster_id,
1667 target_replica: materialized_view.target_replica,
1668 non_null_assertions: materialized_view.non_null_assertions,
1669 custom_logical_compaction_window: materialized_view.compaction_window,
1670 refresh_schedule: materialized_view.refresh_schedule,
1671 initial_as_of,
1672 optimized_plan: None,
1673 physical_plan: None,
1674 dataflow_metainfo: None,
1675 })
1676 }
1677 Plan::CreateIndex(CreateIndexPlan { index, .. }) => CatalogItem::Index(Index {
1678 create_sql: index.create_sql,
1679 global_id,
1680 on: index.on,
1681 keys: index.keys.into(),
1682 conn_id: None,
1683 resolved_ids,
1684 cluster_id: index.cluster_id,
1685 custom_logical_compaction_window: custom_logical_compaction_window
1686 .or(index.compaction_window),
1687 is_retained_metrics_object,
1688 optimized_plan: None,
1689 physical_plan: None,
1690 dataflow_metainfo: None,
1691 }),
1692 Plan::CreateMetricSink(CreateMetricSinkPlan { metric_sink, .. }) => {
1693 CatalogItem::MetricSink(MetricSink {
1694 create_sql: metric_sink.create_sql,
1695 global_id,
1696 from: metric_sink.from,
1697 resolved_ids,
1698 cluster_id: metric_sink.cluster_id,
1699 prefix: metric_sink.prefix,
1700 optimized_plan: None,
1701 physical_plan: None,
1702 dataflow_metainfo: None,
1703 })
1704 }
1705 Plan::CreateSink(CreateSinkPlan {
1706 sink,
1707 with_snapshot,
1708 in_cluster,
1709 ..
1710 }) => CatalogItem::Sink(Sink {
1711 create_sql: sink.create_sql,
1712 global_id,
1713 from: sink.from,
1714 connection: sink.connection,
1715 envelope: sink.envelope,
1716 version: sink.version,
1717 with_snapshot,
1718 resolved_ids,
1719 cluster_id: in_cluster,
1720 commit_interval: sink.commit_interval,
1721 }),
1722 Plan::CreateType(CreateTypePlan { typ, .. }) => {
1723 if let Err(err) = typ.inner.desc(&session_catalog) {
1727 return Err((err.into(), cached_expr));
1728 }
1729 CatalogItem::Type(Type {
1730 create_sql: Some(typ.create_sql),
1731 global_id,
1732 details: CatalogTypeDetails {
1733 array_id: None,
1734 typ: typ.inner,
1735 pg_metadata: None,
1736 },
1737 resolved_ids,
1738 })
1739 }
1740 Plan::CreateSecret(CreateSecretPlan { secret, .. }) => CatalogItem::Secret(Secret {
1741 create_sql: secret.create_sql,
1742 global_id,
1743 }),
1744 Plan::CreateConnection(CreateConnectionPlan {
1745 connection:
1746 mz_sql::plan::Connection {
1747 create_sql,
1748 details,
1749 },
1750 ..
1751 }) => CatalogItem::Connection(Connection {
1752 create_sql,
1753 global_id,
1754 details,
1755 resolved_ids,
1756 }),
1757 _ => {
1758 return Err((
1759 Error::new(ErrorKind::Corruption {
1760 detail: "catalog entry generated inappropriate plan".to_string(),
1761 })
1762 .into(),
1763 cached_expr,
1764 ));
1765 }
1766 };
1767
1768 if let Some((prev_optimized, prev_physical, prev_metainfo)) = previous_plans {
1772 if let Some((optimized_plan, physical_plan, dataflow_metainfo)) = item.plan_fields_mut()
1773 {
1774 *optimized_plan = prev_optimized;
1775 *physical_plan = prev_physical;
1776 *dataflow_metainfo = prev_metainfo;
1777 }
1778 }
1779
1780 Ok((item, uncached_expr))
1781 }
1782
1783 pub fn with_enable_for_item_parsing<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
1789 let restore = Arc::clone(&self.system_configuration);
1805 Arc::make_mut(&mut self.system_configuration).enable_for_item_parsing();
1806 let res = f(self);
1807 self.system_configuration = restore;
1808 res
1809 }
1810
1811 pub fn get_indexes_on(
1813 &self,
1814 id: GlobalId,
1815 cluster: ClusterId,
1816 ) -> impl Iterator<Item = (GlobalId, &Index)> {
1817 let index_matches = move |idx: &Index| idx.on == id && idx.cluster_id == cluster;
1818
1819 self.try_get_entry_by_global_id(&id)
1820 .into_iter()
1821 .map(move |e| {
1822 e.used_by()
1823 .iter()
1824 .filter_map(move |uses_id| match self.get_entry(uses_id).item() {
1825 CatalogItem::Index(index) if index_matches(index) => {
1826 Some((index.global_id(), index))
1827 }
1828 _ => None,
1829 })
1830 })
1831 .flatten()
1832 }
1833
1834 pub(super) fn get_database(&self, database_id: &DatabaseId) -> &Database {
1835 &self.database_by_id[database_id]
1836 }
1837
1838 pub(super) fn try_get_cluster_replica(
1843 &self,
1844 id: ClusterId,
1845 replica_id: ReplicaId,
1846 ) -> Option<&ClusterReplica> {
1847 self.try_get_cluster(id)
1848 .and_then(|cluster| cluster.replica(replica_id))
1849 }
1850
1851 pub(crate) fn get_cluster_replica(
1855 &self,
1856 cluster_id: ClusterId,
1857 replica_id: ReplicaId,
1858 ) -> &ClusterReplica {
1859 self.try_get_cluster_replica(cluster_id, replica_id)
1860 .unwrap_or_else(|| panic!("unknown cluster replica: {cluster_id}.{replica_id}"))
1861 }
1862
1863 pub(super) fn resolve_replica_in_cluster(
1864 &self,
1865 cluster_id: &ClusterId,
1866 replica_name: &str,
1867 ) -> Result<&ClusterReplica, SqlCatalogError> {
1868 let cluster = self.get_cluster(*cluster_id);
1869 let replica_id = cluster
1870 .replica_id_by_name_
1871 .get(replica_name)
1872 .ok_or_else(|| SqlCatalogError::UnknownClusterReplica(replica_name.to_string()))?;
1873 Ok(&cluster.replicas_by_id_[replica_id])
1874 }
1875
1876 pub fn get_system_configuration(&self, name: &str) -> Result<&dyn Var, Error> {
1878 Ok(self.system_configuration.get(name)?)
1879 }
1880
1881 pub(super) fn parse_system_configuration(
1885 &self,
1886 name: &str,
1887 value: VarInput,
1888 ) -> Result<String, Error> {
1889 let value = self.system_configuration.parse(name, value)?;
1890 Ok(value.format())
1891 }
1892
1893 pub(super) fn resolve_schema_in_database(
1895 &self,
1896 database_spec: &ResolvedDatabaseSpecifier,
1897 schema_name: &str,
1898 conn_id: &ConnectionId,
1899 ) -> Result<&Schema, SqlCatalogError> {
1900 let schema = match database_spec {
1901 ResolvedDatabaseSpecifier::Ambient if schema_name == MZ_TEMP_SCHEMA => {
1902 self.temporary_namespaces.schema(conn_id)
1903 }
1904 ResolvedDatabaseSpecifier::Ambient => self
1905 .ambient_schemas_by_name
1906 .get(schema_name)
1907 .and_then(|id| self.ambient_schemas_by_id.get(id)),
1908 ResolvedDatabaseSpecifier::Id(id) => self.database_by_id.get(id).and_then(|db| {
1909 db.schemas_by_name
1910 .get(schema_name)
1911 .and_then(|id| db.schemas_by_id.get(id))
1912 }),
1913 };
1914 schema.ok_or_else(|| SqlCatalogError::UnknownSchema(schema_name.into()))
1915 }
1916
1917 pub fn try_get_schema(
1923 &self,
1924 database_spec: &ResolvedDatabaseSpecifier,
1925 schema_spec: &SchemaSpecifier,
1926 conn_id: &ConnectionId,
1927 ) -> Option<&Schema> {
1928 match (database_spec, schema_spec) {
1930 (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Temporary) => {
1931 self.temporary_namespaces.schema(conn_id)
1932 }
1933 (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Id(id)) => {
1934 self.ambient_schemas_by_id.get(id)
1935 }
1936 (ResolvedDatabaseSpecifier::Id(database_id), SchemaSpecifier::Id(schema_id)) => self
1937 .database_by_id
1938 .get(database_id)
1939 .and_then(|db| db.schemas_by_id.get(schema_id)),
1940 (ResolvedDatabaseSpecifier::Id(_), SchemaSpecifier::Temporary) => {
1941 unreachable!("temporary schemas are in the ambient database")
1942 }
1943 }
1944 }
1945
1946 pub fn get_schema(
1947 &self,
1948 database_spec: &ResolvedDatabaseSpecifier,
1949 schema_spec: &SchemaSpecifier,
1950 conn_id: &ConnectionId,
1951 ) -> &Schema {
1952 self.try_get_schema(database_spec, schema_spec, conn_id)
1954 .expect("schema must exist")
1955 }
1956
1957 pub(super) fn find_non_temp_schema(&self, schema_id: &SchemaId) -> &Schema {
1958 self.database_by_id
1959 .values()
1960 .filter_map(|database| database.schemas_by_id.get(schema_id))
1961 .chain(self.ambient_schemas_by_id.values())
1962 .filter(|schema| schema.id() == &SchemaSpecifier::from(*schema_id))
1963 .into_first()
1964 }
1965
1966 pub fn get_mz_catalog_schema_id(&self) -> SchemaId {
1967 self.ambient_schemas_by_name[MZ_CATALOG_SCHEMA]
1968 }
1969
1970 pub fn get_mz_catalog_unstable_schema_id(&self) -> SchemaId {
1971 self.ambient_schemas_by_name[MZ_CATALOG_UNSTABLE_SCHEMA]
1972 }
1973
1974 pub fn get_pg_catalog_schema_id(&self) -> SchemaId {
1975 self.ambient_schemas_by_name[PG_CATALOG_SCHEMA]
1976 }
1977
1978 pub fn get_information_schema_id(&self) -> SchemaId {
1979 self.ambient_schemas_by_name[INFORMATION_SCHEMA]
1980 }
1981
1982 pub fn get_mz_internal_schema_id(&self) -> SchemaId {
1983 self.ambient_schemas_by_name[MZ_INTERNAL_SCHEMA]
1984 }
1985
1986 pub fn get_mz_introspection_schema_id(&self) -> SchemaId {
1987 self.ambient_schemas_by_name[MZ_INTROSPECTION_SCHEMA]
1988 }
1989
1990 pub fn get_mz_unsafe_schema_id(&self) -> SchemaId {
1991 self.ambient_schemas_by_name[MZ_UNSAFE_SCHEMA]
1992 }
1993
1994 pub fn system_schema_ids(&self) -> impl Iterator<Item = SchemaId> + '_ {
1995 SYSTEM_SCHEMAS
1996 .iter()
1997 .map(|name| self.ambient_schemas_by_name[*name])
1998 }
1999
2000 pub fn is_system_schema_id(&self, id: SchemaId) -> bool {
2001 self.system_schema_ids().contains(&id)
2002 }
2003
2004 pub fn is_system_schema_specifier(&self, spec: SchemaSpecifier) -> bool {
2005 match spec {
2006 SchemaSpecifier::Temporary => false,
2007 SchemaSpecifier::Id(id) => self.is_system_schema_id(id),
2008 }
2009 }
2010
2011 pub fn unstable_schema_ids(&self) -> impl Iterator<Item = SchemaId> + '_ {
2012 UNSTABLE_SCHEMAS
2013 .iter()
2014 .map(|name| self.ambient_schemas_by_name[*name])
2015 }
2016
2017 pub fn is_unstable_schema_id(&self, id: SchemaId) -> bool {
2018 self.unstable_schema_ids().contains(&id)
2019 }
2020
2021 pub fn is_unstable_schema_specifier(&self, spec: SchemaSpecifier) -> bool {
2022 match spec {
2023 SchemaSpecifier::Temporary => false,
2024 SchemaSpecifier::Id(id) => self.is_unstable_schema_id(id),
2025 }
2026 }
2027
2028 pub(crate) fn get_temporary_oids(&self) -> impl Iterator<Item = u32> + '_ {
2030 std::iter::empty()
2031 .chain(self.ambient_schemas_by_id.values().filter_map(|schema| {
2032 if schema.id.is_temporary() {
2033 Some(schema.oid)
2034 } else {
2035 None
2036 }
2037 }))
2038 .chain(self.entry_by_id.values().filter_map(|entry| {
2039 if entry.item().is_temporary() {
2040 Some(entry.oid)
2041 } else {
2042 None
2043 }
2044 }))
2045 }
2046
2047 pub fn resolve_builtin_table(&self, builtin: &'static BuiltinTable) -> CatalogItemId {
2051 self.resolve_builtin_object(&Builtin::<IdReference>::Table(builtin))
2052 }
2053
2054 pub fn resolve_builtin_log(&self, builtin: &'static BuiltinLog) -> (CatalogItemId, GlobalId) {
2058 let item_id = self.resolve_builtin_object(&Builtin::<IdReference>::Log(builtin));
2059 let log = match self.get_entry(&item_id).item() {
2060 CatalogItem::Log(log) => log,
2061 other => unreachable!("programming error, expected BuiltinLog, found {other:?}"),
2062 };
2063 (item_id, log.global_id)
2064 }
2065
2066 pub fn resolve_builtin_source(&self, builtin: &'static BuiltinSource) -> CatalogItemId {
2070 self.resolve_builtin_object(&Builtin::<IdReference>::Source(builtin))
2071 }
2072
2073 pub fn resolve_builtin_object<T: TypeReference>(&self, builtin: &Builtin<T>) -> CatalogItemId {
2077 let schema_id = &self.ambient_schemas_by_name[builtin.schema()];
2078 let schema = &self.ambient_schemas_by_id[schema_id];
2079 match builtin.catalog_item_type() {
2080 CatalogItemType::Type => schema.types[builtin.name()],
2081 CatalogItemType::Func => schema.functions[builtin.name()],
2082 CatalogItemType::Table
2083 | CatalogItemType::Source
2084 | CatalogItemType::Sink
2085 | CatalogItemType::MetricSink
2086 | CatalogItemType::View
2087 | CatalogItemType::MaterializedView
2088 | CatalogItemType::Index
2089 | CatalogItemType::Secret
2090 | CatalogItemType::Connection => schema.items[builtin.name()],
2091 }
2092 }
2093
2094 pub fn resolve_builtin_type_references(
2096 &self,
2097 builtin: &BuiltinType<NameReference>,
2098 ) -> BuiltinType<IdReference> {
2099 let typ: CatalogType<IdReference> = match &builtin.details.typ {
2100 CatalogType::AclItem => CatalogType::AclItem,
2101 CatalogType::Array { element_reference } => CatalogType::Array {
2102 element_reference: self.get_system_type(element_reference).id,
2103 },
2104 CatalogType::List {
2105 element_reference,
2106 element_modifiers,
2107 } => CatalogType::List {
2108 element_reference: self.get_system_type(element_reference).id,
2109 element_modifiers: element_modifiers.clone(),
2110 },
2111 CatalogType::Map {
2112 key_reference,
2113 value_reference,
2114 key_modifiers,
2115 value_modifiers,
2116 } => CatalogType::Map {
2117 key_reference: self.get_system_type(key_reference).id,
2118 value_reference: self.get_system_type(value_reference).id,
2119 key_modifiers: key_modifiers.clone(),
2120 value_modifiers: value_modifiers.clone(),
2121 },
2122 CatalogType::Range { element_reference } => CatalogType::Range {
2123 element_reference: self.get_system_type(element_reference).id,
2124 },
2125 CatalogType::Record { fields } => CatalogType::Record {
2126 fields: fields
2127 .into_iter()
2128 .map(|f| CatalogRecordField {
2129 name: f.name.clone(),
2130 type_reference: self.get_system_type(f.type_reference).id,
2131 type_modifiers: f.type_modifiers.clone(),
2132 })
2133 .collect(),
2134 },
2135 CatalogType::Bool => CatalogType::Bool,
2136 CatalogType::Bytes => CatalogType::Bytes,
2137 CatalogType::Char => CatalogType::Char,
2138 CatalogType::Date => CatalogType::Date,
2139 CatalogType::Float32 => CatalogType::Float32,
2140 CatalogType::Float64 => CatalogType::Float64,
2141 CatalogType::Int16 => CatalogType::Int16,
2142 CatalogType::Int32 => CatalogType::Int32,
2143 CatalogType::Int64 => CatalogType::Int64,
2144 CatalogType::UInt16 => CatalogType::UInt16,
2145 CatalogType::UInt32 => CatalogType::UInt32,
2146 CatalogType::UInt64 => CatalogType::UInt64,
2147 CatalogType::MzTimestamp => CatalogType::MzTimestamp,
2148 CatalogType::Interval => CatalogType::Interval,
2149 CatalogType::Jsonb => CatalogType::Jsonb,
2150 CatalogType::Numeric => CatalogType::Numeric,
2151 CatalogType::Oid => CatalogType::Oid,
2152 CatalogType::PgLegacyChar => CatalogType::PgLegacyChar,
2153 CatalogType::PgLegacyName => CatalogType::PgLegacyName,
2154 CatalogType::Pseudo => CatalogType::Pseudo,
2155 CatalogType::RegClass => CatalogType::RegClass,
2156 CatalogType::RegProc => CatalogType::RegProc,
2157 CatalogType::RegType => CatalogType::RegType,
2158 CatalogType::String => CatalogType::String,
2159 CatalogType::Time => CatalogType::Time,
2160 CatalogType::Timestamp => CatalogType::Timestamp,
2161 CatalogType::TimestampTz => CatalogType::TimestampTz,
2162 CatalogType::Uuid => CatalogType::Uuid,
2163 CatalogType::VarChar => CatalogType::VarChar,
2164 CatalogType::Int2Vector => CatalogType::Int2Vector,
2165 CatalogType::MzAclItem => CatalogType::MzAclItem,
2166 };
2167
2168 BuiltinType {
2169 name: builtin.name,
2170 schema: builtin.schema,
2171 oid: builtin.oid,
2172 details: CatalogTypeDetails {
2173 array_id: builtin.details.array_id,
2174 typ,
2175 pg_metadata: builtin.details.pg_metadata.clone(),
2176 },
2177 }
2178 }
2179
2180 pub fn config(&self) -> &mz_sql::catalog::CatalogConfig {
2181 &self.config
2182 }
2183
2184 pub fn resolve_database(&self, database_name: &str) -> Result<&Database, SqlCatalogError> {
2185 match self.database_by_name.get(database_name) {
2186 Some(id) => Ok(&self.database_by_id[id]),
2187 None => Err(SqlCatalogError::UnknownDatabase(database_name.into())),
2188 }
2189 }
2190
2191 pub fn resolve_schema(
2192 &self,
2193 current_database: Option<&DatabaseId>,
2194 database_name: Option<&str>,
2195 schema_name: &str,
2196 conn_id: &ConnectionId,
2197 ) -> Result<&Schema, SqlCatalogError> {
2198 let database_spec = match database_name {
2199 Some(database) => Some(ResolvedDatabaseSpecifier::Id(
2204 self.resolve_database(database)?.id().clone(),
2205 )),
2206 None => current_database.map(|id| ResolvedDatabaseSpecifier::Id(id.clone())),
2207 };
2208
2209 if let Some(database_spec) = database_spec {
2211 if let Ok(schema) =
2212 self.resolve_schema_in_database(&database_spec, schema_name, conn_id)
2213 {
2214 return Ok(schema);
2215 }
2216 }
2217
2218 if let Ok(schema) = self.resolve_schema_in_database(
2220 &ResolvedDatabaseSpecifier::Ambient,
2221 schema_name,
2222 conn_id,
2223 ) {
2224 return Ok(schema);
2225 }
2226
2227 Err(SqlCatalogError::UnknownSchema(schema_name.into()))
2228 }
2229
2230 pub fn resolve_system_schema(&self, name: &'static str) -> SchemaId {
2234 self.ambient_schemas_by_name[name]
2235 }
2236
2237 pub fn resolve_search_path(
2238 &self,
2239 session: &dyn SessionMetadata,
2240 ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
2241 let database = self
2242 .database_by_name
2243 .get(session.database())
2244 .map(|id| id.clone());
2245
2246 session
2252 .search_path()
2253 .iter()
2254 .map(|schema| {
2255 self.resolve_schema(database.as_ref(), None, schema.as_str(), session.conn_id())
2256 })
2257 .filter_map(|schema| schema.ok())
2258 .map(|schema| (schema.name().database.clone(), schema.id().clone()))
2259 .collect()
2260 }
2261
2262 pub fn effective_search_path(
2263 &self,
2264 search_path: &[(ResolvedDatabaseSpecifier, SchemaSpecifier)],
2265 include_temp_schema: bool,
2266 ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
2267 let mut v = Vec::with_capacity(search_path.len() + 3);
2268 let temp_schema = (
2270 ResolvedDatabaseSpecifier::Ambient,
2271 SchemaSpecifier::Temporary,
2272 );
2273 if include_temp_schema && !search_path.contains(&temp_schema) {
2274 v.push(temp_schema);
2275 }
2276 let default_schemas = [
2277 (
2278 ResolvedDatabaseSpecifier::Ambient,
2279 SchemaSpecifier::Id(self.get_mz_catalog_schema_id()),
2280 ),
2281 (
2282 ResolvedDatabaseSpecifier::Ambient,
2283 SchemaSpecifier::Id(self.get_pg_catalog_schema_id()),
2284 ),
2285 ];
2286 for schema in default_schemas.into_iter() {
2287 if !search_path.contains(&schema) {
2288 v.push(schema);
2289 }
2290 }
2291 v.extend_from_slice(search_path);
2292 v
2293 }
2294
2295 pub fn resolve_cluster(&self, name: &str) -> Result<&Cluster, SqlCatalogError> {
2296 let id = self
2297 .clusters_by_name
2298 .get(name)
2299 .ok_or_else(|| SqlCatalogError::UnknownCluster(name.to_string()))?;
2300 Ok(&self.clusters_by_id[id])
2301 }
2302
2303 pub fn resolve_builtin_cluster(&self, cluster: &BuiltinCluster) -> &Cluster {
2304 let id = self
2305 .clusters_by_name
2306 .get(cluster.name)
2307 .expect("failed to lookup BuiltinCluster by name");
2308 self.clusters_by_id
2309 .get(id)
2310 .expect("failed to lookup BuiltinCluster by ID")
2311 }
2312
2313 pub fn resolve_cluster_replica(
2314 &self,
2315 cluster_replica_name: &QualifiedReplica,
2316 ) -> Result<&ClusterReplica, SqlCatalogError> {
2317 let cluster = self.resolve_cluster(cluster_replica_name.cluster.as_str())?;
2318 let replica_name = cluster_replica_name.replica.as_str();
2319 let replica_id = cluster
2320 .replica_id(replica_name)
2321 .ok_or_else(|| SqlCatalogError::UnknownClusterReplica(replica_name.to_string()))?;
2322 Ok(cluster.replica(replica_id).expect("Must exist"))
2323 }
2324
2325 #[allow(clippy::useless_let_if_seq)]
2331 pub fn resolve(
2332 &self,
2333 get_schema_entries: fn(&Schema) -> &BTreeMap<String, CatalogItemId>,
2334 current_database: Option<&DatabaseId>,
2335 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2336 name: &PartialItemName,
2337 conn_id: &ConnectionId,
2338 err_gen: fn(String) -> SqlCatalogError,
2339 ) -> Result<&CatalogEntry, SqlCatalogError> {
2340 let schemas = match &name.schema {
2345 Some(schema_name) => {
2346 match self.resolve_schema(
2347 current_database,
2348 name.database.as_deref(),
2349 schema_name,
2350 conn_id,
2351 ) {
2352 Ok(schema) => vec![(schema.name.database.clone(), schema.id.clone())],
2353 Err(e) => return Err(e),
2354 }
2355 }
2356 None => match self
2357 .try_get_schema(
2358 &ResolvedDatabaseSpecifier::Ambient,
2359 &SchemaSpecifier::Temporary,
2360 conn_id,
2361 )
2362 .and_then(|schema| schema.items.get(&name.item))
2363 {
2364 Some(id) => return Ok(self.get_entry(id)),
2365 None => search_path.to_vec(),
2366 },
2367 };
2368
2369 for (database_spec, schema_spec) in &schemas {
2370 let Some(schema) = self.try_get_schema(database_spec, schema_spec, conn_id) else {
2373 continue;
2374 };
2375
2376 if let Some(id) = get_schema_entries(schema).get(&name.item) {
2377 return Ok(&self.entry_by_id[id]);
2378 }
2379 }
2380
2381 let mz_internal_schema = SchemaSpecifier::Id(self.get_mz_internal_schema_id());
2386 if schemas.iter().any(|(_, spec)| *spec == mz_internal_schema) {
2387 for schema_id in [
2388 self.get_mz_catalog_unstable_schema_id(),
2389 self.get_mz_introspection_schema_id(),
2390 ] {
2391 let schema = self.get_schema(
2392 &ResolvedDatabaseSpecifier::Ambient,
2393 &SchemaSpecifier::Id(schema_id),
2394 conn_id,
2395 );
2396
2397 if let Some(id) = get_schema_entries(schema).get(&name.item) {
2398 debug!(
2399 github_27831 = true,
2400 "encountered use of outdated schema `mz_internal` for relation: {name}",
2401 );
2402 return Ok(&self.entry_by_id[id]);
2403 }
2404 }
2405 }
2406
2407 Err(err_gen(name.to_string()))
2408 }
2409
2410 pub fn resolve_entry(
2412 &self,
2413 current_database: Option<&DatabaseId>,
2414 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2415 name: &PartialItemName,
2416 conn_id: &ConnectionId,
2417 ) -> Result<&CatalogEntry, SqlCatalogError> {
2418 self.resolve(
2419 |schema| &schema.items,
2420 current_database,
2421 search_path,
2422 name,
2423 conn_id,
2424 SqlCatalogError::UnknownItem,
2425 )
2426 }
2427
2428 pub fn resolve_function(
2430 &self,
2431 current_database: Option<&DatabaseId>,
2432 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2433 name: &PartialItemName,
2434 conn_id: &ConnectionId,
2435 ) -> Result<&CatalogEntry, SqlCatalogError> {
2436 self.resolve(
2437 |schema| &schema.functions,
2438 current_database,
2439 search_path,
2440 name,
2441 conn_id,
2442 |name| SqlCatalogError::UnknownFunction {
2443 name,
2444 alternative: None,
2445 },
2446 )
2447 }
2448
2449 pub fn resolve_type(
2451 &self,
2452 current_database: Option<&DatabaseId>,
2453 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2454 name: &PartialItemName,
2455 conn_id: &ConnectionId,
2456 ) -> Result<&CatalogEntry, SqlCatalogError> {
2457 static NON_PG_CATALOG_TYPES: LazyLock<
2458 BTreeMap<&'static str, &'static BuiltinType<NameReference>>,
2459 > = LazyLock::new(|| {
2460 BUILTINS::types()
2461 .filter(|typ| typ.schema != PG_CATALOG_SCHEMA)
2462 .map(|typ| (typ.name, typ))
2463 .collect()
2464 });
2465
2466 let entry = self.resolve(
2467 |schema| &schema.types,
2468 current_database,
2469 search_path,
2470 name,
2471 conn_id,
2472 |name| SqlCatalogError::UnknownType { name },
2473 )?;
2474
2475 if conn_id != &SYSTEM_CONN_ID && name.schema.as_deref() == Some(PG_CATALOG_SCHEMA) {
2476 if let Some(typ) = NON_PG_CATALOG_TYPES.get(entry.name().item.as_str()) {
2477 warn!(
2478 "user specified an incorrect schema of {} for the type {}, which should be in \
2479 the {} schema. This works now due to a bug but will be fixed in a later release.",
2480 PG_CATALOG_SCHEMA.quoted(),
2481 typ.name.quoted(),
2482 typ.schema.quoted(),
2483 )
2484 }
2485 }
2486
2487 Ok(entry)
2488 }
2489
2490 pub(super) fn get_comment_id(&self, object_id: ObjectId) -> CommentObjectId {
2492 match object_id {
2493 ObjectId::Item(item_id) => self.get_entry(&item_id).comment_object_id(),
2494 ObjectId::Role(role_id) => CommentObjectId::Role(role_id),
2495 ObjectId::Database(database_id) => CommentObjectId::Database(database_id),
2496 ObjectId::Schema((database, schema)) => CommentObjectId::Schema((database, schema)),
2497 ObjectId::Cluster(cluster_id) => CommentObjectId::Cluster(cluster_id),
2498 ObjectId::ClusterReplica(cluster_replica_id) => {
2499 CommentObjectId::ClusterReplica(cluster_replica_id)
2500 }
2501 ObjectId::NetworkPolicy(network_policy_id) => {
2502 CommentObjectId::NetworkPolicy(network_policy_id)
2503 }
2504 }
2505 }
2506
2507 pub fn system_config(&self) -> &SystemVars {
2509 &self.system_configuration
2510 }
2511
2512 pub fn cluster_scoped_optimizer_overrides(
2516 &self,
2517 cluster_id: ClusterId,
2518 ) -> OptimizerFeatureOverrides {
2519 self.scoped_system_parameters
2520 .cluster
2521 .get(&cluster_id)
2522 .cloned()
2523 .map(OptimizerFeatureOverrides::from)
2524 .unwrap_or_default()
2525 }
2526
2527 pub fn scoped_system_parameters(&self) -> &ScopedParameters {
2530 &self.scoped_system_parameters
2531 }
2532
2533 pub fn system_config_mut(&mut self) -> &mut SystemVars {
2535 Arc::make_mut(&mut self.system_configuration)
2536 }
2537
2538 pub fn dump(&self, unfinalized_shards: Option<BTreeSet<String>>) -> Result<String, Error> {
2548 let mut dump = serde_json::to_value(&self).map_err(|e| {
2550 Error::new(ErrorKind::Unstructured(format!(
2551 "internal error: could not dump catalog: {}",
2554 e
2555 )))
2556 })?;
2557
2558 let dump_obj = dump.as_object_mut().expect("state must have been dumped");
2559 dump_obj.insert(
2561 "system_parameter_defaults".into(),
2562 serde_json::json!(self.system_config().defaults()),
2563 );
2564 if let Some(unfinalized_shards) = unfinalized_shards {
2566 dump_obj
2567 .get_mut("storage_metadata")
2568 .expect("known to exist")
2569 .as_object_mut()
2570 .expect("storage_metadata is an object")
2571 .insert(
2572 "unfinalized_shards".into(),
2573 serde_json::json!(unfinalized_shards),
2574 );
2575 }
2576 let temporary_gids: Vec<_> = self
2581 .entry_by_global_id
2582 .iter()
2583 .filter(|(_gid, item_id)| self.get_entry(item_id).conn_id().is_some())
2584 .map(|(gid, _item_id)| *gid)
2585 .collect();
2586 if !temporary_gids.is_empty() {
2587 let gids = dump_obj
2588 .get_mut("entry_by_global_id")
2589 .expect("known_to_exist")
2590 .as_object_mut()
2591 .expect("entry_by_global_id is an object");
2592 for gid in temporary_gids {
2593 gids.remove(&gid.to_string());
2594 }
2595 }
2596 dump_obj.remove("role_auth_by_id");
2599 dump_obj.remove("mock_authentication_nonce");
2604
2605 Ok(serde_json::to_string_pretty(&dump).expect("cannot fail on serde_json::Value"))
2607 }
2608
2609 pub fn availability_zones(&self) -> &[String] {
2610 &self.availability_zones
2611 }
2612
2613 pub fn concretize_replica_location(
2614 &self,
2615 location: mz_catalog::durable::ReplicaLocation,
2616 allowed_sizes: &Vec<String>,
2617 allowed_availability_zones: Option<&[String]>,
2618 allow_disabled: bool,
2619 ) -> Result<ReplicaLocation, Error> {
2620 let location = match location {
2621 mz_catalog::durable::ReplicaLocation::Unmanaged {
2622 storagectl_addrs,
2623 computectl_addrs,
2624 } => {
2625 if allowed_availability_zones.is_some() {
2626 return Err(Error {
2627 kind: ErrorKind::Internal(
2628 "tried concretize unmanaged replica with specific availability_zones"
2629 .to_string(),
2630 ),
2631 });
2632 }
2633 ReplicaLocation::Unmanaged(UnmanagedReplicaLocation {
2634 storagectl_addrs,
2635 computectl_addrs,
2636 })
2637 }
2638 mz_catalog::durable::ReplicaLocation::Managed {
2639 size,
2640 availability_zones,
2646 billed_as,
2647 internal,
2648 pending,
2649 } => {
2650 self.ensure_valid_replica_size(allowed_sizes, &size, allow_disabled)?;
2651 let cluster_replica_sizes = &self.cluster_replica_sizes;
2652
2653 ReplicaLocation::Managed(ManagedReplicaLocation {
2654 allocation: cluster_replica_sizes
2655 .0
2656 .get(&size)
2657 .expect("catalog out of sync")
2658 .clone(),
2659 availability_zones: match allowed_availability_zones {
2660 Some(azs) => azs.to_vec(),
2661 None => availability_zones,
2662 },
2663 size,
2664 billed_as,
2665 internal,
2666 pending,
2667 })
2668 }
2669 };
2670 Ok(location)
2671 }
2672
2673 pub(crate) fn ensure_valid_replica_size(
2674 &self,
2675 allowed_sizes: &[String],
2676 size: &String,
2677 allow_disabled: bool,
2678 ) -> Result<(), Error> {
2679 let cluster_replica_sizes = &self.cluster_replica_sizes;
2680
2681 if !cluster_replica_sizes.0.contains_key(size)
2682 || (!allowed_sizes.is_empty() && !allowed_sizes.contains(size))
2683 || (!allow_disabled && cluster_replica_sizes.0[size].disabled)
2684 {
2685 let mut entries = cluster_replica_sizes
2686 .enabled_allocations()
2687 .collect::<Vec<_>>();
2688
2689 if !allowed_sizes.is_empty() {
2690 let allowed_sizes = BTreeSet::<&String>::from_iter(allowed_sizes.iter());
2691 entries.retain(|(name, _)| allowed_sizes.contains(name));
2692 }
2693
2694 entries.sort_by_key(
2695 |(
2696 _name,
2697 ReplicaAllocation {
2698 scale, cpu_limit, ..
2699 },
2700 )| (scale, cpu_limit),
2701 );
2702
2703 Err(Error {
2704 kind: ErrorKind::InvalidClusterReplicaSize {
2705 size: size.to_owned(),
2706 expected: entries.into_iter().map(|(name, _)| name.clone()).collect(),
2707 },
2708 })
2709 } else {
2710 Ok(())
2711 }
2712 }
2713
2714 pub fn ensure_not_reserved_role(&self, role_id: &RoleId) -> Result<(), Error> {
2715 if role_id.is_builtin() {
2716 let role = self.get_role(role_id);
2717 Err(Error::new(ErrorKind::ReservedRoleName(
2718 role.name().to_string(),
2719 )))
2720 } else {
2721 Ok(())
2722 }
2723 }
2724
2725 pub fn ensure_not_reserved_network_policy(
2726 &self,
2727 network_policy_id: &NetworkPolicyId,
2728 ) -> Result<(), Error> {
2729 if network_policy_id.is_builtin() {
2730 let policy = self.get_network_policy(network_policy_id);
2731 Err(Error::new(ErrorKind::ReservedNetworkPolicyName(
2732 policy.name.clone(),
2733 )))
2734 } else {
2735 Ok(())
2736 }
2737 }
2738
2739 pub fn ensure_grantable_role(&self, role_id: &RoleId) -> Result<(), Error> {
2740 let is_grantable = !role_id.is_public() && !role_id.is_system();
2741 if is_grantable {
2742 Ok(())
2743 } else {
2744 let role = self.get_role(role_id);
2745 Err(Error::new(ErrorKind::UngrantableRoleName(
2746 role.name().to_string(),
2747 )))
2748 }
2749 }
2750
2751 pub fn ensure_not_system_role(&self, role_id: &RoleId) -> Result<(), Error> {
2752 if role_id.is_system() {
2753 let role = self.get_role(role_id);
2754 Err(Error::new(ErrorKind::ReservedSystemRoleName(
2755 role.name().to_string(),
2756 )))
2757 } else {
2758 Ok(())
2759 }
2760 }
2761
2762 pub fn ensure_not_predefined_role(&self, role_id: &RoleId) -> Result<(), Error> {
2763 if role_id.is_predefined() {
2764 let role = self.get_role(role_id);
2765 Err(Error::new(ErrorKind::ReservedSystemRoleName(
2766 role.name().to_string(),
2767 )))
2768 } else {
2769 Ok(())
2770 }
2771 }
2772
2773 pub(crate) fn add_to_audit_log(
2776 system_configuration: &SystemVars,
2777 oracle_write_ts: mz_repr::Timestamp,
2778 session: Option<&ConnMeta>,
2779 tx: &mut mz_catalog::durable::Transaction,
2780 audit_events: &mut Vec<VersionedEvent>,
2781 event_type: EventType,
2782 object_type: ObjectType,
2783 details: EventDetails,
2784 ) -> Result<(), Error> {
2785 let user = session.map(|session| session.user().name.to_string());
2786
2787 let occurred_at = match system_configuration.unsafe_mock_audit_event_timestamp() {
2790 Some(ts) => ts.into(),
2791 _ => oracle_write_ts.into(),
2792 };
2793 let id = tx.allocate_audit_log_id()?;
2794 let event = VersionedEvent::new(id, event_type, object_type, details, user, occurred_at);
2795 audit_events.push(event.clone());
2796 tx.insert_audit_log_event(event);
2797 Ok(())
2798 }
2799
2800 pub(super) fn get_owner_id(&self, id: &ObjectId, conn_id: &ConnectionId) -> Option<RoleId> {
2801 match id {
2802 ObjectId::Cluster(id) => Some(self.get_cluster(*id).owner_id()),
2803 ObjectId::ClusterReplica((cluster_id, replica_id)) => Some(
2804 self.get_cluster_replica(*cluster_id, *replica_id)
2805 .owner_id(),
2806 ),
2807 ObjectId::Database(id) => Some(self.get_database(id).owner_id()),
2808 ObjectId::Schema((database_spec, schema_spec)) => Some(
2809 self.get_schema(database_spec, schema_spec, conn_id)
2810 .owner_id(),
2811 ),
2812 ObjectId::Item(id) => Some(*self.get_entry(id).owner_id()),
2813 ObjectId::Role(_) => None,
2814 ObjectId::NetworkPolicy(id) => Some(self.get_network_policy(id).owner_id.clone()),
2815 }
2816 }
2817
2818 pub(super) fn get_object_type(&self, object_id: &ObjectId) -> mz_sql::catalog::ObjectType {
2819 match object_id {
2820 ObjectId::Cluster(_) => mz_sql::catalog::ObjectType::Cluster,
2821 ObjectId::ClusterReplica(_) => mz_sql::catalog::ObjectType::ClusterReplica,
2822 ObjectId::Database(_) => mz_sql::catalog::ObjectType::Database,
2823 ObjectId::Schema(_) => mz_sql::catalog::ObjectType::Schema,
2824 ObjectId::Role(_) => mz_sql::catalog::ObjectType::Role,
2825 ObjectId::Item(id) => self.get_entry(id).item_type().into(),
2826 ObjectId::NetworkPolicy(_) => mz_sql::catalog::ObjectType::NetworkPolicy,
2827 }
2828 }
2829
2830 pub(super) fn get_system_object_type(
2831 &self,
2832 id: &SystemObjectId,
2833 ) -> mz_sql::catalog::SystemObjectType {
2834 match id {
2835 SystemObjectId::Object(object_id) => {
2836 SystemObjectType::Object(self.get_object_type(object_id))
2837 }
2838 SystemObjectId::System => SystemObjectType::System,
2839 }
2840 }
2841
2842 pub fn storage_metadata(&self) -> &StorageMetadata {
2846 &self.storage_metadata
2847 }
2848
2849 pub fn source_compaction_windows(
2851 &self,
2852 ids: impl IntoIterator<Item = CatalogItemId>,
2853 ) -> BTreeMap<CompactionWindow, BTreeSet<CatalogItemId>> {
2854 let mut cws: BTreeMap<CompactionWindow, BTreeSet<CatalogItemId>> = BTreeMap::new();
2855 let mut seen = BTreeSet::new();
2856 for item_id in ids {
2857 if !seen.insert(item_id) {
2858 continue;
2859 }
2860 let entry = self.get_entry(&item_id);
2861 match entry.item() {
2862 CatalogItem::Source(source) => {
2863 let source_cw = source.custom_logical_compaction_window.unwrap_or_default();
2864 cws.entry(source_cw).or_default().insert(item_id);
2865 }
2866 CatalogItem::Table(table) => {
2867 let table_cw = table.custom_logical_compaction_window.unwrap_or_default();
2868 match &table.data_source {
2869 TableDataSource::DataSource {
2870 desc:
2871 DataSourceDesc::IngestionExport { .. }
2872 | DataSourceDesc::Webhook { .. },
2874 timeline: _,
2875 } => {
2876 cws.entry(table_cw).or_default().insert(item_id);
2877 }
2878 TableDataSource::TableWrites { .. } => {}
2881 TableDataSource::DataSource {
2882 desc:
2883 DataSourceDesc::Ingestion { .. }
2884 | DataSourceDesc::OldSyntaxIngestion { .. }
2885 | DataSourceDesc::Introspection(_)
2886 | DataSourceDesc::Progress
2887 | DataSourceDesc::Catalog,
2888 ..
2889 } => {
2890 unreachable!(
2891 "unexpected DataSourceDesc for table {item_id}: {:?}",
2892 table.data_source
2893 )
2894 }
2895 }
2896 }
2897 _ => {
2898 continue;
2900 }
2901 }
2902 }
2903 cws
2904 }
2905
2906 pub fn comment_id_to_item_id(id: &CommentObjectId) -> Option<CatalogItemId> {
2907 match id {
2908 CommentObjectId::Table(id)
2909 | CommentObjectId::View(id)
2910 | CommentObjectId::MaterializedView(id)
2911 | CommentObjectId::Source(id)
2912 | CommentObjectId::Sink(id)
2913 | CommentObjectId::MetricSink(id)
2914 | CommentObjectId::Index(id)
2915 | CommentObjectId::Func(id)
2916 | CommentObjectId::Connection(id)
2917 | CommentObjectId::Type(id)
2918 | CommentObjectId::Secret(id) => Some(*id),
2919 CommentObjectId::Role(_)
2920 | CommentObjectId::Database(_)
2921 | CommentObjectId::Schema(_)
2922 | CommentObjectId::Cluster(_)
2923 | CommentObjectId::ClusterReplica(_)
2924 | CommentObjectId::NetworkPolicy(_) => None,
2925 }
2926 }
2927
2928 pub fn get_comment_id_entry(&self, id: &CommentObjectId) -> Option<&CatalogEntry> {
2929 Self::comment_id_to_item_id(id).map(|id| self.get_entry(&id))
2930 }
2931
2932 pub fn comment_id_to_audit_log_name(
2933 &self,
2934 id: CommentObjectId,
2935 conn_id: &ConnectionId,
2936 ) -> String {
2937 match id {
2938 CommentObjectId::Table(id)
2939 | CommentObjectId::View(id)
2940 | CommentObjectId::MaterializedView(id)
2941 | CommentObjectId::Source(id)
2942 | CommentObjectId::Sink(id)
2943 | CommentObjectId::MetricSink(id)
2944 | CommentObjectId::Index(id)
2945 | CommentObjectId::Func(id)
2946 | CommentObjectId::Connection(id)
2947 | CommentObjectId::Type(id)
2948 | CommentObjectId::Secret(id) => {
2949 let item = self.get_entry(&id);
2950 let name = self.resolve_full_name(item.name(), Some(conn_id));
2951 name.to_string()
2952 }
2953 CommentObjectId::Role(id) => self.get_role(&id).name.clone(),
2954 CommentObjectId::Database(id) => self.get_database(&id).name.clone(),
2955 CommentObjectId::Schema((spec, schema_id)) => {
2956 let schema = self.get_schema(&spec, &schema_id, conn_id);
2957 self.resolve_full_schema_name(&schema.name).to_string()
2958 }
2959 CommentObjectId::Cluster(id) => self.get_cluster(id).name.clone(),
2960 CommentObjectId::ClusterReplica((cluster_id, replica_id)) => {
2961 let cluster = self.get_cluster(cluster_id);
2962 let replica = self.get_cluster_replica(cluster_id, replica_id);
2963 QualifiedReplica {
2964 cluster: Ident::new_unchecked(cluster.name.clone()),
2965 replica: Ident::new_unchecked(replica.name.clone()),
2966 }
2967 .to_string()
2968 }
2969 CommentObjectId::NetworkPolicy(id) => self.get_network_policy(&id).name.clone(),
2970 }
2971 }
2972
2973 pub fn mock_authentication_nonce(&self) -> String {
2974 self.mock_authentication_nonce.clone().unwrap_or_default()
2975 }
2976}
2977
2978impl ConnectionResolver for CatalogState {
2979 fn resolve_connection(
2980 &self,
2981 id: CatalogItemId,
2982 ) -> mz_storage_types::connections::Connection<InlinedConnection> {
2983 use mz_storage_types::connections::Connection::*;
2984 match self
2985 .get_entry(&id)
2986 .connection()
2987 .expect("catalog out of sync")
2988 .details
2989 .to_connection()
2990 {
2991 Kafka(conn) => Kafka(conn.into_inline_connection(self)),
2992 Postgres(conn) => Postgres(conn.into_inline_connection(self)),
2993 Csr(conn) => Csr(conn.into_inline_connection(self)),
2994 GlueSchemaRegistry(conn) => GlueSchemaRegistry(conn.into_inline_connection(self)),
2995 Ssh(conn) => Ssh(conn),
2996 Aws(conn) => Aws(conn),
2997 AwsPrivatelink(conn) => AwsPrivatelink(conn),
2998 Gcp(conn) => Gcp(conn),
2999 MySql(conn) => MySql(conn.into_inline_connection(self)),
3000 SqlServer(conn) => SqlServer(conn.into_inline_connection(self)),
3001 IcebergCatalog(conn) => IcebergCatalog(conn.into_inline_connection(self)),
3002 }
3003 }
3004}
3005
3006impl OptimizerCatalog for CatalogState {
3007 fn get_entry(&self, id: &GlobalId) -> CatalogCollectionEntry {
3008 CatalogState::get_entry_by_global_id(self, id)
3009 }
3010 fn get_entry_by_item_id(&self, id: &CatalogItemId) -> &CatalogEntry {
3011 CatalogState::get_entry(self, id)
3012 }
3013 fn resolve_full_name(
3014 &self,
3015 name: &QualifiedItemName,
3016 conn_id: Option<&ConnectionId>,
3017 ) -> FullItemName {
3018 CatalogState::resolve_full_name(self, name, conn_id)
3019 }
3020 fn get_indexes_on(
3021 &self,
3022 id: GlobalId,
3023 cluster: ClusterId,
3024 ) -> Box<dyn Iterator<Item = (GlobalId, &Index)> + '_> {
3025 Box::new(CatalogState::get_indexes_on(self, id, cluster))
3026 }
3027}
3028
3029impl OptimizerCatalog for Catalog {
3030 fn get_entry(&self, id: &GlobalId) -> CatalogCollectionEntry {
3031 self.state.get_entry_by_global_id(id)
3032 }
3033
3034 fn get_entry_by_item_id(&self, id: &CatalogItemId) -> &CatalogEntry {
3035 self.state.get_entry(id)
3036 }
3037
3038 fn resolve_full_name(
3039 &self,
3040 name: &QualifiedItemName,
3041 conn_id: Option<&ConnectionId>,
3042 ) -> FullItemName {
3043 self.state.resolve_full_name(name, conn_id)
3044 }
3045
3046 fn get_indexes_on(
3047 &self,
3048 id: GlobalId,
3049 cluster: ClusterId,
3050 ) -> Box<dyn Iterator<Item = (GlobalId, &Index)> + '_> {
3051 Box::new(self.state.get_indexes_on(id, cluster))
3052 }
3053}
3054
3055impl Catalog {
3056 pub fn as_optimizer_catalog(self: Arc<Self>) -> Arc<dyn OptimizerCatalog> {
3057 self
3058 }
3059}
3060
3061#[cfg(test)]
3062mod tests {
3063 use super::*;
3064
3065 #[mz_ore::test(tokio::test)]
3070 #[cfg_attr(miri, ignore)] async fn item_dependents_deep_chain_no_stack_overflow() {
3072 use mz_ore::cast::CastFrom;
3073
3074 Catalog::with_debug(|mut catalog| async move {
3075 const DEPTH: usize = 100_000;
3078 const BASE: u64 = 1 << 40;
3081
3082 let template = catalog
3086 .state
3087 .entry_by_id
3088 .values()
3089 .find(|entry| matches!(entry.item(), CatalogItem::Log(_)))
3090 .expect("debug catalog has log sources")
3091 .clone();
3092
3093 for i in 0..=DEPTH {
3095 let id = CatalogItemId::User(BASE + u64::cast_from(i));
3096 let mut entry = template.clone();
3097 entry.id = id;
3098 entry.referenced_by = Vec::new();
3099 entry.used_by = if i < DEPTH {
3100 vec![CatalogItemId::User(BASE + u64::cast_from(i + 1))]
3101 } else {
3102 Vec::new()
3103 };
3104 catalog.state.entry_by_id.insert(id, entry);
3105 }
3106
3107 let mut seen = BTreeSet::new();
3108 let dependents = catalog
3109 .state
3110 .item_dependents(CatalogItemId::User(BASE), &mut seen);
3111
3112 assert_eq!(dependents.len(), DEPTH + 1);
3114 for (offset, dependent) in dependents.iter().enumerate() {
3117 let expected = CatalogItemId::User(BASE + u64::cast_from(DEPTH - offset));
3118 assert_eq!(dependent, &ObjectId::Item(expected));
3119 }
3120
3121 catalog.expire().await;
3122 })
3123 .await
3124 }
3125
3126 #[mz_ore::test(tokio::test)]
3131 #[cfg_attr(miri, ignore)] async fn validate_read_then_write_deep_chain_no_stack_overflow() {
3133 use crate::coord::read_then_write::{
3134 DependencyPolicy, validate_read_then_write_dependencies,
3135 };
3136
3137 Catalog::with_debug(|mut catalog| async move {
3138 const DEPTH: usize = 100_000;
3141 const BASE: u64 = 1 << 40;
3142 insert_synthetic_view_chain(&mut catalog, BASE, DEPTH);
3143
3144 validate_read_then_write_dependencies(
3147 &catalog,
3148 [CatalogItemId::User(BASE)],
3149 usize::MAX,
3150 DependencyPolicy::UserDml,
3151 )
3152 .expect("deep chain of user views is valid for read-then-write");
3153
3154 catalog.expire().await;
3155 })
3156 .await
3157 }
3158
3159 #[mz_ore::test(tokio::test)]
3163 #[cfg_attr(miri, ignore)] async fn validate_read_then_write_dependency_limit() {
3165 use crate::coord::read_then_write::{
3166 DependencyPolicy, validate_read_then_write_dependencies,
3167 };
3168 use crate::error::AdapterError;
3169
3170 Catalog::with_debug(|mut catalog| async move {
3171 const DEPTH: usize = 100;
3172 const BASE: u64 = 1 << 40;
3173 insert_synthetic_view_chain(&mut catalog, BASE, DEPTH);
3174
3175 const OBJECTS: usize = DEPTH + 1;
3177
3178 validate_read_then_write_dependencies(
3180 &catalog,
3181 [CatalogItemId::User(BASE)],
3182 OBJECTS,
3183 DependencyPolicy::UserDml,
3184 )
3185 .expect("chain at the limit is valid");
3186
3187 let err = validate_read_then_write_dependencies(
3189 &catalog,
3190 [CatalogItemId::User(BASE)],
3191 OBJECTS - 1,
3192 DependencyPolicy::UserDml,
3193 )
3194 .expect_err("chain over the limit is rejected");
3195 assert!(matches!(
3196 err,
3197 AdapterError::ReadThenWriteDependencyLimitExceeded {
3198 max_rw_dependencies
3199 } if max_rw_dependencies == OBJECTS - 1
3200 ));
3201
3202 catalog.expire().await;
3203 })
3204 .await
3205 }
3206
3207 fn insert_synthetic_view_chain(catalog: &mut Catalog, base: u64, depth: usize) {
3216 use mz_ore::cast::CastFrom;
3217 use mz_sql::names::{DependencyIds, ResolvedIds};
3218
3219 let template = catalog
3220 .state
3221 .entry_by_id
3222 .values()
3223 .find(|entry| matches!(entry.item(), CatalogItem::View(_)))
3224 .expect("debug catalog has builtin views")
3225 .clone();
3226
3227 for i in 0..=depth {
3230 let id = CatalogItemId::User(base + u64::cast_from(i));
3231 let mut entry = template.clone();
3232 entry.id = id;
3233 entry.referenced_by = Vec::new();
3234 entry.used_by = Vec::new();
3235 let mut resolved_ids = ResolvedIds::empty();
3236 if i < depth {
3237 resolved_ids.add_item(CatalogItemId::User(base + u64::cast_from(i + 1)));
3238 }
3239 match &mut entry.item {
3240 CatalogItem::View(view) => {
3241 view.resolved_ids = resolved_ids;
3242 view.dependencies = DependencyIds(BTreeSet::new());
3243 }
3244 _ => unreachable!("template is a view"),
3245 }
3246 catalog.state.entry_by_id.insert(id, entry);
3247 }
3248 }
3249}