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