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,
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, CreateSecretPlan,
78 CreateSinkPlan, CreateSourcePlan, CreateTablePlan, CreateTypePlan, CreateViewPlan, Params,
79 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};
96
97use crate::AdapterError;
99use crate::catalog::{Catalog, ConnCatalog};
100use crate::config::ScopedParameters;
101use crate::coord::{ConnMeta, infer_sql_type_for_catalog};
102use crate::optimize::{self, Optimize, OptimizerCatalog};
103use crate::session::Session;
104
105#[derive(Debug, Clone, Serialize)]
112pub struct CatalogState {
113 pub(super) database_by_name: imbl::OrdMap<String, DatabaseId>,
119 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
120 pub(super) database_by_id: imbl::OrdMap<DatabaseId, Database>,
121 #[serde(serialize_with = "skip_temp_items")]
122 pub(super) entry_by_id: imbl::OrdMap<CatalogItemId, CatalogEntry>,
123 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
124 pub(super) entry_by_global_id: imbl::OrdMap<GlobalId, CatalogItemId>,
125 pub(super) ambient_schemas_by_name: imbl::OrdMap<String, SchemaId>,
126 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
127 pub(super) ambient_schemas_by_id: imbl::OrdMap<SchemaId, Schema>,
128 pub(super) clusters_by_name: imbl::OrdMap<String, ClusterId>,
129 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
130 pub(super) clusters_by_id: imbl::OrdMap<ClusterId, Cluster>,
131 pub(super) roles_by_name: imbl::OrdMap<String, RoleId>,
132 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
133 pub(super) roles_by_id: imbl::OrdMap<RoleId, Role>,
134 pub(super) network_policies_by_name: imbl::OrdMap<String, NetworkPolicyId>,
135 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
136 pub(super) network_policies_by_id: imbl::OrdMap<NetworkPolicyId, NetworkPolicy>,
137 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
138 pub(super) role_auth_by_id: imbl::OrdMap<RoleId, RoleAuth>,
139
140 #[serde(skip)]
141 pub(super) system_configuration: Arc<SystemVars>,
142 #[serde(skip)]
150 pub(super) scoped_system_parameters: ScopedParameters,
151 pub(super) default_privileges: Arc<DefaultPrivileges>,
152 pub(super) system_privileges: Arc<PrivilegeMap>,
153 pub(super) comments: Arc<CommentsMap>,
154 #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
155 pub(super) source_references: imbl::OrdMap<CatalogItemId, SourceReferences>,
156 pub(super) storage_metadata: Arc<StorageMetadata>,
157 pub(super) mock_authentication_nonce: Option<String>,
158
159 #[serde(skip)]
164 pub(super) notices_by_dep_id: imbl::OrdMap<GlobalId, Vec<Arc<OptimizerNotice>>>,
165
166 #[serde(skip)]
170 pub(super) temporary_schemas: imbl::OrdMap<ConnectionId, Schema>,
171
172 #[serde(skip)]
174 pub(super) config: mz_sql::catalog::CatalogConfig,
175 pub(super) cluster_replica_sizes: ClusterReplicaSizeMap,
176 #[serde(skip)]
177 pub(crate) availability_zones: Vec<String>,
178
179 #[serde(skip)]
181 pub(super) egress_addresses: Vec<IpNet>,
182 pub(super) aws_principal_context: Option<AwsPrincipalContext>,
183 pub(super) aws_privatelink_availability_zones: Option<BTreeSet<String>>,
184 pub(super) http_host_name: Option<String>,
185
186 #[serde(skip)]
188 pub(super) license_key: ValidatedLicenseKey,
189}
190
191#[derive(Debug, Clone, Serialize)]
196pub(crate) enum LocalExpressionCache {
197 Open {
199 cached_exprs: BTreeMap<GlobalId, LocalExpressions>,
201 uncached_exprs: BTreeMap<GlobalId, LocalExpressions>,
203 },
204 Closed,
206}
207
208impl LocalExpressionCache {
209 pub(super) fn new(cached_exprs: BTreeMap<GlobalId, LocalExpressions>) -> Self {
210 Self::Open {
211 cached_exprs,
212 uncached_exprs: BTreeMap::new(),
213 }
214 }
215
216 pub(super) fn remove_cached_expression(&mut self, id: &GlobalId) -> Option<LocalExpressions> {
217 match self {
218 LocalExpressionCache::Open { cached_exprs, .. } => cached_exprs.remove(id),
219 LocalExpressionCache::Closed => None,
220 }
221 }
222
223 pub(super) fn insert_cached_expression(
226 &mut self,
227 id: GlobalId,
228 local_expressions: LocalExpressions,
229 ) {
230 match self {
231 LocalExpressionCache::Open { cached_exprs, .. } => {
232 cached_exprs.insert(id, local_expressions);
233 }
234 LocalExpressionCache::Closed => {}
235 }
236 }
237
238 pub(super) fn insert_uncached_expression(
241 &mut self,
242 id: GlobalId,
243 local_mir: OptimizedMirRelationExpr,
244 optimizer_features: OptimizerFeatures,
245 ) {
246 match self {
247 LocalExpressionCache::Open { uncached_exprs, .. } => {
248 let local_expr = LocalExpressions {
249 local_mir,
250 optimizer_features,
251 };
252 let prev = uncached_exprs.remove(&id);
259 match prev {
260 Some(prev) if prev == local_expr => {
261 uncached_exprs.insert(id, local_expr);
262 }
263 None => {
264 uncached_exprs.insert(id, local_expr);
265 }
266 Some(_) => {}
267 }
268 }
269 LocalExpressionCache::Closed => {}
270 }
271 }
272
273 pub(super) fn into_uncached_exprs(self) -> BTreeMap<GlobalId, LocalExpressions> {
274 match self {
275 LocalExpressionCache::Open { uncached_exprs, .. } => uncached_exprs,
276 LocalExpressionCache::Closed => BTreeMap::new(),
277 }
278 }
279}
280
281fn skip_temp_items<S>(
282 entries: &imbl::OrdMap<CatalogItemId, CatalogEntry>,
283 serializer: S,
284) -> Result<S::Ok, S::Error>
285where
286 S: serde::Serializer,
287{
288 mz_ore::serde::map_key_to_string(
289 entries.iter().filter(|(_k, v)| v.conn_id().is_none()),
290 serializer,
291 )
292}
293
294impl CatalogState {
295 pub fn empty_test() -> Self {
299 CatalogState {
300 database_by_name: Default::default(),
301 database_by_id: Default::default(),
302 entry_by_id: Default::default(),
303 entry_by_global_id: Default::default(),
304 notices_by_dep_id: Default::default(),
305 ambient_schemas_by_name: Default::default(),
306 ambient_schemas_by_id: Default::default(),
307 temporary_schemas: Default::default(),
308 clusters_by_id: Default::default(),
309 clusters_by_name: Default::default(),
310 network_policies_by_name: Default::default(),
311 roles_by_name: Default::default(),
312 roles_by_id: Default::default(),
313 network_policies_by_id: Default::default(),
314 role_auth_by_id: Default::default(),
315 config: CatalogConfig {
316 start_time: Default::default(),
317 start_instant: Instant::now(),
318 nonce: Default::default(),
319 environment_id: EnvironmentId::for_tests(),
320 session_id: Default::default(),
321 build_info: &DUMMY_BUILD_INFO,
322 now: NOW_ZERO.clone(),
323 connection_context: ConnectionContext::for_tests(Arc::new(
324 InMemorySecretsController::new(),
325 )),
326 helm_chart_version: None,
327 },
328 cluster_replica_sizes: ClusterReplicaSizeMap::for_tests(),
329 availability_zones: Default::default(),
330 system_configuration: Arc::new(SystemVars::default()),
331 scoped_system_parameters: Default::default(),
332 egress_addresses: Default::default(),
333 aws_principal_context: Default::default(),
334 aws_privatelink_availability_zones: Default::default(),
335 http_host_name: Default::default(),
336 default_privileges: Arc::new(DefaultPrivileges::default()),
337 system_privileges: Arc::new(PrivilegeMap::default()),
338 comments: Arc::new(CommentsMap::default()),
339 source_references: Default::default(),
340 storage_metadata: Arc::new(StorageMetadata::default()),
341 license_key: ValidatedLicenseKey::for_tests(),
342 mock_authentication_nonce: Default::default(),
343 }
344 }
345
346 pub fn for_session<'a>(&'a self, session: &'a Session) -> ConnCatalog<'a> {
347 let search_path = self.resolve_search_path(session);
348 let database = self
349 .database_by_name
350 .get(session.vars().database())
351 .map(|id| id.clone());
352 let state = match session.transaction().catalog_state() {
353 Some(txn_catalog_state) => Cow::Borrowed(txn_catalog_state),
354 None => Cow::Borrowed(self),
355 };
356 ConnCatalog {
357 state,
358 unresolvable_ids: BTreeSet::new(),
359 conn_id: session.conn_id().clone(),
360 cluster: session.vars().cluster().into(),
361 database,
362 search_path,
363 role_id: session.current_role_id().clone(),
364 prepared_statements: Some(session.prepared_statements()),
365 portals: Some(session.portals()),
366 notices_tx: session.retain_notice_transmitter(),
367 restrict_to_user_objects: session.vars().restrict_to_user_objects(),
368 }
369 }
370
371 pub fn for_sessionless_user(&self, role_id: RoleId) -> ConnCatalog<'_> {
372 let (notices_tx, _notices_rx) = mpsc::unbounded_channel();
373 let cluster = self.system_configuration.default_cluster();
374
375 ConnCatalog {
376 state: Cow::Borrowed(self),
377 unresolvable_ids: BTreeSet::new(),
378 conn_id: SYSTEM_CONN_ID.clone(),
379 cluster,
380 database: self
381 .resolve_database(DEFAULT_DATABASE_NAME)
382 .ok()
383 .map(|db| db.id()),
384 search_path: Vec::new(),
387 role_id,
388 prepared_statements: None,
389 portals: None,
390 notices_tx,
391 restrict_to_user_objects: false,
392 }
393 }
394
395 pub fn for_system_session(&self) -> ConnCatalog<'_> {
396 self.for_sessionless_user(MZ_SYSTEM_ROLE_ID)
397 }
398
399 pub fn transitive_uses(&self, id: CatalogItemId) -> impl Iterator<Item = CatalogItemId> + '_ {
404 struct I<'a> {
405 queue: VecDeque<CatalogItemId>,
406 seen: BTreeSet<CatalogItemId>,
407 this: &'a CatalogState,
408 }
409 impl<'a> Iterator for I<'a> {
410 type Item = CatalogItemId;
411 fn next(&mut self) -> Option<Self::Item> {
412 if let Some(next) = self.queue.pop_front() {
413 for child in self.this.get_entry(&next).item().uses() {
414 if !self.seen.contains(&child) {
415 self.queue.push_back(child);
416 self.seen.insert(child);
417 }
418 }
419 Some(next)
420 } else {
421 None
422 }
423 }
424 }
425
426 I {
427 queue: [id].into_iter().collect(),
428 seen: [id].into_iter().collect(),
429 this: self,
430 }
431 }
432
433 pub fn introspection_dependencies(&self, id: CatalogItemId) -> Vec<CatalogItemId> {
436 let mut out = Vec::new();
437
438 let mut queue: VecDeque<CatalogItemId> = [id].into_iter().collect();
441 let mut seen: BTreeSet<CatalogItemId> = [id].into_iter().collect();
442 while let Some(id) = queue.pop_front() {
443 match self.get_entry(&id).item() {
444 CatalogItem::Log(_) => out.push(id),
445 item @ (CatalogItem::View(_)
446 | CatalogItem::MaterializedView(_)
447 | CatalogItem::Connection(_)) => {
448 for item_id in item.references().items() {
450 if seen.insert(*item_id) {
451 queue.push_back(*item_id);
452 }
453 }
454 }
455 CatalogItem::Sink(sink) => {
456 let from_item_id = self.get_entry_by_global_id(&sink.from).id();
457 if seen.insert(from_item_id) {
458 queue.push_back(from_item_id);
459 }
460 }
461 CatalogItem::Index(idx) => {
462 let on_item_id = self.get_entry_by_global_id(&idx.on).id();
463 if seen.insert(on_item_id) {
464 queue.push_back(on_item_id);
465 }
466 }
467 CatalogItem::Table(_)
468 | CatalogItem::Source(_)
469 | CatalogItem::Type(_)
470 | CatalogItem::Func(_)
471 | CatalogItem::Secret(_) => (),
472 }
473 }
474
475 out
476 }
477
478 pub(super) fn object_dependents(
484 &self,
485 object_ids: &Vec<ObjectId>,
486 conn_id: &ConnectionId,
487 seen: &mut BTreeSet<ObjectId>,
488 ) -> Vec<ObjectId> {
489 let mut dependents = Vec::new();
490 for object_id in object_ids {
491 match object_id {
492 ObjectId::Cluster(id) => {
493 dependents.extend_from_slice(&self.cluster_dependents(*id, seen));
494 }
495 ObjectId::ClusterReplica((cluster_id, replica_id)) => dependents.extend_from_slice(
496 &self.cluster_replica_dependents(*cluster_id, *replica_id, seen),
497 ),
498 ObjectId::Database(id) => {
499 dependents.extend_from_slice(&self.database_dependents(*id, conn_id, seen))
500 }
501 ObjectId::Schema((database_spec, schema_spec)) => {
502 dependents.extend_from_slice(&self.schema_dependents(
503 database_spec.clone(),
504 schema_spec.clone(),
505 conn_id,
506 seen,
507 ));
508 }
509 ObjectId::NetworkPolicy(id) => {
510 dependents.extend_from_slice(&self.network_policy_dependents(*id, seen));
511 }
512 id @ ObjectId::Role(_) => {
513 let unseen = seen.insert(id.clone());
514 if unseen {
515 dependents.push(id.clone());
516 }
517 }
518 ObjectId::Item(id) => {
519 dependents.extend_from_slice(&self.item_dependents(*id, seen))
520 }
521 }
522 }
523 dependents
524 }
525
526 fn cluster_dependents(
533 &self,
534 cluster_id: ClusterId,
535 seen: &mut BTreeSet<ObjectId>,
536 ) -> Vec<ObjectId> {
537 let mut dependents = Vec::new();
538 let object_id = ObjectId::Cluster(cluster_id);
539 if !seen.contains(&object_id) {
540 seen.insert(object_id.clone());
541 let cluster = self.get_cluster(cluster_id);
542 for item_id in cluster.bound_objects() {
543 dependents.extend_from_slice(&self.item_dependents(*item_id, seen));
544 }
545 for replica_id in cluster.replica_ids().values() {
546 dependents.extend_from_slice(&self.cluster_replica_dependents(
547 cluster_id,
548 *replica_id,
549 seen,
550 ));
551 }
552 dependents.push(object_id);
553 }
554 dependents
555 }
556
557 pub(super) fn cluster_replica_dependents(
564 &self,
565 cluster_id: ClusterId,
566 replica_id: ReplicaId,
567 seen: &mut BTreeSet<ObjectId>,
568 ) -> Vec<ObjectId> {
569 let mut dependents = Vec::new();
570 let object_id = ObjectId::ClusterReplica((cluster_id, replica_id));
571 if !seen.contains(&object_id) {
572 seen.insert(object_id.clone());
573 let cluster = self.get_cluster(cluster_id);
577 for item_id in cluster.bound_objects() {
578 if let CatalogItem::MaterializedView(mv) = self.get_entry(item_id).item()
579 && mv.target_replica == Some(replica_id)
580 {
581 dependents.extend_from_slice(&self.item_dependents(*item_id, seen));
582 }
583 }
584 dependents.push(object_id);
585 }
586 dependents
587 }
588
589 fn database_dependents(
596 &self,
597 database_id: DatabaseId,
598 conn_id: &ConnectionId,
599 seen: &mut BTreeSet<ObjectId>,
600 ) -> Vec<ObjectId> {
601 let mut dependents = Vec::new();
602 let object_id = ObjectId::Database(database_id);
603 if !seen.contains(&object_id) {
604 seen.insert(object_id.clone());
605 let database = self.get_database(&database_id);
606 for schema_id in database.schema_ids().values() {
607 dependents.extend_from_slice(&self.schema_dependents(
608 ResolvedDatabaseSpecifier::Id(database_id),
609 SchemaSpecifier::Id(*schema_id),
610 conn_id,
611 seen,
612 ));
613 }
614 dependents.push(object_id);
615 }
616 dependents
617 }
618
619 fn schema_dependents(
626 &self,
627 database_spec: ResolvedDatabaseSpecifier,
628 schema_spec: SchemaSpecifier,
629 conn_id: &ConnectionId,
630 seen: &mut BTreeSet<ObjectId>,
631 ) -> Vec<ObjectId> {
632 let mut dependents = Vec::new();
633 let object_id = ObjectId::Schema((database_spec, schema_spec.clone()));
634 if !seen.contains(&object_id) {
635 seen.insert(object_id.clone());
636 let schema = self.get_schema(&database_spec, &schema_spec, conn_id);
637 for item_id in schema.item_ids() {
638 dependents.extend_from_slice(&self.item_dependents(item_id, seen));
639 }
640 dependents.push(object_id)
641 }
642 dependents
643 }
644
645 pub(super) fn item_dependents(
652 &self,
653 item_id: CatalogItemId,
654 seen: &mut BTreeSet<ObjectId>,
655 ) -> Vec<ObjectId> {
656 let mut dependents = Vec::new();
657
658 enum Work {
662 Enter(CatalogItemId),
663 Emit(ObjectId),
664 }
665 let mut stack = vec![Work::Enter(item_id)];
666 while let Some(work) = stack.pop() {
667 match work {
668 Work::Enter(item_id) => {
669 let object_id = ObjectId::Item(item_id);
670 if !seen.insert(object_id.clone()) {
671 continue;
672 }
673 let entry = self.get_entry(&item_id);
674 if let Some(progress_id) = entry.progress_id() {
681 stack.push(Work::Enter(progress_id));
682 }
683 stack.push(Work::Emit(object_id));
684 for dependent_id in entry.used_by().iter().rev() {
685 stack.push(Work::Enter(*dependent_id));
686 }
687 }
688 Work::Emit(object_id) => dependents.push(object_id),
689 }
690 }
691
692 dependents
693 }
694
695 pub(super) fn network_policy_dependents(
702 &self,
703 network_policy_id: NetworkPolicyId,
704 _seen: &mut BTreeSet<ObjectId>,
705 ) -> Vec<ObjectId> {
706 let object_id = ObjectId::NetworkPolicy(network_policy_id);
707 vec![object_id]
711 }
712
713 fn is_stable(&self, id: CatalogItemId) -> bool {
717 let spec = self.get_entry(&id).name().qualifiers.schema_spec;
718 !self.is_unstable_schema_specifier(spec)
719 }
720
721 pub(super) fn check_unstable_dependencies(&self, item: &CatalogItem) -> Result<(), Error> {
722 if self.system_config().unsafe_enable_unstable_dependencies() {
723 return Ok(());
724 }
725
726 let unstable_dependencies: Vec<_> = item
727 .references()
728 .items()
729 .filter(|id| !self.is_stable(**id))
730 .map(|id| self.get_entry(id).name().item.clone())
731 .collect();
732
733 if unstable_dependencies.is_empty() || item.is_temporary() {
737 Ok(())
738 } else {
739 let object_type = item.typ().to_string();
740 Err(Error {
741 kind: ErrorKind::UnstableDependency {
742 object_type,
743 unstable_dependencies,
744 },
745 })
746 }
747 }
748
749 pub fn resolve_full_name(
750 &self,
751 name: &QualifiedItemName,
752 conn_id: Option<&ConnectionId>,
753 ) -> FullItemName {
754 let conn_id = conn_id.unwrap_or(&SYSTEM_CONN_ID);
755
756 let database = match &name.qualifiers.database_spec {
757 ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
758 ResolvedDatabaseSpecifier::Id(id) => {
759 RawDatabaseSpecifier::Name(self.get_database(id).name().to_string())
760 }
761 };
762 let schema = match &name.qualifiers.schema_spec {
765 SchemaSpecifier::Temporary => MZ_TEMP_SCHEMA.to_string(),
766 SchemaSpecifier::Id(_) => self
767 .get_schema(
768 &name.qualifiers.database_spec,
769 &name.qualifiers.schema_spec,
770 conn_id,
771 )
772 .name()
773 .schema
774 .clone(),
775 };
776 FullItemName {
777 database,
778 schema,
779 item: name.item.clone(),
780 }
781 }
782
783 pub(super) fn resolve_full_schema_name(&self, name: &QualifiedSchemaName) -> FullSchemaName {
784 let database = match &name.database {
785 ResolvedDatabaseSpecifier::Ambient => RawDatabaseSpecifier::Ambient,
786 ResolvedDatabaseSpecifier::Id(id) => {
787 RawDatabaseSpecifier::Name(self.get_database(id).name().to_string())
788 }
789 };
790 FullSchemaName {
791 database,
792 schema: name.schema.clone(),
793 }
794 }
795
796 pub fn get_entry(&self, id: &CatalogItemId) -> &CatalogEntry {
797 self.entry_by_id
798 .get(id)
799 .unwrap_or_else(|| panic!("catalog out of sync, missing id {id:?}"))
800 }
801
802 pub fn get_entry_by_global_id(&self, id: &GlobalId) -> CatalogCollectionEntry {
803 let item_id = self
804 .entry_by_global_id
805 .get(id)
806 .unwrap_or_else(|| panic!("catalog out of sync, missing id {id:?}"));
807
808 let entry = self.get_entry(item_id).clone();
809 let version = match entry.item() {
810 CatalogItem::Table(table) => {
811 let (version, _) = table
812 .collections
813 .iter()
814 .find(|(_verison, gid)| *gid == id)
815 .expect("version to exist");
816 RelationVersionSelector::Specific(*version)
817 }
818 _ => RelationVersionSelector::Latest,
819 };
820 CatalogCollectionEntry { entry, version }
821 }
822
823 pub fn get_entries(&self) -> impl Iterator<Item = (&CatalogItemId, &CatalogEntry)> + '_ {
824 self.entry_by_id.iter()
825 }
826
827 pub fn get_temp_items(&self, conn: &ConnectionId) -> impl Iterator<Item = ObjectId> + '_ {
828 self.temporary_schemas
830 .get(conn)
831 .into_iter()
832 .flat_map(|schema| schema.items.values().copied().map(ObjectId::from))
833 }
834
835 pub fn has_temporary_schema(&self, conn: &ConnectionId) -> bool {
841 self.temporary_schemas.contains_key(conn)
842 }
843
844 pub(super) fn get_system_type(&self, name: &str) -> &CatalogEntry {
850 let mut res = None;
851 for schema_id in self.system_schema_ids() {
852 let schema = &self.ambient_schemas_by_id[&schema_id];
853 if let Some(global_id) = schema.types.get(name) {
854 match res {
855 None => res = Some(self.get_entry(global_id)),
856 Some(_) => panic!(
857 "only call get_system_type on objects uniquely identifiable in one system schema"
858 ),
859 }
860 }
861 }
862
863 res.unwrap_or_else(|| panic!("cannot find type {} in system schema", name))
864 }
865
866 pub fn get_item_by_name(
867 &self,
868 name: &QualifiedItemName,
869 conn_id: &ConnectionId,
870 ) -> Option<&CatalogEntry> {
871 self.get_schema(
872 &name.qualifiers.database_spec,
873 &name.qualifiers.schema_spec,
874 conn_id,
875 )
876 .items
877 .get(&name.item)
878 .and_then(|id| self.try_get_entry(id))
879 }
880
881 pub fn get_type_by_name(
882 &self,
883 name: &QualifiedItemName,
884 conn_id: &ConnectionId,
885 ) -> Option<&CatalogEntry> {
886 self.get_schema(
887 &name.qualifiers.database_spec,
888 &name.qualifiers.schema_spec,
889 conn_id,
890 )
891 .types
892 .get(&name.item)
893 .and_then(|id| self.try_get_entry(id))
894 }
895
896 pub(super) fn find_available_name(
897 &self,
898 mut name: QualifiedItemName,
899 conn_id: &ConnectionId,
900 ) -> QualifiedItemName {
901 let mut i = 0;
902 let orig_item_name = name.item.clone();
903 while self.get_item_by_name(&name, conn_id).is_some() {
904 i += 1;
905 name.item = format!("{}{}", orig_item_name, i);
906 }
907 name
908 }
909
910 pub fn try_get_entry(&self, id: &CatalogItemId) -> Option<&CatalogEntry> {
911 self.entry_by_id.get(id)
912 }
913
914 pub fn try_get_entry_by_global_id(&self, id: &GlobalId) -> Option<&CatalogEntry> {
915 let item_id = self.entry_by_global_id.get(id)?;
916 self.try_get_entry(item_id)
917 }
918
919 pub fn try_get_desc_by_global_id(&self, id: &GlobalId) -> Option<Cow<'_, RelationDesc>> {
922 let entry = self.try_get_entry_by_global_id(id)?;
923 let desc = match entry.item() {
924 CatalogItem::Table(table) => Cow::Owned(table.desc_for(id)),
925 other => other.relation_desc(RelationVersionSelector::Latest)?,
927 };
928 Some(desc)
929 }
930
931 pub(crate) fn get_cluster(&self, cluster_id: ClusterId) -> &Cluster {
932 self.try_get_cluster(cluster_id)
933 .unwrap_or_else(|| panic!("unknown cluster {cluster_id}"))
934 }
935
936 pub(super) fn try_get_cluster(&self, cluster_id: ClusterId) -> Option<&Cluster> {
937 self.clusters_by_id.get(&cluster_id)
938 }
939
940 pub(super) fn try_get_role(&self, id: &RoleId) -> Option<&Role> {
941 self.roles_by_id.get(id)
942 }
943
944 pub fn get_role(&self, id: &RoleId) -> &Role {
945 self.roles_by_id.get(id).expect("catalog out of sync")
946 }
947
948 pub fn get_roles(&self) -> impl Iterator<Item = &RoleId> {
949 self.roles_by_id.keys()
950 }
951
952 pub(super) fn try_get_role_by_name(&self, role_name: &str) -> Option<&Role> {
953 self.roles_by_name
954 .get(role_name)
955 .map(|id| &self.roles_by_id[id])
956 }
957
958 pub(super) fn get_role_auth(&self, id: &RoleId) -> &RoleAuth {
959 self.role_auth_by_id
960 .get(id)
961 .unwrap_or_else(|| panic!("catalog out of sync, missing role auth for {id}"))
962 }
963
964 pub(super) fn try_get_role_auth_by_id(&self, id: &RoleId) -> Option<&RoleAuth> {
965 self.role_auth_by_id.get(id)
966 }
967
968 pub(super) fn try_get_network_policy_by_name(
969 &self,
970 policy_name: &str,
971 ) -> Option<&NetworkPolicy> {
972 self.network_policies_by_name
973 .get(policy_name)
974 .map(|id| &self.network_policies_by_id[id])
975 }
976
977 pub(crate) fn collect_role_membership(&self, id: &RoleId) -> BTreeSet<RoleId> {
978 let mut membership = BTreeSet::new();
979 let mut queue = VecDeque::from(vec![id]);
980 while let Some(cur_id) = queue.pop_front() {
981 if !membership.contains(cur_id) {
982 membership.insert(cur_id.clone());
983 let role = self.get_role(cur_id);
984 soft_assert_no_log!(
985 !role.membership().keys().contains(id),
986 "circular membership exists in the catalog"
987 );
988 queue.extend(role.membership().keys());
989 }
990 }
991 membership.insert(RoleId::Public);
992 membership
993 }
994
995 pub fn get_network_policy(&self, id: &NetworkPolicyId) -> &NetworkPolicy {
996 self.network_policies_by_id
997 .get(id)
998 .expect("catalog out of sync")
999 }
1000
1001 pub fn get_network_policies(&self) -> impl Iterator<Item = &NetworkPolicyId> {
1002 self.network_policies_by_id.keys()
1003 }
1004
1005 pub fn try_get_webhook_url(&self, id: &CatalogItemId) -> Option<url::Url> {
1010 let entry = self.try_get_entry(id)?;
1011 let name = self.resolve_full_name(entry.name(), None);
1013 let host_name = self
1014 .http_host_name
1015 .as_ref()
1016 .map(|x| x.as_str())
1017 .unwrap_or_else(|| "HOST");
1018
1019 let RawDatabaseSpecifier::Name(database) = name.database else {
1020 return None;
1021 };
1022
1023 let mut url = url::Url::parse(&format!("https://{host_name}/api/webhook")).ok()?;
1024 url.path_segments_mut()
1025 .ok()?
1026 .push(&database)
1027 .push(&name.schema)
1028 .push(&name.item);
1029
1030 Some(url)
1031 }
1032
1033 pub(crate) fn deserialize_plan_with_enable_for_item_parsing(
1041 &mut self,
1044 create_sql: &str,
1045 force_if_exists_skip: bool,
1046 ) -> Result<(Plan, ResolvedIds), AdapterError> {
1047 self.with_enable_for_item_parsing(|state| {
1048 let pcx = PlanContext::zero().with_ignore_if_exists_errors(force_if_exists_skip);
1049 let pcx = Some(&pcx);
1050 let session_catalog = state.for_system_session();
1051
1052 let stmt = mz_sql::parse::parse(create_sql)?.into_element().ast;
1053 let (stmt, resolved_ids) = mz_sql::names::resolve(&session_catalog, stmt)?;
1054 let (plan, _sql_impl_ids) =
1055 mz_sql::plan::plan(pcx, &session_catalog, stmt, &Params::empty(), &resolved_ids)?;
1056
1057 Ok((plan, resolved_ids))
1058 })
1059 }
1060
1061 #[mz_ore::instrument]
1063 pub(crate) fn parse_plan(
1064 create_sql: &str,
1065 pcx: Option<&PlanContext>,
1066 catalog: &ConnCatalog,
1067 ) -> Result<(Plan, ResolvedIds), AdapterError> {
1068 let stmt = mz_sql::parse::parse(create_sql)?.into_element().ast;
1069 let (stmt, resolved_ids) = mz_sql::names::resolve(catalog, stmt)?;
1070 let (plan, _sql_impl_ids) =
1071 mz_sql::plan::plan(pcx, catalog, stmt, &Params::empty(), &resolved_ids)?;
1072
1073 Ok((plan, resolved_ids))
1074 }
1075
1076 pub(crate) fn deserialize_item(
1078 &self,
1079 global_id: GlobalId,
1080 create_sql: &str,
1081 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1082 local_expression_cache: &mut LocalExpressionCache,
1083 previous_item: Option<CatalogItem>,
1084 ) -> Result<CatalogItem, AdapterError> {
1085 self.parse_item(
1086 global_id,
1087 create_sql,
1088 extra_versions,
1089 None,
1090 false,
1091 None,
1092 local_expression_cache,
1093 previous_item,
1094 )
1095 }
1096
1097 #[mz_ore::instrument]
1099 pub(crate) fn parse_item(
1100 &self,
1101 global_id: GlobalId,
1102 create_sql: &str,
1103 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1104 pcx: Option<&PlanContext>,
1105 is_retained_metrics_object: bool,
1106 custom_logical_compaction_window: Option<CompactionWindow>,
1107 local_expression_cache: &mut LocalExpressionCache,
1108 previous_item: Option<CatalogItem>,
1109 ) -> Result<CatalogItem, AdapterError> {
1110 let cached_expr = local_expression_cache.remove_cached_expression(&global_id);
1111 match self.parse_item_inner(
1112 global_id,
1113 create_sql,
1114 extra_versions,
1115 pcx,
1116 is_retained_metrics_object,
1117 custom_logical_compaction_window,
1118 cached_expr,
1119 previous_item,
1120 ) {
1121 Ok((item, uncached_expr)) => {
1122 if let Some((uncached_expr, optimizer_features)) = uncached_expr {
1123 local_expression_cache.insert_uncached_expression(
1124 global_id,
1125 uncached_expr,
1126 optimizer_features,
1127 );
1128 }
1129 Ok(item)
1130 }
1131 Err((err, cached_expr)) => {
1132 if let Some(local_expr) = cached_expr {
1133 local_expression_cache.insert_cached_expression(global_id, local_expr);
1134 }
1135 Err(err)
1136 }
1137 }
1138 }
1139
1140 #[mz_ore::instrument]
1147 pub(crate) fn parse_item_inner(
1148 &self,
1149 global_id: GlobalId,
1150 create_sql: &str,
1151 extra_versions: &BTreeMap<RelationVersion, GlobalId>,
1152 pcx: Option<&PlanContext>,
1153 is_retained_metrics_object: bool,
1154 custom_logical_compaction_window: Option<CompactionWindow>,
1155 cached_expr: Option<LocalExpressions>,
1156 previous_item: Option<CatalogItem>,
1157 ) -> Result<
1158 (
1159 CatalogItem,
1160 Option<(OptimizedMirRelationExpr, OptimizerFeatures)>,
1161 ),
1162 (AdapterError, Option<LocalExpressions>),
1163 > {
1164 let session_catalog = self.for_system_session();
1165
1166 let (plan, resolved_ids) = match Self::parse_plan(create_sql, pcx, &session_catalog) {
1167 Ok((plan, resolved_ids)) => (plan, resolved_ids),
1168 Err(err) => return Err((err, cached_expr)),
1169 };
1170
1171 let mut uncached_expr = None;
1172
1173 let previous_plans = previous_item.as_ref().map(|item| {
1183 (
1184 item.optimized_plan().cloned(),
1185 item.physical_plan().cloned(),
1186 item.dataflow_metainfo().cloned(),
1187 )
1188 });
1189
1190 let mut item = match plan {
1191 Plan::CreateTable(CreateTablePlan { table, .. }) => {
1192 let collections = extra_versions
1193 .iter()
1194 .map(|(version, gid)| (*version, *gid))
1195 .chain([(RelationVersion::root(), global_id)].into_iter())
1196 .collect();
1197
1198 CatalogItem::Table(Table {
1199 create_sql: Some(table.create_sql),
1200 desc: table.desc,
1201 collections,
1202 conn_id: None,
1203 resolved_ids,
1204 custom_logical_compaction_window: custom_logical_compaction_window
1205 .or(table.compaction_window),
1206 is_retained_metrics_object,
1207 data_source: match table.data_source {
1208 mz_sql::plan::TableDataSource::TableWrites { defaults } => {
1209 TableDataSource::TableWrites { defaults }
1210 }
1211 mz_sql::plan::TableDataSource::DataSource {
1212 desc: data_source_desc,
1213 timeline,
1214 } => match data_source_desc {
1215 mz_sql::plan::DataSourceDesc::IngestionExport {
1216 ingestion_id,
1217 external_reference,
1218 details,
1219 data_config,
1220 } => TableDataSource::DataSource {
1221 desc: DataSourceDesc::IngestionExport {
1222 ingestion_id,
1223 external_reference,
1224 details,
1225 data_config,
1226 },
1227 timeline,
1228 },
1229 mz_sql::plan::DataSourceDesc::Webhook {
1230 validate_using,
1231 body_format,
1232 headers,
1233 cluster_id,
1234 } => TableDataSource::DataSource {
1235 desc: DataSourceDesc::Webhook {
1236 validate_using,
1237 body_format,
1238 headers,
1239 cluster_id: cluster_id
1240 .expect("Webhook Tables must have a cluster_id set"),
1241 },
1242 timeline,
1243 },
1244 _ => {
1245 return Err((
1246 AdapterError::Unstructured(anyhow::anyhow!(
1247 "unsupported data source for table"
1248 )),
1249 cached_expr,
1250 ));
1251 }
1252 },
1253 },
1254 })
1255 }
1256 Plan::CreateSource(CreateSourcePlan {
1257 source,
1258 timeline,
1259 in_cluster,
1260 ..
1261 }) => CatalogItem::Source(Source {
1262 create_sql: Some(source.create_sql),
1263 data_source: match source.data_source {
1264 mz_sql::plan::DataSourceDesc::Ingestion(desc) => DataSourceDesc::Ingestion {
1265 desc,
1266 cluster_id: match in_cluster {
1267 Some(id) => id,
1268 None => {
1269 return Err((
1270 AdapterError::Unstructured(anyhow::anyhow!(
1271 "ingestion-based sources must have cluster specified"
1272 )),
1273 cached_expr,
1274 ));
1275 }
1276 },
1277 },
1278 mz_sql::plan::DataSourceDesc::OldSyntaxIngestion {
1279 desc,
1280 progress_subsource,
1281 data_config,
1282 details,
1283 } => DataSourceDesc::OldSyntaxIngestion {
1284 desc,
1285 progress_subsource,
1286 data_config,
1287 details,
1288 cluster_id: match in_cluster {
1289 Some(id) => id,
1290 None => {
1291 return Err((
1292 AdapterError::Unstructured(anyhow::anyhow!(
1293 "ingestion-based sources must have cluster specified"
1294 )),
1295 cached_expr,
1296 ));
1297 }
1298 },
1299 },
1300 mz_sql::plan::DataSourceDesc::IngestionExport {
1301 ingestion_id,
1302 external_reference,
1303 details,
1304 data_config,
1305 } => DataSourceDesc::IngestionExport {
1306 ingestion_id,
1307 external_reference,
1308 details,
1309 data_config,
1310 },
1311 mz_sql::plan::DataSourceDesc::Progress => DataSourceDesc::Progress,
1312 mz_sql::plan::DataSourceDesc::Webhook {
1313 validate_using,
1314 body_format,
1315 headers,
1316 cluster_id,
1317 } => {
1318 mz_ore::soft_assert_or_log!(
1319 cluster_id.is_none(),
1320 "cluster_id set at Source level for Webhooks"
1321 );
1322 DataSourceDesc::Webhook {
1323 validate_using,
1324 body_format,
1325 headers,
1326 cluster_id: in_cluster
1327 .expect("webhook sources must use an existing cluster"),
1328 }
1329 }
1330 },
1331 desc: source.desc,
1332 global_id,
1333 timeline,
1334 resolved_ids,
1335 custom_logical_compaction_window: source
1336 .compaction_window
1337 .or(custom_logical_compaction_window),
1338 is_retained_metrics_object,
1339 }),
1340 Plan::CreateView(CreateViewPlan { view, .. }) => {
1341 let optimizer_config =
1343 optimize::OptimizerConfig::from(session_catalog.system_vars());
1344 let previous_exprs = previous_item.map(|item| match item {
1345 CatalogItem::View(view) => Some((view.raw_expr, view.locally_optimized_expr)),
1346 _ => None,
1347 });
1348
1349 let (raw_expr, optimized_expr) = match (cached_expr, previous_exprs) {
1350 (Some(local_expr), _)
1351 if local_expr.optimizer_features == optimizer_config.features =>
1352 {
1353 debug!("local expression cache hit for {global_id:?}");
1354 (Arc::new(view.expr), Arc::new(local_expr.local_mir))
1355 }
1356 (_, Some(Some((raw_expr, optimized_expr)))) if *raw_expr == view.expr => {
1358 (Arc::clone(&raw_expr), Arc::clone(&optimized_expr))
1359 }
1360 (cached_expr, _) => {
1361 let optimizer_features = optimizer_config.features.clone();
1362 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
1364
1365 let raw_expr = view.expr;
1367 let optimized_expr = match optimizer.optimize(raw_expr.clone()) {
1368 Ok(optimzed_expr) => optimzed_expr,
1369 Err(err) => return Err((err.into(), cached_expr)),
1370 };
1371
1372 uncached_expr = Some((optimized_expr.clone(), optimizer_features));
1373
1374 (Arc::new(raw_expr), Arc::new(optimized_expr))
1375 }
1376 };
1377
1378 let dependencies: BTreeSet<_> = raw_expr
1380 .depends_on()
1381 .into_iter()
1382 .map(|gid| self.get_entry_by_global_id(&gid).id())
1383 .collect();
1384
1385 let typ = infer_sql_type_for_catalog(&raw_expr, &optimized_expr);
1386 CatalogItem::View(View {
1387 create_sql: view.create_sql,
1388 global_id,
1389 raw_expr,
1390 desc: RelationDesc::new(typ, view.column_names),
1391 locally_optimized_expr: optimized_expr,
1392 conn_id: None,
1393 resolved_ids,
1394 dependencies: DependencyIds(dependencies),
1395 })
1396 }
1397 Plan::CreateMaterializedView(CreateMaterializedViewPlan {
1398 materialized_view, ..
1399 }) => {
1400 let collections = extra_versions
1401 .iter()
1402 .map(|(version, gid)| (*version, *gid))
1403 .chain([(RelationVersion::root(), global_id)].into_iter())
1404 .collect();
1405
1406 let system_vars = session_catalog.system_vars();
1408 let overrides = self
1409 .get_cluster(materialized_view.cluster_id)
1410 .config
1411 .features();
1412 let optimizer_config =
1413 optimize::OptimizerConfig::from(system_vars).override_from(&overrides);
1414 let previous_exprs = previous_item.map(|item| match item {
1415 CatalogItem::MaterializedView(materialized_view) => (
1416 materialized_view.raw_expr,
1417 materialized_view.locally_optimized_expr,
1418 ),
1419 item => unreachable!("expected materialized view, found: {item:#?}"),
1420 });
1421
1422 let (raw_expr, optimized_expr) = match (cached_expr, previous_exprs) {
1423 (Some(local_expr), _)
1424 if local_expr.optimizer_features == optimizer_config.features =>
1425 {
1426 debug!("local expression cache hit for {global_id:?}");
1427 (
1428 Arc::new(materialized_view.expr),
1429 Arc::new(local_expr.local_mir),
1430 )
1431 }
1432 (_, Some((raw_expr, optimized_expr)))
1434 if *raw_expr == materialized_view.expr =>
1435 {
1436 (Arc::clone(&raw_expr), Arc::clone(&optimized_expr))
1437 }
1438 (cached_expr, _) => {
1439 let optimizer_features = optimizer_config.features.clone();
1440 let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
1442
1443 let raw_expr = materialized_view.expr;
1444 let optimized_expr = match optimizer.optimize(raw_expr.clone()) {
1445 Ok(optimized_expr) => optimized_expr,
1446 Err(err) => return Err((err.into(), cached_expr)),
1447 };
1448
1449 uncached_expr = Some((optimized_expr.clone(), optimizer_features));
1450
1451 (Arc::new(raw_expr), Arc::new(optimized_expr))
1452 }
1453 };
1454 let mut typ = infer_sql_type_for_catalog(&raw_expr, &optimized_expr);
1455
1456 for &i in &materialized_view.non_null_assertions {
1457 typ.column_types[i].nullable = false;
1458 }
1459 let desc = RelationDesc::new(typ, materialized_view.column_names);
1460 let desc = VersionedRelationDesc::new(desc);
1461
1462 let initial_as_of = materialized_view.as_of.map(Antichain::from_elem);
1463
1464 let dependencies = raw_expr
1466 .depends_on()
1467 .into_iter()
1468 .map(|gid| self.get_entry_by_global_id(&gid).id())
1469 .collect();
1470
1471 CatalogItem::MaterializedView(MaterializedView {
1472 create_sql: materialized_view.create_sql,
1473 collections,
1474 raw_expr,
1475 locally_optimized_expr: optimized_expr,
1476 desc,
1477 resolved_ids,
1478 dependencies,
1479 replacement_target: materialized_view.replacement_target,
1480 cluster_id: materialized_view.cluster_id,
1481 target_replica: materialized_view.target_replica,
1482 non_null_assertions: materialized_view.non_null_assertions,
1483 custom_logical_compaction_window: materialized_view.compaction_window,
1484 refresh_schedule: materialized_view.refresh_schedule,
1485 initial_as_of,
1486 optimized_plan: None,
1487 physical_plan: None,
1488 dataflow_metainfo: None,
1489 })
1490 }
1491 Plan::CreateIndex(CreateIndexPlan { index, .. }) => CatalogItem::Index(Index {
1492 create_sql: index.create_sql,
1493 global_id,
1494 on: index.on,
1495 keys: index.keys.into(),
1496 conn_id: None,
1497 resolved_ids,
1498 cluster_id: index.cluster_id,
1499 custom_logical_compaction_window: custom_logical_compaction_window
1500 .or(index.compaction_window),
1501 is_retained_metrics_object,
1502 optimized_plan: None,
1503 physical_plan: None,
1504 dataflow_metainfo: None,
1505 }),
1506 Plan::CreateSink(CreateSinkPlan {
1507 sink,
1508 with_snapshot,
1509 in_cluster,
1510 ..
1511 }) => CatalogItem::Sink(Sink {
1512 create_sql: sink.create_sql,
1513 global_id,
1514 from: sink.from,
1515 connection: sink.connection,
1516 envelope: sink.envelope,
1517 version: sink.version,
1518 with_snapshot,
1519 resolved_ids,
1520 cluster_id: in_cluster,
1521 commit_interval: sink.commit_interval,
1522 }),
1523 Plan::CreateType(CreateTypePlan { typ, .. }) => {
1524 if let Err(err) = typ.inner.desc(&session_catalog) {
1528 return Err((err.into(), cached_expr));
1529 }
1530 CatalogItem::Type(Type {
1531 create_sql: Some(typ.create_sql),
1532 global_id,
1533 details: CatalogTypeDetails {
1534 array_id: None,
1535 typ: typ.inner,
1536 pg_metadata: None,
1537 },
1538 resolved_ids,
1539 })
1540 }
1541 Plan::CreateSecret(CreateSecretPlan { secret, .. }) => CatalogItem::Secret(Secret {
1542 create_sql: secret.create_sql,
1543 global_id,
1544 }),
1545 Plan::CreateConnection(CreateConnectionPlan {
1546 connection:
1547 mz_sql::plan::Connection {
1548 create_sql,
1549 details,
1550 },
1551 ..
1552 }) => CatalogItem::Connection(Connection {
1553 create_sql,
1554 global_id,
1555 details,
1556 resolved_ids,
1557 }),
1558 _ => {
1559 return Err((
1560 Error::new(ErrorKind::Corruption {
1561 detail: "catalog entry generated inappropriate plan".to_string(),
1562 })
1563 .into(),
1564 cached_expr,
1565 ));
1566 }
1567 };
1568
1569 if let Some((prev_optimized, prev_physical, prev_metainfo)) = previous_plans {
1573 if let Some((optimized_plan, physical_plan, dataflow_metainfo)) = item.plan_fields_mut()
1574 {
1575 *optimized_plan = prev_optimized;
1576 *physical_plan = prev_physical;
1577 *dataflow_metainfo = prev_metainfo;
1578 }
1579 }
1580
1581 Ok((item, uncached_expr))
1582 }
1583
1584 pub fn with_enable_for_item_parsing<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
1590 let restore = Arc::clone(&self.system_configuration);
1601 Arc::make_mut(&mut self.system_configuration).enable_for_item_parsing();
1602 let res = f(self);
1603 self.system_configuration = restore;
1604 res
1605 }
1606
1607 pub fn get_indexes_on(
1609 &self,
1610 id: GlobalId,
1611 cluster: ClusterId,
1612 ) -> impl Iterator<Item = (GlobalId, &Index)> {
1613 let index_matches = move |idx: &Index| idx.on == id && idx.cluster_id == cluster;
1614
1615 self.try_get_entry_by_global_id(&id)
1616 .into_iter()
1617 .map(move |e| {
1618 e.used_by()
1619 .iter()
1620 .filter_map(move |uses_id| match self.get_entry(uses_id).item() {
1621 CatalogItem::Index(index) if index_matches(index) => {
1622 Some((index.global_id(), index))
1623 }
1624 _ => None,
1625 })
1626 })
1627 .flatten()
1628 }
1629
1630 pub(super) fn get_database(&self, database_id: &DatabaseId) -> &Database {
1631 &self.database_by_id[database_id]
1632 }
1633
1634 pub(super) fn try_get_cluster_replica(
1639 &self,
1640 id: ClusterId,
1641 replica_id: ReplicaId,
1642 ) -> Option<&ClusterReplica> {
1643 self.try_get_cluster(id)
1644 .and_then(|cluster| cluster.replica(replica_id))
1645 }
1646
1647 pub(crate) fn get_cluster_replica(
1651 &self,
1652 cluster_id: ClusterId,
1653 replica_id: ReplicaId,
1654 ) -> &ClusterReplica {
1655 self.try_get_cluster_replica(cluster_id, replica_id)
1656 .unwrap_or_else(|| panic!("unknown cluster replica: {cluster_id}.{replica_id}"))
1657 }
1658
1659 pub(super) fn resolve_replica_in_cluster(
1660 &self,
1661 cluster_id: &ClusterId,
1662 replica_name: &str,
1663 ) -> Result<&ClusterReplica, SqlCatalogError> {
1664 let cluster = self.get_cluster(*cluster_id);
1665 let replica_id = cluster
1666 .replica_id_by_name_
1667 .get(replica_name)
1668 .ok_or_else(|| SqlCatalogError::UnknownClusterReplica(replica_name.to_string()))?;
1669 Ok(&cluster.replicas_by_id_[replica_id])
1670 }
1671
1672 pub fn get_system_configuration(&self, name: &str) -> Result<&dyn Var, Error> {
1674 Ok(self.system_configuration.get(name)?)
1675 }
1676
1677 pub(super) fn parse_system_configuration(
1681 &self,
1682 name: &str,
1683 value: VarInput,
1684 ) -> Result<String, Error> {
1685 let value = self.system_configuration.parse(name, value)?;
1686 Ok(value.format())
1687 }
1688
1689 pub(super) fn resolve_schema_in_database(
1691 &self,
1692 database_spec: &ResolvedDatabaseSpecifier,
1693 schema_name: &str,
1694 conn_id: &ConnectionId,
1695 ) -> Result<&Schema, SqlCatalogError> {
1696 let schema = match database_spec {
1697 ResolvedDatabaseSpecifier::Ambient if schema_name == MZ_TEMP_SCHEMA => {
1698 self.temporary_schemas.get(conn_id)
1699 }
1700 ResolvedDatabaseSpecifier::Ambient => self
1701 .ambient_schemas_by_name
1702 .get(schema_name)
1703 .and_then(|id| self.ambient_schemas_by_id.get(id)),
1704 ResolvedDatabaseSpecifier::Id(id) => self.database_by_id.get(id).and_then(|db| {
1705 db.schemas_by_name
1706 .get(schema_name)
1707 .and_then(|id| db.schemas_by_id.get(id))
1708 }),
1709 };
1710 schema.ok_or_else(|| SqlCatalogError::UnknownSchema(schema_name.into()))
1711 }
1712
1713 pub fn try_get_schema(
1718 &self,
1719 database_spec: &ResolvedDatabaseSpecifier,
1720 schema_spec: &SchemaSpecifier,
1721 conn_id: &ConnectionId,
1722 ) -> Option<&Schema> {
1723 match (database_spec, schema_spec) {
1725 (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Temporary) => {
1726 self.temporary_schemas.get(conn_id)
1727 }
1728 (ResolvedDatabaseSpecifier::Ambient, SchemaSpecifier::Id(id)) => {
1729 self.ambient_schemas_by_id.get(id)
1730 }
1731 (ResolvedDatabaseSpecifier::Id(database_id), SchemaSpecifier::Id(schema_id)) => self
1732 .database_by_id
1733 .get(database_id)
1734 .and_then(|db| db.schemas_by_id.get(schema_id)),
1735 (ResolvedDatabaseSpecifier::Id(_), SchemaSpecifier::Temporary) => {
1736 unreachable!("temporary schemas are in the ambient database")
1737 }
1738 }
1739 }
1740
1741 pub fn get_schema(
1742 &self,
1743 database_spec: &ResolvedDatabaseSpecifier,
1744 schema_spec: &SchemaSpecifier,
1745 conn_id: &ConnectionId,
1746 ) -> &Schema {
1747 self.try_get_schema(database_spec, schema_spec, conn_id)
1749 .expect("schema must exist")
1750 }
1751
1752 pub(super) fn find_non_temp_schema(&self, schema_id: &SchemaId) -> &Schema {
1753 self.database_by_id
1754 .values()
1755 .filter_map(|database| database.schemas_by_id.get(schema_id))
1756 .chain(self.ambient_schemas_by_id.values())
1757 .filter(|schema| schema.id() == &SchemaSpecifier::from(*schema_id))
1758 .into_first()
1759 }
1760
1761 pub(super) fn find_temp_schema(&self, schema_id: &SchemaId) -> &Schema {
1762 self.temporary_schemas
1763 .values()
1764 .filter(|schema| schema.id() == &SchemaSpecifier::from(*schema_id))
1765 .into_first()
1766 }
1767
1768 pub fn get_mz_catalog_schema_id(&self) -> SchemaId {
1769 self.ambient_schemas_by_name[MZ_CATALOG_SCHEMA]
1770 }
1771
1772 pub fn get_mz_catalog_unstable_schema_id(&self) -> SchemaId {
1773 self.ambient_schemas_by_name[MZ_CATALOG_UNSTABLE_SCHEMA]
1774 }
1775
1776 pub fn get_pg_catalog_schema_id(&self) -> SchemaId {
1777 self.ambient_schemas_by_name[PG_CATALOG_SCHEMA]
1778 }
1779
1780 pub fn get_information_schema_id(&self) -> SchemaId {
1781 self.ambient_schemas_by_name[INFORMATION_SCHEMA]
1782 }
1783
1784 pub fn get_mz_internal_schema_id(&self) -> SchemaId {
1785 self.ambient_schemas_by_name[MZ_INTERNAL_SCHEMA]
1786 }
1787
1788 pub fn get_mz_introspection_schema_id(&self) -> SchemaId {
1789 self.ambient_schemas_by_name[MZ_INTROSPECTION_SCHEMA]
1790 }
1791
1792 pub fn get_mz_unsafe_schema_id(&self) -> SchemaId {
1793 self.ambient_schemas_by_name[MZ_UNSAFE_SCHEMA]
1794 }
1795
1796 pub fn system_schema_ids(&self) -> impl Iterator<Item = SchemaId> + '_ {
1797 SYSTEM_SCHEMAS
1798 .iter()
1799 .map(|name| self.ambient_schemas_by_name[*name])
1800 }
1801
1802 pub fn is_system_schema_id(&self, id: SchemaId) -> bool {
1803 self.system_schema_ids().contains(&id)
1804 }
1805
1806 pub fn is_system_schema_specifier(&self, spec: SchemaSpecifier) -> bool {
1807 match spec {
1808 SchemaSpecifier::Temporary => false,
1809 SchemaSpecifier::Id(id) => self.is_system_schema_id(id),
1810 }
1811 }
1812
1813 pub fn unstable_schema_ids(&self) -> impl Iterator<Item = SchemaId> + '_ {
1814 UNSTABLE_SCHEMAS
1815 .iter()
1816 .map(|name| self.ambient_schemas_by_name[*name])
1817 }
1818
1819 pub fn is_unstable_schema_id(&self, id: SchemaId) -> bool {
1820 self.unstable_schema_ids().contains(&id)
1821 }
1822
1823 pub fn is_unstable_schema_specifier(&self, spec: SchemaSpecifier) -> bool {
1824 match spec {
1825 SchemaSpecifier::Temporary => false,
1826 SchemaSpecifier::Id(id) => self.is_unstable_schema_id(id),
1827 }
1828 }
1829
1830 pub fn create_temporary_schema(
1833 &mut self,
1834 conn_id: &ConnectionId,
1835 owner_id: RoleId,
1836 ) -> Result<(), Error> {
1837 let oid = INVALID_OID;
1842 self.temporary_schemas.insert(
1843 conn_id.clone(),
1844 Schema {
1845 name: QualifiedSchemaName {
1846 database: ResolvedDatabaseSpecifier::Ambient,
1847 schema: MZ_TEMP_SCHEMA.into(),
1848 },
1849 id: SchemaSpecifier::Temporary,
1850 oid,
1851 items: BTreeMap::new(),
1852 functions: BTreeMap::new(),
1853 types: BTreeMap::new(),
1854 owner_id,
1855 privileges: PrivilegeMap::from_mz_acl_items(vec![rbac::owner_privilege(
1856 mz_sql::catalog::ObjectType::Schema,
1857 owner_id,
1858 )]),
1859 },
1860 );
1861 Ok(())
1862 }
1863
1864 pub(crate) fn get_temporary_oids(&self) -> impl Iterator<Item = u32> + '_ {
1866 std::iter::empty()
1867 .chain(self.ambient_schemas_by_id.values().filter_map(|schema| {
1868 if schema.id.is_temporary() {
1869 Some(schema.oid)
1870 } else {
1871 None
1872 }
1873 }))
1874 .chain(self.entry_by_id.values().filter_map(|entry| {
1875 if entry.item().is_temporary() {
1876 Some(entry.oid)
1877 } else {
1878 None
1879 }
1880 }))
1881 }
1882
1883 pub fn resolve_builtin_table(&self, builtin: &'static BuiltinTable) -> CatalogItemId {
1887 self.resolve_builtin_object(&Builtin::<IdReference>::Table(builtin))
1888 }
1889
1890 pub fn resolve_builtin_log(&self, builtin: &'static BuiltinLog) -> (CatalogItemId, GlobalId) {
1894 let item_id = self.resolve_builtin_object(&Builtin::<IdReference>::Log(builtin));
1895 let log = match self.get_entry(&item_id).item() {
1896 CatalogItem::Log(log) => log,
1897 other => unreachable!("programming error, expected BuiltinLog, found {other:?}"),
1898 };
1899 (item_id, log.global_id)
1900 }
1901
1902 pub fn resolve_builtin_source(&self, builtin: &'static BuiltinSource) -> CatalogItemId {
1906 self.resolve_builtin_object(&Builtin::<IdReference>::Source(builtin))
1907 }
1908
1909 pub fn resolve_builtin_object<T: TypeReference>(&self, builtin: &Builtin<T>) -> CatalogItemId {
1913 let schema_id = &self.ambient_schemas_by_name[builtin.schema()];
1914 let schema = &self.ambient_schemas_by_id[schema_id];
1915 match builtin.catalog_item_type() {
1916 CatalogItemType::Type => schema.types[builtin.name()],
1917 CatalogItemType::Func => schema.functions[builtin.name()],
1918 CatalogItemType::Table
1919 | CatalogItemType::Source
1920 | CatalogItemType::Sink
1921 | CatalogItemType::View
1922 | CatalogItemType::MaterializedView
1923 | CatalogItemType::Index
1924 | CatalogItemType::Secret
1925 | CatalogItemType::Connection => schema.items[builtin.name()],
1926 }
1927 }
1928
1929 pub fn resolve_builtin_type_references(
1931 &self,
1932 builtin: &BuiltinType<NameReference>,
1933 ) -> BuiltinType<IdReference> {
1934 let typ: CatalogType<IdReference> = match &builtin.details.typ {
1935 CatalogType::AclItem => CatalogType::AclItem,
1936 CatalogType::Array { element_reference } => CatalogType::Array {
1937 element_reference: self.get_system_type(element_reference).id,
1938 },
1939 CatalogType::List {
1940 element_reference,
1941 element_modifiers,
1942 } => CatalogType::List {
1943 element_reference: self.get_system_type(element_reference).id,
1944 element_modifiers: element_modifiers.clone(),
1945 },
1946 CatalogType::Map {
1947 key_reference,
1948 value_reference,
1949 key_modifiers,
1950 value_modifiers,
1951 } => CatalogType::Map {
1952 key_reference: self.get_system_type(key_reference).id,
1953 value_reference: self.get_system_type(value_reference).id,
1954 key_modifiers: key_modifiers.clone(),
1955 value_modifiers: value_modifiers.clone(),
1956 },
1957 CatalogType::Range { element_reference } => CatalogType::Range {
1958 element_reference: self.get_system_type(element_reference).id,
1959 },
1960 CatalogType::Record { fields } => CatalogType::Record {
1961 fields: fields
1962 .into_iter()
1963 .map(|f| CatalogRecordField {
1964 name: f.name.clone(),
1965 type_reference: self.get_system_type(f.type_reference).id,
1966 type_modifiers: f.type_modifiers.clone(),
1967 })
1968 .collect(),
1969 },
1970 CatalogType::Bool => CatalogType::Bool,
1971 CatalogType::Bytes => CatalogType::Bytes,
1972 CatalogType::Char => CatalogType::Char,
1973 CatalogType::Date => CatalogType::Date,
1974 CatalogType::Float32 => CatalogType::Float32,
1975 CatalogType::Float64 => CatalogType::Float64,
1976 CatalogType::Int16 => CatalogType::Int16,
1977 CatalogType::Int32 => CatalogType::Int32,
1978 CatalogType::Int64 => CatalogType::Int64,
1979 CatalogType::UInt16 => CatalogType::UInt16,
1980 CatalogType::UInt32 => CatalogType::UInt32,
1981 CatalogType::UInt64 => CatalogType::UInt64,
1982 CatalogType::MzTimestamp => CatalogType::MzTimestamp,
1983 CatalogType::Interval => CatalogType::Interval,
1984 CatalogType::Jsonb => CatalogType::Jsonb,
1985 CatalogType::Numeric => CatalogType::Numeric,
1986 CatalogType::Oid => CatalogType::Oid,
1987 CatalogType::PgLegacyChar => CatalogType::PgLegacyChar,
1988 CatalogType::PgLegacyName => CatalogType::PgLegacyName,
1989 CatalogType::Pseudo => CatalogType::Pseudo,
1990 CatalogType::RegClass => CatalogType::RegClass,
1991 CatalogType::RegProc => CatalogType::RegProc,
1992 CatalogType::RegType => CatalogType::RegType,
1993 CatalogType::String => CatalogType::String,
1994 CatalogType::Time => CatalogType::Time,
1995 CatalogType::Timestamp => CatalogType::Timestamp,
1996 CatalogType::TimestampTz => CatalogType::TimestampTz,
1997 CatalogType::Uuid => CatalogType::Uuid,
1998 CatalogType::VarChar => CatalogType::VarChar,
1999 CatalogType::Int2Vector => CatalogType::Int2Vector,
2000 CatalogType::MzAclItem => CatalogType::MzAclItem,
2001 };
2002
2003 BuiltinType {
2004 name: builtin.name,
2005 schema: builtin.schema,
2006 oid: builtin.oid,
2007 details: CatalogTypeDetails {
2008 array_id: builtin.details.array_id,
2009 typ,
2010 pg_metadata: builtin.details.pg_metadata.clone(),
2011 },
2012 }
2013 }
2014
2015 pub fn config(&self) -> &mz_sql::catalog::CatalogConfig {
2016 &self.config
2017 }
2018
2019 pub fn resolve_database(&self, database_name: &str) -> Result<&Database, SqlCatalogError> {
2020 match self.database_by_name.get(database_name) {
2021 Some(id) => Ok(&self.database_by_id[id]),
2022 None => Err(SqlCatalogError::UnknownDatabase(database_name.into())),
2023 }
2024 }
2025
2026 pub fn resolve_schema(
2027 &self,
2028 current_database: Option<&DatabaseId>,
2029 database_name: Option<&str>,
2030 schema_name: &str,
2031 conn_id: &ConnectionId,
2032 ) -> Result<&Schema, SqlCatalogError> {
2033 let database_spec = match database_name {
2034 Some(database) => Some(ResolvedDatabaseSpecifier::Id(
2039 self.resolve_database(database)?.id().clone(),
2040 )),
2041 None => current_database.map(|id| ResolvedDatabaseSpecifier::Id(id.clone())),
2042 };
2043
2044 if let Some(database_spec) = database_spec {
2046 if let Ok(schema) =
2047 self.resolve_schema_in_database(&database_spec, schema_name, conn_id)
2048 {
2049 return Ok(schema);
2050 }
2051 }
2052
2053 if let Ok(schema) = self.resolve_schema_in_database(
2055 &ResolvedDatabaseSpecifier::Ambient,
2056 schema_name,
2057 conn_id,
2058 ) {
2059 return Ok(schema);
2060 }
2061
2062 Err(SqlCatalogError::UnknownSchema(schema_name.into()))
2063 }
2064
2065 pub fn resolve_system_schema(&self, name: &'static str) -> SchemaId {
2069 self.ambient_schemas_by_name[name]
2070 }
2071
2072 pub fn resolve_search_path(
2073 &self,
2074 session: &dyn SessionMetadata,
2075 ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
2076 let database = self
2077 .database_by_name
2078 .get(session.database())
2079 .map(|id| id.clone());
2080
2081 session
2082 .search_path()
2083 .iter()
2084 .map(|schema| {
2085 self.resolve_schema(database.as_ref(), None, schema.as_str(), session.conn_id())
2086 })
2087 .filter_map(|schema| schema.ok())
2088 .map(|schema| (schema.name().database.clone(), schema.id().clone()))
2089 .collect()
2090 }
2091
2092 pub fn effective_search_path(
2093 &self,
2094 search_path: &[(ResolvedDatabaseSpecifier, SchemaSpecifier)],
2095 include_temp_schema: bool,
2096 ) -> Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)> {
2097 let mut v = Vec::with_capacity(search_path.len() + 3);
2098 let temp_schema = (
2100 ResolvedDatabaseSpecifier::Ambient,
2101 SchemaSpecifier::Temporary,
2102 );
2103 if include_temp_schema && !search_path.contains(&temp_schema) {
2104 v.push(temp_schema);
2105 }
2106 let default_schemas = [
2107 (
2108 ResolvedDatabaseSpecifier::Ambient,
2109 SchemaSpecifier::Id(self.get_mz_catalog_schema_id()),
2110 ),
2111 (
2112 ResolvedDatabaseSpecifier::Ambient,
2113 SchemaSpecifier::Id(self.get_pg_catalog_schema_id()),
2114 ),
2115 ];
2116 for schema in default_schemas.into_iter() {
2117 if !search_path.contains(&schema) {
2118 v.push(schema);
2119 }
2120 }
2121 v.extend_from_slice(search_path);
2122 v
2123 }
2124
2125 pub fn resolve_cluster(&self, name: &str) -> Result<&Cluster, SqlCatalogError> {
2126 let id = self
2127 .clusters_by_name
2128 .get(name)
2129 .ok_or_else(|| SqlCatalogError::UnknownCluster(name.to_string()))?;
2130 Ok(&self.clusters_by_id[id])
2131 }
2132
2133 pub fn resolve_builtin_cluster(&self, cluster: &BuiltinCluster) -> &Cluster {
2134 let id = self
2135 .clusters_by_name
2136 .get(cluster.name)
2137 .expect("failed to lookup BuiltinCluster by name");
2138 self.clusters_by_id
2139 .get(id)
2140 .expect("failed to lookup BuiltinCluster by ID")
2141 }
2142
2143 pub fn resolve_cluster_replica(
2144 &self,
2145 cluster_replica_name: &QualifiedReplica,
2146 ) -> Result<&ClusterReplica, SqlCatalogError> {
2147 let cluster = self.resolve_cluster(cluster_replica_name.cluster.as_str())?;
2148 let replica_name = cluster_replica_name.replica.as_str();
2149 let replica_id = cluster
2150 .replica_id(replica_name)
2151 .ok_or_else(|| SqlCatalogError::UnknownClusterReplica(replica_name.to_string()))?;
2152 Ok(cluster.replica(replica_id).expect("Must exist"))
2153 }
2154
2155 #[allow(clippy::useless_let_if_seq)]
2161 pub fn resolve(
2162 &self,
2163 get_schema_entries: fn(&Schema) -> &BTreeMap<String, CatalogItemId>,
2164 current_database: Option<&DatabaseId>,
2165 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2166 name: &PartialItemName,
2167 conn_id: &ConnectionId,
2168 err_gen: fn(String) -> SqlCatalogError,
2169 ) -> Result<&CatalogEntry, SqlCatalogError> {
2170 let schemas = match &name.schema {
2175 Some(schema_name) => {
2176 match self.resolve_schema(
2177 current_database,
2178 name.database.as_deref(),
2179 schema_name,
2180 conn_id,
2181 ) {
2182 Ok(schema) => vec![(schema.name.database.clone(), schema.id.clone())],
2183 Err(e) => return Err(e),
2184 }
2185 }
2186 None => match self
2187 .try_get_schema(
2188 &ResolvedDatabaseSpecifier::Ambient,
2189 &SchemaSpecifier::Temporary,
2190 conn_id,
2191 )
2192 .and_then(|schema| schema.items.get(&name.item))
2193 {
2194 Some(id) => return Ok(self.get_entry(id)),
2195 None => search_path.to_vec(),
2196 },
2197 };
2198
2199 for (database_spec, schema_spec) in &schemas {
2200 let Some(schema) = self.try_get_schema(database_spec, schema_spec, conn_id) else {
2203 continue;
2204 };
2205
2206 if let Some(id) = get_schema_entries(schema).get(&name.item) {
2207 return Ok(&self.entry_by_id[id]);
2208 }
2209 }
2210
2211 let mz_internal_schema = SchemaSpecifier::Id(self.get_mz_internal_schema_id());
2216 if schemas.iter().any(|(_, spec)| *spec == mz_internal_schema) {
2217 for schema_id in [
2218 self.get_mz_catalog_unstable_schema_id(),
2219 self.get_mz_introspection_schema_id(),
2220 ] {
2221 let schema = self.get_schema(
2222 &ResolvedDatabaseSpecifier::Ambient,
2223 &SchemaSpecifier::Id(schema_id),
2224 conn_id,
2225 );
2226
2227 if let Some(id) = get_schema_entries(schema).get(&name.item) {
2228 debug!(
2229 github_27831 = true,
2230 "encountered use of outdated schema `mz_internal` for relation: {name}",
2231 );
2232 return Ok(&self.entry_by_id[id]);
2233 }
2234 }
2235 }
2236
2237 Err(err_gen(name.to_string()))
2238 }
2239
2240 pub fn resolve_entry(
2242 &self,
2243 current_database: Option<&DatabaseId>,
2244 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2245 name: &PartialItemName,
2246 conn_id: &ConnectionId,
2247 ) -> Result<&CatalogEntry, SqlCatalogError> {
2248 self.resolve(
2249 |schema| &schema.items,
2250 current_database,
2251 search_path,
2252 name,
2253 conn_id,
2254 SqlCatalogError::UnknownItem,
2255 )
2256 }
2257
2258 pub fn resolve_function(
2260 &self,
2261 current_database: Option<&DatabaseId>,
2262 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2263 name: &PartialItemName,
2264 conn_id: &ConnectionId,
2265 ) -> Result<&CatalogEntry, SqlCatalogError> {
2266 self.resolve(
2267 |schema| &schema.functions,
2268 current_database,
2269 search_path,
2270 name,
2271 conn_id,
2272 |name| SqlCatalogError::UnknownFunction {
2273 name,
2274 alternative: None,
2275 },
2276 )
2277 }
2278
2279 pub fn resolve_type(
2281 &self,
2282 current_database: Option<&DatabaseId>,
2283 search_path: &Vec<(ResolvedDatabaseSpecifier, SchemaSpecifier)>,
2284 name: &PartialItemName,
2285 conn_id: &ConnectionId,
2286 ) -> Result<&CatalogEntry, SqlCatalogError> {
2287 static NON_PG_CATALOG_TYPES: LazyLock<
2288 BTreeMap<&'static str, &'static BuiltinType<NameReference>>,
2289 > = LazyLock::new(|| {
2290 BUILTINS::types()
2291 .filter(|typ| typ.schema != PG_CATALOG_SCHEMA)
2292 .map(|typ| (typ.name, typ))
2293 .collect()
2294 });
2295
2296 let entry = self.resolve(
2297 |schema| &schema.types,
2298 current_database,
2299 search_path,
2300 name,
2301 conn_id,
2302 |name| SqlCatalogError::UnknownType { name },
2303 )?;
2304
2305 if conn_id != &SYSTEM_CONN_ID && name.schema.as_deref() == Some(PG_CATALOG_SCHEMA) {
2306 if let Some(typ) = NON_PG_CATALOG_TYPES.get(entry.name().item.as_str()) {
2307 warn!(
2308 "user specified an incorrect schema of {} for the type {}, which should be in \
2309 the {} schema. This works now due to a bug but will be fixed in a later release.",
2310 PG_CATALOG_SCHEMA.quoted(),
2311 typ.name.quoted(),
2312 typ.schema.quoted(),
2313 )
2314 }
2315 }
2316
2317 Ok(entry)
2318 }
2319
2320 pub(super) fn get_comment_id(&self, object_id: ObjectId) -> CommentObjectId {
2322 match object_id {
2323 ObjectId::Item(item_id) => self.get_entry(&item_id).comment_object_id(),
2324 ObjectId::Role(role_id) => CommentObjectId::Role(role_id),
2325 ObjectId::Database(database_id) => CommentObjectId::Database(database_id),
2326 ObjectId::Schema((database, schema)) => CommentObjectId::Schema((database, schema)),
2327 ObjectId::Cluster(cluster_id) => CommentObjectId::Cluster(cluster_id),
2328 ObjectId::ClusterReplica(cluster_replica_id) => {
2329 CommentObjectId::ClusterReplica(cluster_replica_id)
2330 }
2331 ObjectId::NetworkPolicy(network_policy_id) => {
2332 CommentObjectId::NetworkPolicy(network_policy_id)
2333 }
2334 }
2335 }
2336
2337 pub fn system_config(&self) -> &SystemVars {
2339 &self.system_configuration
2340 }
2341
2342 pub fn cluster_scoped_optimizer_overrides(
2346 &self,
2347 cluster_id: ClusterId,
2348 ) -> OptimizerFeatureOverrides {
2349 self.scoped_system_parameters
2350 .cluster
2351 .get(&cluster_id)
2352 .cloned()
2353 .map(OptimizerFeatureOverrides::from)
2354 .unwrap_or_default()
2355 }
2356
2357 pub fn scoped_system_parameters(&self) -> &ScopedParameters {
2360 &self.scoped_system_parameters
2361 }
2362
2363 pub fn system_config_mut(&mut self) -> &mut SystemVars {
2365 Arc::make_mut(&mut self.system_configuration)
2366 }
2367
2368 pub fn dump(&self, unfinalized_shards: Option<BTreeSet<String>>) -> Result<String, Error> {
2378 let mut dump = serde_json::to_value(&self).map_err(|e| {
2380 Error::new(ErrorKind::Unstructured(format!(
2381 "internal error: could not dump catalog: {}",
2384 e
2385 )))
2386 })?;
2387
2388 let dump_obj = dump.as_object_mut().expect("state must have been dumped");
2389 dump_obj.insert(
2391 "system_parameter_defaults".into(),
2392 serde_json::json!(self.system_config().defaults()),
2393 );
2394 if let Some(unfinalized_shards) = unfinalized_shards {
2396 dump_obj
2397 .get_mut("storage_metadata")
2398 .expect("known to exist")
2399 .as_object_mut()
2400 .expect("storage_metadata is an object")
2401 .insert(
2402 "unfinalized_shards".into(),
2403 serde_json::json!(unfinalized_shards),
2404 );
2405 }
2406 let temporary_gids: Vec<_> = self
2411 .entry_by_global_id
2412 .iter()
2413 .filter(|(_gid, item_id)| self.get_entry(item_id).conn_id().is_some())
2414 .map(|(gid, _item_id)| *gid)
2415 .collect();
2416 if !temporary_gids.is_empty() {
2417 let gids = dump_obj
2418 .get_mut("entry_by_global_id")
2419 .expect("known_to_exist")
2420 .as_object_mut()
2421 .expect("entry_by_global_id is an object");
2422 for gid in temporary_gids {
2423 gids.remove(&gid.to_string());
2424 }
2425 }
2426 dump_obj.remove("role_auth_by_id");
2429 dump_obj.remove("mock_authentication_nonce");
2434
2435 Ok(serde_json::to_string_pretty(&dump).expect("cannot fail on serde_json::Value"))
2437 }
2438
2439 pub fn availability_zones(&self) -> &[String] {
2440 &self.availability_zones
2441 }
2442
2443 pub fn concretize_replica_location(
2444 &self,
2445 location: mz_catalog::durable::ReplicaLocation,
2446 allowed_sizes: &Vec<String>,
2447 allowed_availability_zones: Option<&[String]>,
2448 allow_disabled: bool,
2449 ) -> Result<ReplicaLocation, Error> {
2450 let location = match location {
2451 mz_catalog::durable::ReplicaLocation::Unmanaged {
2452 storagectl_addrs,
2453 computectl_addrs,
2454 } => {
2455 if allowed_availability_zones.is_some() {
2456 return Err(Error {
2457 kind: ErrorKind::Internal(
2458 "tried concretize unmanaged replica with specific availability_zones"
2459 .to_string(),
2460 ),
2461 });
2462 }
2463 ReplicaLocation::Unmanaged(UnmanagedReplicaLocation {
2464 storagectl_addrs,
2465 computectl_addrs,
2466 })
2467 }
2468 mz_catalog::durable::ReplicaLocation::Managed {
2469 size,
2470 availability_zones,
2476 billed_as,
2477 internal,
2478 pending,
2479 } => {
2480 self.ensure_valid_replica_size(allowed_sizes, &size, allow_disabled)?;
2481 let cluster_replica_sizes = &self.cluster_replica_sizes;
2482
2483 ReplicaLocation::Managed(ManagedReplicaLocation {
2484 allocation: cluster_replica_sizes
2485 .0
2486 .get(&size)
2487 .expect("catalog out of sync")
2488 .clone(),
2489 availability_zones: match allowed_availability_zones {
2490 Some(azs) => azs.to_vec(),
2491 None => availability_zones,
2492 },
2493 size,
2494 billed_as,
2495 internal,
2496 pending,
2497 })
2498 }
2499 };
2500 Ok(location)
2501 }
2502
2503 pub(crate) fn ensure_valid_replica_size(
2504 &self,
2505 allowed_sizes: &[String],
2506 size: &String,
2507 allow_disabled: bool,
2508 ) -> Result<(), Error> {
2509 let cluster_replica_sizes = &self.cluster_replica_sizes;
2510
2511 if !cluster_replica_sizes.0.contains_key(size)
2512 || (!allowed_sizes.is_empty() && !allowed_sizes.contains(size))
2513 || (!allow_disabled && cluster_replica_sizes.0[size].disabled)
2514 {
2515 let mut entries = cluster_replica_sizes
2516 .enabled_allocations()
2517 .collect::<Vec<_>>();
2518
2519 if !allowed_sizes.is_empty() {
2520 let allowed_sizes = BTreeSet::<&String>::from_iter(allowed_sizes.iter());
2521 entries.retain(|(name, _)| allowed_sizes.contains(name));
2522 }
2523
2524 entries.sort_by_key(
2525 |(
2526 _name,
2527 ReplicaAllocation {
2528 scale, cpu_limit, ..
2529 },
2530 )| (scale, cpu_limit),
2531 );
2532
2533 Err(Error {
2534 kind: ErrorKind::InvalidClusterReplicaSize {
2535 size: size.to_owned(),
2536 expected: entries.into_iter().map(|(name, _)| name.clone()).collect(),
2537 },
2538 })
2539 } else {
2540 Ok(())
2541 }
2542 }
2543
2544 pub fn ensure_not_reserved_role(&self, role_id: &RoleId) -> Result<(), Error> {
2545 if role_id.is_builtin() {
2546 let role = self.get_role(role_id);
2547 Err(Error::new(ErrorKind::ReservedRoleName(
2548 role.name().to_string(),
2549 )))
2550 } else {
2551 Ok(())
2552 }
2553 }
2554
2555 pub fn ensure_not_reserved_network_policy(
2556 &self,
2557 network_policy_id: &NetworkPolicyId,
2558 ) -> Result<(), Error> {
2559 if network_policy_id.is_builtin() {
2560 let policy = self.get_network_policy(network_policy_id);
2561 Err(Error::new(ErrorKind::ReservedNetworkPolicyName(
2562 policy.name.clone(),
2563 )))
2564 } else {
2565 Ok(())
2566 }
2567 }
2568
2569 pub fn ensure_grantable_role(&self, role_id: &RoleId) -> Result<(), Error> {
2570 let is_grantable = !role_id.is_public() && !role_id.is_system();
2571 if is_grantable {
2572 Ok(())
2573 } else {
2574 let role = self.get_role(role_id);
2575 Err(Error::new(ErrorKind::UngrantableRoleName(
2576 role.name().to_string(),
2577 )))
2578 }
2579 }
2580
2581 pub fn ensure_not_system_role(&self, role_id: &RoleId) -> Result<(), Error> {
2582 if role_id.is_system() {
2583 let role = self.get_role(role_id);
2584 Err(Error::new(ErrorKind::ReservedSystemRoleName(
2585 role.name().to_string(),
2586 )))
2587 } else {
2588 Ok(())
2589 }
2590 }
2591
2592 pub fn ensure_not_predefined_role(&self, role_id: &RoleId) -> Result<(), Error> {
2593 if role_id.is_predefined() {
2594 let role = self.get_role(role_id);
2595 Err(Error::new(ErrorKind::ReservedSystemRoleName(
2596 role.name().to_string(),
2597 )))
2598 } else {
2599 Ok(())
2600 }
2601 }
2602
2603 pub(crate) fn add_to_audit_log(
2606 system_configuration: &SystemVars,
2607 oracle_write_ts: mz_repr::Timestamp,
2608 session: Option<&ConnMeta>,
2609 tx: &mut mz_catalog::durable::Transaction,
2610 audit_events: &mut Vec<VersionedEvent>,
2611 event_type: EventType,
2612 object_type: ObjectType,
2613 details: EventDetails,
2614 ) -> Result<(), Error> {
2615 let user = session.map(|session| session.user().name.to_string());
2616
2617 let occurred_at = match system_configuration.unsafe_mock_audit_event_timestamp() {
2620 Some(ts) => ts.into(),
2621 _ => oracle_write_ts.into(),
2622 };
2623 let id = tx.allocate_audit_log_id()?;
2624 let event = VersionedEvent::new(id, event_type, object_type, details, user, occurred_at);
2625 audit_events.push(event.clone());
2626 tx.insert_audit_log_event(event);
2627 Ok(())
2628 }
2629
2630 pub(super) fn get_owner_id(&self, id: &ObjectId, conn_id: &ConnectionId) -> Option<RoleId> {
2631 match id {
2632 ObjectId::Cluster(id) => Some(self.get_cluster(*id).owner_id()),
2633 ObjectId::ClusterReplica((cluster_id, replica_id)) => Some(
2634 self.get_cluster_replica(*cluster_id, *replica_id)
2635 .owner_id(),
2636 ),
2637 ObjectId::Database(id) => Some(self.get_database(id).owner_id()),
2638 ObjectId::Schema((database_spec, schema_spec)) => Some(
2639 self.get_schema(database_spec, schema_spec, conn_id)
2640 .owner_id(),
2641 ),
2642 ObjectId::Item(id) => Some(*self.get_entry(id).owner_id()),
2643 ObjectId::Role(_) => None,
2644 ObjectId::NetworkPolicy(id) => Some(self.get_network_policy(id).owner_id.clone()),
2645 }
2646 }
2647
2648 pub(super) fn get_object_type(&self, object_id: &ObjectId) -> mz_sql::catalog::ObjectType {
2649 match object_id {
2650 ObjectId::Cluster(_) => mz_sql::catalog::ObjectType::Cluster,
2651 ObjectId::ClusterReplica(_) => mz_sql::catalog::ObjectType::ClusterReplica,
2652 ObjectId::Database(_) => mz_sql::catalog::ObjectType::Database,
2653 ObjectId::Schema(_) => mz_sql::catalog::ObjectType::Schema,
2654 ObjectId::Role(_) => mz_sql::catalog::ObjectType::Role,
2655 ObjectId::Item(id) => self.get_entry(id).item_type().into(),
2656 ObjectId::NetworkPolicy(_) => mz_sql::catalog::ObjectType::NetworkPolicy,
2657 }
2658 }
2659
2660 pub(super) fn get_system_object_type(
2661 &self,
2662 id: &SystemObjectId,
2663 ) -> mz_sql::catalog::SystemObjectType {
2664 match id {
2665 SystemObjectId::Object(object_id) => {
2666 SystemObjectType::Object(self.get_object_type(object_id))
2667 }
2668 SystemObjectId::System => SystemObjectType::System,
2669 }
2670 }
2671
2672 pub fn storage_metadata(&self) -> &StorageMetadata {
2676 &self.storage_metadata
2677 }
2678
2679 pub fn source_compaction_windows(
2681 &self,
2682 ids: impl IntoIterator<Item = CatalogItemId>,
2683 ) -> BTreeMap<CompactionWindow, BTreeSet<CatalogItemId>> {
2684 let mut cws: BTreeMap<CompactionWindow, BTreeSet<CatalogItemId>> = BTreeMap::new();
2685 let mut seen = BTreeSet::new();
2686 for item_id in ids {
2687 if !seen.insert(item_id) {
2688 continue;
2689 }
2690 let entry = self.get_entry(&item_id);
2691 match entry.item() {
2692 CatalogItem::Source(source) => {
2693 let source_cw = source.custom_logical_compaction_window.unwrap_or_default();
2694 cws.entry(source_cw).or_default().insert(item_id);
2695 }
2696 CatalogItem::Table(table) => {
2697 let table_cw = table.custom_logical_compaction_window.unwrap_or_default();
2698 match &table.data_source {
2699 TableDataSource::DataSource {
2700 desc:
2701 DataSourceDesc::IngestionExport { .. }
2702 | DataSourceDesc::Webhook { .. },
2704 timeline: _,
2705 } => {
2706 cws.entry(table_cw).or_default().insert(item_id);
2707 }
2708 TableDataSource::TableWrites { .. } => {}
2711 TableDataSource::DataSource {
2712 desc:
2713 DataSourceDesc::Ingestion { .. }
2714 | DataSourceDesc::OldSyntaxIngestion { .. }
2715 | DataSourceDesc::Introspection(_)
2716 | DataSourceDesc::Progress
2717 | DataSourceDesc::Catalog,
2718 ..
2719 } => {
2720 unreachable!(
2721 "unexpected DataSourceDesc for table {item_id}: {:?}",
2722 table.data_source
2723 )
2724 }
2725 }
2726 }
2727 _ => {
2728 continue;
2730 }
2731 }
2732 }
2733 cws
2734 }
2735
2736 pub fn comment_id_to_item_id(id: &CommentObjectId) -> Option<CatalogItemId> {
2737 match id {
2738 CommentObjectId::Table(id)
2739 | CommentObjectId::View(id)
2740 | CommentObjectId::MaterializedView(id)
2741 | CommentObjectId::Source(id)
2742 | CommentObjectId::Sink(id)
2743 | CommentObjectId::Index(id)
2744 | CommentObjectId::Func(id)
2745 | CommentObjectId::Connection(id)
2746 | CommentObjectId::Type(id)
2747 | CommentObjectId::Secret(id) => Some(*id),
2748 CommentObjectId::Role(_)
2749 | CommentObjectId::Database(_)
2750 | CommentObjectId::Schema(_)
2751 | CommentObjectId::Cluster(_)
2752 | CommentObjectId::ClusterReplica(_)
2753 | CommentObjectId::NetworkPolicy(_) => None,
2754 }
2755 }
2756
2757 pub fn get_comment_id_entry(&self, id: &CommentObjectId) -> Option<&CatalogEntry> {
2758 Self::comment_id_to_item_id(id).map(|id| self.get_entry(&id))
2759 }
2760
2761 pub fn comment_id_to_audit_log_name(
2762 &self,
2763 id: CommentObjectId,
2764 conn_id: &ConnectionId,
2765 ) -> String {
2766 match id {
2767 CommentObjectId::Table(id)
2768 | CommentObjectId::View(id)
2769 | CommentObjectId::MaterializedView(id)
2770 | CommentObjectId::Source(id)
2771 | CommentObjectId::Sink(id)
2772 | CommentObjectId::Index(id)
2773 | CommentObjectId::Func(id)
2774 | CommentObjectId::Connection(id)
2775 | CommentObjectId::Type(id)
2776 | CommentObjectId::Secret(id) => {
2777 let item = self.get_entry(&id);
2778 let name = self.resolve_full_name(item.name(), Some(conn_id));
2779 name.to_string()
2780 }
2781 CommentObjectId::Role(id) => self.get_role(&id).name.clone(),
2782 CommentObjectId::Database(id) => self.get_database(&id).name.clone(),
2783 CommentObjectId::Schema((spec, schema_id)) => {
2784 let schema = self.get_schema(&spec, &schema_id, conn_id);
2785 self.resolve_full_schema_name(&schema.name).to_string()
2786 }
2787 CommentObjectId::Cluster(id) => self.get_cluster(id).name.clone(),
2788 CommentObjectId::ClusterReplica((cluster_id, replica_id)) => {
2789 let cluster = self.get_cluster(cluster_id);
2790 let replica = self.get_cluster_replica(cluster_id, replica_id);
2791 QualifiedReplica {
2792 cluster: Ident::new_unchecked(cluster.name.clone()),
2793 replica: Ident::new_unchecked(replica.name.clone()),
2794 }
2795 .to_string()
2796 }
2797 CommentObjectId::NetworkPolicy(id) => self.get_network_policy(&id).name.clone(),
2798 }
2799 }
2800
2801 pub fn mock_authentication_nonce(&self) -> String {
2802 self.mock_authentication_nonce.clone().unwrap_or_default()
2803 }
2804}
2805
2806impl ConnectionResolver for CatalogState {
2807 fn resolve_connection(
2808 &self,
2809 id: CatalogItemId,
2810 ) -> mz_storage_types::connections::Connection<InlinedConnection> {
2811 use mz_storage_types::connections::Connection::*;
2812 match self
2813 .get_entry(&id)
2814 .connection()
2815 .expect("catalog out of sync")
2816 .details
2817 .to_connection()
2818 {
2819 Kafka(conn) => Kafka(conn.into_inline_connection(self)),
2820 Postgres(conn) => Postgres(conn.into_inline_connection(self)),
2821 Csr(conn) => Csr(conn.into_inline_connection(self)),
2822 GlueSchemaRegistry(conn) => GlueSchemaRegistry(conn.into_inline_connection(self)),
2823 Ssh(conn) => Ssh(conn),
2824 Aws(conn) => Aws(conn),
2825 AwsPrivatelink(conn) => AwsPrivatelink(conn),
2826 Gcp(conn) => Gcp(conn),
2827 MySql(conn) => MySql(conn.into_inline_connection(self)),
2828 SqlServer(conn) => SqlServer(conn.into_inline_connection(self)),
2829 IcebergCatalog(conn) => IcebergCatalog(conn.into_inline_connection(self)),
2830 }
2831 }
2832}
2833
2834impl OptimizerCatalog for CatalogState {
2835 fn get_entry(&self, id: &GlobalId) -> CatalogCollectionEntry {
2836 CatalogState::get_entry_by_global_id(self, id)
2837 }
2838 fn get_entry_by_item_id(&self, id: &CatalogItemId) -> &CatalogEntry {
2839 CatalogState::get_entry(self, id)
2840 }
2841 fn resolve_full_name(
2842 &self,
2843 name: &QualifiedItemName,
2844 conn_id: Option<&ConnectionId>,
2845 ) -> FullItemName {
2846 CatalogState::resolve_full_name(self, name, conn_id)
2847 }
2848 fn get_indexes_on(
2849 &self,
2850 id: GlobalId,
2851 cluster: ClusterId,
2852 ) -> Box<dyn Iterator<Item = (GlobalId, &Index)> + '_> {
2853 Box::new(CatalogState::get_indexes_on(self, id, cluster))
2854 }
2855}
2856
2857impl OptimizerCatalog for Catalog {
2858 fn get_entry(&self, id: &GlobalId) -> CatalogCollectionEntry {
2859 self.state.get_entry_by_global_id(id)
2860 }
2861
2862 fn get_entry_by_item_id(&self, id: &CatalogItemId) -> &CatalogEntry {
2863 self.state.get_entry(id)
2864 }
2865
2866 fn resolve_full_name(
2867 &self,
2868 name: &QualifiedItemName,
2869 conn_id: Option<&ConnectionId>,
2870 ) -> FullItemName {
2871 self.state.resolve_full_name(name, conn_id)
2872 }
2873
2874 fn get_indexes_on(
2875 &self,
2876 id: GlobalId,
2877 cluster: ClusterId,
2878 ) -> Box<dyn Iterator<Item = (GlobalId, &Index)> + '_> {
2879 Box::new(self.state.get_indexes_on(id, cluster))
2880 }
2881}
2882
2883impl Catalog {
2884 pub fn as_optimizer_catalog(self: Arc<Self>) -> Arc<dyn OptimizerCatalog> {
2885 self
2886 }
2887}
2888
2889#[cfg(test)]
2890mod tests {
2891 use super::*;
2892
2893 #[mz_ore::test(tokio::test)]
2898 #[cfg_attr(miri, ignore)] async fn item_dependents_deep_chain_no_stack_overflow() {
2900 use mz_ore::cast::CastFrom;
2901
2902 Catalog::with_debug(|mut catalog| async move {
2903 const DEPTH: usize = 100_000;
2906 const BASE: u64 = 1 << 40;
2909
2910 let template = catalog
2914 .state
2915 .entry_by_id
2916 .values()
2917 .find(|entry| matches!(entry.item(), CatalogItem::Log(_)))
2918 .expect("debug catalog has log sources")
2919 .clone();
2920
2921 for i in 0..=DEPTH {
2923 let id = CatalogItemId::User(BASE + u64::cast_from(i));
2924 let mut entry = template.clone();
2925 entry.id = id;
2926 entry.referenced_by = Vec::new();
2927 entry.used_by = if i < DEPTH {
2928 vec![CatalogItemId::User(BASE + u64::cast_from(i + 1))]
2929 } else {
2930 Vec::new()
2931 };
2932 catalog.state.entry_by_id.insert(id, entry);
2933 }
2934
2935 let mut seen = BTreeSet::new();
2936 let dependents = catalog
2937 .state
2938 .item_dependents(CatalogItemId::User(BASE), &mut seen);
2939
2940 assert_eq!(dependents.len(), DEPTH + 1);
2942 for (offset, dependent) in dependents.iter().enumerate() {
2945 let expected = CatalogItemId::User(BASE + u64::cast_from(DEPTH - offset));
2946 assert_eq!(dependent, &ObjectId::Item(expected));
2947 }
2948
2949 catalog.expire().await;
2950 })
2951 .await
2952 }
2953}