1use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet};
14use std::sync::Arc;
15use std::sync::atomic;
16use std::time::Duration;
17
18use itertools::Itertools;
19use mz_adapter_types::cluster_state::ExpectedClusterState;
20use mz_adapter_types::compaction::CompactionWindow;
21use mz_adapter_types::connection::ConnectionId;
22use mz_adapter_types::dyncfgs::{
23 ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT, WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL,
24 WITH_0DT_DEPLOYMENT_MAX_WAIT,
25};
26use mz_audit_log::{
27 CreateOrDropClusterReplicaReasonV1, EventDetails, EventType, IdFullNameV1, IdNameV1,
28 ObjectType, SchedulingDecisionsWithReasonsV2, VersionedEvent,
29};
30use mz_catalog::SYSTEM_CONN_ID;
31use mz_catalog::builtin::BuiltinLog;
32use mz_catalog::durable::{NetworkPolicy, Snapshot, Transaction};
33use mz_catalog::expr_cache::LocalExpressions;
34use mz_catalog::memory::error::{AmbiguousRename, Error, ErrorKind};
35use mz_catalog::memory::objects::{
36 CatalogEntry, CatalogItem, ClusterConfig, DataSourceDesc, DefaultPrivileges, SourceReferences,
37 StateDiff, StateUpdate, StateUpdateKind, TemporaryItem,
38};
39use mz_controller::clusters::{ManagedReplicaLocation, ReplicaConfig, ReplicaLocation};
40use mz_controller_types::{ClusterId, ReplicaId};
41use mz_ore::collections::HashSet;
42use mz_ore::instrument;
43use mz_persist_types::ShardId;
44use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap, merge_mz_acl_items};
45use mz_repr::network_policy_id::NetworkPolicyId;
46use mz_repr::optimize::OptimizerFeatures;
47use mz_repr::role_id::RoleId;
48use mz_repr::{CatalogItemId, ColumnName, GlobalId, SqlColumnType, strconv};
49use mz_sql::ast::RawDataType;
50use mz_sql::catalog::{
51 AutoProvisionSource, CatalogDatabase, CatalogError as SqlCatalogError,
52 CatalogItem as SqlCatalogItem, CatalogRole, CatalogSchema, DefaultPrivilegeAclItem,
53 DefaultPrivilegeObject, PasswordAction, PasswordConfig, RoleAttributesRaw, RoleMembership,
54 RoleVars,
55};
56use mz_sql::names::{
57 CommentObjectId, DatabaseId, FullItemName, ObjectId, QualifiedItemName,
58 ResolvedDatabaseSpecifier, SchemaId, SchemaSpecifier, SystemObjectId,
59};
60use mz_sql::plan::{NetworkPolicyRule, PlanError};
61use mz_sql::session::user::{MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID};
62use mz_sql::session::vars::OwnedVarInput;
63use mz_sql::session::vars::{Value as VarValue, VarInput};
64use mz_sql::{DEFAULT_SCHEMA, rbac};
65use mz_sql_parser::ast::{QualifiedReplica, Value};
66use mz_storage_client::storage_collections::StorageCollections;
67use serde::{Deserialize, Serialize};
68use tracing::{info, trace};
69
70use crate::AdapterError;
71use crate::catalog::state::LocalExpressionCache;
72use crate::catalog::{
73 BuiltinTableUpdate, Catalog, CatalogState, UpdatePrivilegeVariant,
74 catalog_type_to_audit_object_type, comment_id_to_audit_object_type, is_reserved_name,
75 is_reserved_role_name, object_type_to_audit_object_type,
76 system_object_type_to_audit_object_type,
77};
78use crate::config::{ScopedParameters, ScopedParametersScope};
79use crate::coord::ConnMeta;
80use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdate;
81use crate::coord::cluster_scheduling::SchedulingDecision;
82use crate::util::ResultExt;
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct InjectedAuditEvent {
90 pub event_type: EventType,
91 pub object_type: ObjectType,
92 pub details: EventDetails,
93 pub user: Option<String>,
94}
95
96#[derive(Debug, Clone)]
97pub enum Op {
98 AlterRetainHistory {
99 id: CatalogItemId,
100 value: Option<Value>,
101 window: CompactionWindow,
102 },
103 AlterSourceTimestampInterval {
104 id: CatalogItemId,
105 value: Option<Value>,
106 interval: Duration,
107 },
108 AlterRole {
109 id: RoleId,
110 name: String,
111 attributes: RoleAttributesRaw,
112 nopassword: bool,
113 vars: RoleVars,
114 },
115 AlterNetworkPolicy {
116 id: NetworkPolicyId,
117 rules: Vec<NetworkPolicyRule>,
118 name: String,
119 owner_id: RoleId,
120 },
121 AlterAddColumn {
122 id: CatalogItemId,
123 new_global_id: GlobalId,
124 name: ColumnName,
125 typ: SqlColumnType,
126 sql: RawDataType,
127 },
128 AlterMaterializedViewApplyReplacement {
129 id: CatalogItemId,
130 replacement_id: CatalogItemId,
131 },
132 CreateDatabase {
133 name: String,
134 owner_id: RoleId,
135 },
136 CreateSchema {
137 database_id: ResolvedDatabaseSpecifier,
138 schema_name: String,
139 owner_id: RoleId,
140 },
141 CreateRole {
142 name: String,
143 attributes: RoleAttributesRaw,
144 },
145 CreateCluster {
146 id: ClusterId,
147 name: String,
148 introspection_sources: Vec<&'static BuiltinLog>,
149 owner_id: RoleId,
150 config: ClusterConfig,
151 },
152 CreateClusterReplica {
153 cluster_id: ClusterId,
154 replica_id: ReplicaId,
155 name: String,
156 config: ReplicaConfig,
157 owner_id: RoleId,
158 reason: ReplicaCreateDropReason,
159 },
160 CreateItem {
161 id: CatalogItemId,
162 name: QualifiedItemName,
163 item: CatalogItem,
164 owner_id: RoleId,
165 },
166 CreateNetworkPolicy {
167 rules: Vec<NetworkPolicyRule>,
168 name: String,
169 owner_id: RoleId,
170 },
171 Comment {
172 object_id: CommentObjectId,
173 sub_component: Option<usize>,
174 comment: Option<String>,
175 },
176 DropObjects(Vec<DropObjectInfo>),
177 GrantRole {
178 role_id: RoleId,
179 member_id: RoleId,
180 grantor_id: RoleId,
181 },
182 RenameCluster {
183 id: ClusterId,
184 name: String,
185 to_name: String,
186 check_reserved_names: bool,
187 },
188 RenameClusterReplica {
189 cluster_id: ClusterId,
190 replica_id: ReplicaId,
191 name: QualifiedReplica,
192 to_name: String,
193 },
194 RenameItem {
195 id: CatalogItemId,
196 current_full_name: FullItemName,
197 to_name: String,
198 },
199 RenameSchema {
200 database_spec: ResolvedDatabaseSpecifier,
201 schema_spec: SchemaSpecifier,
202 new_name: String,
203 check_reserved_names: bool,
204 },
205 UpdateOwner {
206 id: ObjectId,
207 new_owner: RoleId,
208 },
209 UpdatePrivilege {
210 target_id: SystemObjectId,
211 privileges: Vec<MzAclItem>,
215 variant: UpdatePrivilegeVariant,
216 },
217 UpdateDefaultPrivilege {
218 privilege_object: DefaultPrivilegeObject,
219 privilege_acl_item: DefaultPrivilegeAclItem,
220 variant: UpdatePrivilegeVariant,
221 },
222 RevokeRole {
223 role_id: RoleId,
224 member_id: RoleId,
225 grantor_id: RoleId,
226 },
227 UpdateClusterConfig {
228 id: ClusterId,
229 name: String,
230 config: ClusterConfig,
231 },
232 UpdateClusterReplicaConfig {
233 cluster_id: ClusterId,
234 replica_id: ReplicaId,
235 config: ReplicaConfig,
236 },
237 UpdateItem {
238 id: CatalogItemId,
239 name: QualifiedItemName,
240 to_item: CatalogItem,
241 },
242 UpdateSourceReferences {
243 source_id: CatalogItemId,
244 references: SourceReferences,
245 },
246 UpdateSystemConfiguration {
247 name: String,
248 value: OwnedVarInput,
249 },
250 ResetSystemConfiguration {
251 name: String,
252 },
253 ResetAllSystemConfiguration,
254 UpdateScopedSystemParameters {
266 scoped: ScopedParameters,
267 prune_scope: Option<ScopedParametersScope>,
268 },
269 InjectAuditEvents {
275 events: Vec<InjectedAuditEvent>,
276 },
277 CheckClusterState {
284 cluster_id: ClusterId,
285 expected: ExpectedClusterState,
286 },
287}
288
289#[derive(Debug, Clone)]
293pub enum DropObjectInfo {
294 Cluster(ClusterId),
295 ClusterReplica((ClusterId, ReplicaId, ReplicaCreateDropReason)),
296 Database(DatabaseId),
297 Schema((ResolvedDatabaseSpecifier, SchemaSpecifier)),
298 Role(RoleId),
299 Item(CatalogItemId),
300 NetworkPolicy(NetworkPolicyId),
301}
302
303impl DropObjectInfo {
304 pub(crate) fn manual_drop_from_object_id(id: ObjectId) -> Self {
307 match id {
308 ObjectId::Cluster(cluster_id) => DropObjectInfo::Cluster(cluster_id),
309 ObjectId::ClusterReplica((cluster_id, replica_id)) => DropObjectInfo::ClusterReplica((
310 cluster_id,
311 replica_id,
312 ReplicaCreateDropReason::Manual,
313 )),
314 ObjectId::Database(database_id) => DropObjectInfo::Database(database_id),
315 ObjectId::Schema(schema) => DropObjectInfo::Schema(schema),
316 ObjectId::Role(role_id) => DropObjectInfo::Role(role_id),
317 ObjectId::Item(item_id) => DropObjectInfo::Item(item_id),
318 ObjectId::NetworkPolicy(policy_id) => DropObjectInfo::NetworkPolicy(policy_id),
319 }
320 }
321
322 fn to_object_id(&self) -> ObjectId {
325 match &self {
326 DropObjectInfo::Cluster(cluster_id) => ObjectId::Cluster(cluster_id.clone()),
327 DropObjectInfo::ClusterReplica((cluster_id, replica_id, _reason)) => {
328 ObjectId::ClusterReplica((cluster_id.clone(), replica_id.clone()))
329 }
330 DropObjectInfo::Database(database_id) => ObjectId::Database(database_id.clone()),
331 DropObjectInfo::Schema(schema) => ObjectId::Schema(schema.clone()),
332 DropObjectInfo::Role(role_id) => ObjectId::Role(role_id.clone()),
333 DropObjectInfo::Item(item_id) => ObjectId::Item(item_id.clone()),
334 DropObjectInfo::NetworkPolicy(network_policy_id) => {
335 ObjectId::NetworkPolicy(network_policy_id.clone())
336 }
337 }
338 }
339}
340
341#[derive(Debug, Clone)]
343pub enum ReplicaCreateDropReason {
344 Manual,
349 ClusterScheduling(Vec<SchedulingDecision>),
352 Retired,
356}
357
358impl ReplicaCreateDropReason {
359 pub fn into_audit_log(
360 self,
361 ) -> (
362 CreateOrDropClusterReplicaReasonV1,
363 Option<SchedulingDecisionsWithReasonsV2>,
364 ) {
365 let (reason, scheduling_policies) = match self {
366 ReplicaCreateDropReason::Manual => (CreateOrDropClusterReplicaReasonV1::Manual, None),
367 ReplicaCreateDropReason::ClusterScheduling(scheduling_decisions) => (
368 CreateOrDropClusterReplicaReasonV1::Schedule,
369 Some(scheduling_decisions),
370 ),
371 ReplicaCreateDropReason::Retired => (CreateOrDropClusterReplicaReasonV1::Retired, None),
372 };
373 (
374 reason,
375 scheduling_policies
376 .as_ref()
377 .map(SchedulingDecision::reasons_to_audit_log_reasons),
378 )
379 }
380}
381
382pub struct TransactionResult {
383 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
384 pub catalog_updates: Vec<ParsedStateUpdate>,
386 pub audit_events: Vec<VersionedEvent>,
387}
388
389#[derive(Debug, Clone, Copy)]
390enum TransactInnerMode {
391 Commit,
395 DryRun,
402}
403
404impl Catalog {
405 fn should_audit_log_item(item: &CatalogItem) -> bool {
406 !item.is_temporary()
407 }
408
409 fn temporary_ids(
412 &self,
413 ops: &[Op],
414 temporary_drops: BTreeSet<(&ConnectionId, String)>,
415 ) -> Result<BTreeSet<CatalogItemId>, Error> {
416 let mut creating = BTreeSet::new();
417 let mut temporary_ids = BTreeSet::new();
418 for op in ops.iter() {
419 if let Op::CreateItem {
420 id,
421 name,
422 item,
423 owner_id: _,
424 } = op
425 {
426 if let Some(conn_id) = item.conn_id() {
427 if self.item_exists_in_temp_schemas(conn_id, &name.item)
428 && !temporary_drops.contains(&(conn_id, name.item.clone()))
429 || creating.contains(&(conn_id, &name.item))
430 {
431 return Err(
432 SqlCatalogError::ItemAlreadyExists(*id, name.item.clone()).into()
433 );
434 } else {
435 creating.insert((conn_id, &name.item));
436 temporary_ids.insert(id.clone());
437 }
438 }
439 }
440 }
441 Ok(temporary_ids)
442 }
443
444 #[instrument(name = "catalog::transact")]
445 pub async fn transact(
446 &mut self,
447 storage_collections: Option<&mut Arc<dyn StorageCollections + Send + Sync>>,
450 oracle_write_ts: mz_repr::Timestamp,
451 session: Option<&ConnMeta>,
452 ops: Vec<Op>,
453 ) -> Result<TransactionResult, AdapterError> {
454 trace!("transact: {:?}", ops);
455 fail::fail_point!("catalog_transact", |arg| {
456 Err(AdapterError::Unstructured(anyhow::anyhow!(
457 "failpoint: {arg:?}"
458 )))
459 });
460
461 let drop_ids: BTreeSet<CatalogItemId> = ops
462 .iter()
463 .filter_map(|op| match op {
464 Op::DropObjects(drop_object_infos) => {
465 let ids = drop_object_infos.iter().map(|info| info.to_object_id());
466 let item_ids = ids.filter_map(|id| match id {
467 ObjectId::Item(id) => Some(id),
468 _ => None,
469 });
470 Some(item_ids)
471 }
472 _ => None,
473 })
474 .flatten()
475 .collect();
476 let temporary_drops = drop_ids
477 .iter()
478 .filter_map(|id| {
479 let entry = self.get_entry(id);
480 match entry.item().conn_id() {
481 Some(conn_id) => Some((conn_id, entry.name().item.clone())),
482 None => None,
483 }
484 })
485 .collect();
486
487 let temporary_ids = self.temporary_ids(&ops, temporary_drops)?;
488 let mut builtin_table_updates = vec![];
489 let mut catalog_updates = vec![];
490 let mut audit_events = vec![];
491 let mut storage = self.storage().await;
492 let mut tx = storage
493 .transaction()
494 .await
495 .unwrap_or_terminate("starting catalog transaction");
496
497 let new_state = Self::transact_inner(
498 TransactInnerMode::Commit,
499 storage_collections,
500 oracle_write_ts,
501 session,
502 ops,
503 temporary_ids,
504 &mut builtin_table_updates,
505 &mut catalog_updates,
506 &mut audit_events,
507 &mut tx,
508 &self.state,
509 )
510 .await?;
511
512 tx.commit(oracle_write_ts)
517 .await
518 .unwrap_or_terminate("catalog storage transaction commit must succeed");
519
520 drop(storage);
523 if let Some(new_state) = new_state {
524 self.transient_revision += 1;
525 self.shared_transient_revision
531 .store(self.transient_revision, atomic::Ordering::SeqCst);
532 self.state = new_state;
533 }
534
535 Ok(TransactionResult {
536 builtin_table_updates,
537 catalog_updates,
538 audit_events,
539 })
540 }
541
542 pub async fn transact_incremental_dry_run(
557 &self,
558 base_state: &CatalogState,
559 ops: Vec<Op>,
560 session: Option<&ConnMeta>,
561 prev_snapshot: Option<Snapshot>,
562 oracle_write_ts: mz_repr::Timestamp,
563 ) -> Result<(CatalogState, Snapshot), AdapterError> {
564 let temporary_ids = self.temporary_ids(&ops, BTreeSet::new())?;
567
568 let mut builtin_table_updates = vec![];
569 let mut catalog_updates = vec![];
570 let mut audit_events = vec![];
571 let mut storage = self.storage().await;
572 let mut tx = if let Some(snapshot) = prev_snapshot {
573 storage
576 .transaction_from_snapshot(snapshot)
577 .unwrap_or_terminate("starting catalog transaction from snapshot")
578 } else {
579 storage
582 .transaction()
583 .await
584 .unwrap_or_terminate("starting catalog transaction")
585 };
586
587 let new_state = Self::transact_inner(
589 TransactInnerMode::DryRun,
590 None,
591 oracle_write_ts,
592 session,
593 ops,
594 temporary_ids,
595 &mut builtin_table_updates,
596 &mut catalog_updates,
597 &mut audit_events,
598 &mut tx,
599 base_state,
600 )
601 .await?;
602
603 let new_snapshot = tx.current_snapshot();
606
607 drop(storage);
609
610 let state = new_state.unwrap_or_else(|| base_state.clone());
612 Ok((state, new_snapshot))
613 }
614
615 fn extract_expressions_from_ops(
619 ops: &[Op],
620 optimizer_features: &OptimizerFeatures,
621 ) -> BTreeMap<GlobalId, LocalExpressions> {
622 let mut exprs = BTreeMap::new();
623
624 for op in ops {
625 if let Op::CreateItem { item, .. } = op {
626 match item {
627 CatalogItem::View(view) => {
628 exprs.insert(
629 view.global_id,
630 LocalExpressions {
631 local_mir: (*view.locally_optimized_expr).clone(),
632 optimizer_features: optimizer_features.clone(),
633 },
634 );
635 }
636 CatalogItem::MaterializedView(mv) => {
637 exprs.insert(
638 mv.global_id_writes(),
639 LocalExpressions {
640 local_mir: (*mv.locally_optimized_expr).clone(),
641 optimizer_features: optimizer_features.clone(),
642 },
643 );
644 }
645 CatalogItem::Table(_)
646 | CatalogItem::Source(_)
647 | CatalogItem::Log(_)
648 | CatalogItem::Sink(_)
649 | CatalogItem::Index(_)
650 | CatalogItem::Type(_)
651 | CatalogItem::Func(_)
652 | CatalogItem::Secret(_)
653 | CatalogItem::Connection(_) => {}
654 }
655 }
656 }
657
658 exprs
659 }
660
661 #[instrument(name = "catalog::transact_inner")]
669 async fn transact_inner(
670 mode: TransactInnerMode,
671 storage_collections: Option<&mut Arc<dyn StorageCollections + Send + Sync>>,
672 oracle_write_ts: mz_repr::Timestamp,
673 session: Option<&ConnMeta>,
674 ops: Vec<Op>,
675 temporary_ids: BTreeSet<CatalogItemId>,
676 builtin_table_updates: &mut Vec<BuiltinTableUpdate>,
677 parsed_catalog_updates: &mut Vec<ParsedStateUpdate>,
678 audit_events: &mut Vec<VersionedEvent>,
679 tx: &mut Transaction<'_>,
680 state: &CatalogState,
681 ) -> Result<Option<CatalogState>, AdapterError> {
682 let mut preliminary_state = Cow::Borrowed(state);
709
710 let mut state = Cow::Borrowed(state);
712
713 if ops.is_empty() {
714 return Ok(None);
715 }
716
717 let optimizer_features = OptimizerFeatures::from(state.system_config());
720 let cached_exprs = Self::extract_expressions_from_ops(&ops, &optimizer_features);
721
722 let mut storage_collections_to_create = BTreeSet::new();
723 let mut storage_collections_to_drop = BTreeSet::new();
724 let mut storage_collections_to_register = BTreeMap::new();
725
726 let mut updates = Vec::new();
727
728 for op in ops {
729 let temporary_item_updates = Self::transact_op(
730 oracle_write_ts,
731 session,
732 op,
733 &temporary_ids,
734 audit_events,
735 tx,
736 &*preliminary_state,
737 &mut storage_collections_to_create,
738 &mut storage_collections_to_drop,
739 &mut storage_collections_to_register,
740 )
741 .await?;
742
743 let upper = tx.upper();
748 let temporary_item_updates =
749 temporary_item_updates
750 .into_iter()
751 .map(|(item, diff)| StateUpdate {
752 kind: StateUpdateKind::TemporaryItem(item),
753 ts: upper,
754 diff,
755 });
756
757 let mut op_updates: Vec<_> = tx.get_and_commit_op_updates();
758 op_updates.extend(temporary_item_updates);
759 if !op_updates.is_empty() {
760 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
763 let (_op_builtin_table_updates, _op_catalog_updates) = preliminary_state
764 .to_mut()
765 .apply_updates(op_updates.clone(), &mut local_expr_cache)
766 .await;
767 }
768 updates.append(&mut op_updates);
769 }
770
771 if !updates.is_empty() {
772 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
773 let (op_builtin_table_updates, op_catalog_updates) = state
774 .to_mut()
775 .apply_updates(updates.clone(), &mut local_expr_cache)
776 .await;
777 let op_builtin_table_updates = state
778 .to_mut()
779 .resolve_builtin_table_updates(op_builtin_table_updates);
780 builtin_table_updates.extend(op_builtin_table_updates);
781 parsed_catalog_updates.extend(op_catalog_updates);
782 }
783
784 match mode {
785 TransactInnerMode::Commit => {
786 if let Some(c) = storage_collections {
788 c.prepare_state(
789 tx,
790 storage_collections_to_create,
791 storage_collections_to_drop,
792 storage_collections_to_register,
793 )
794 .await?;
795 }
796 }
797 TransactInnerMode::DryRun => {
798 debug_assert!(
799 storage_collections.is_none(),
800 "dry-run mode must not prepare storage state"
801 );
802 }
803 }
804
805 let updates = tx.get_and_commit_op_updates();
806 if !updates.is_empty() {
807 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
808 let (op_builtin_table_updates, op_catalog_updates) = state
809 .to_mut()
810 .apply_updates(updates.clone(), &mut local_expr_cache)
811 .await;
812 let op_builtin_table_updates = state
813 .to_mut()
814 .resolve_builtin_table_updates(op_builtin_table_updates);
815 builtin_table_updates.extend(op_builtin_table_updates);
816 parsed_catalog_updates.extend(op_catalog_updates);
817 }
818
819 match state {
820 Cow::Owned(state) => Ok(Some(state)),
821 Cow::Borrowed(_) => Ok(None),
822 }
823 }
824
825 #[instrument]
833 async fn transact_op(
834 oracle_write_ts: mz_repr::Timestamp,
835 session: Option<&ConnMeta>,
836 op: Op,
837 temporary_ids: &BTreeSet<CatalogItemId>,
838 audit_events: &mut Vec<VersionedEvent>,
839 tx: &mut Transaction<'_>,
840 state: &CatalogState,
841 storage_collections_to_create: &mut BTreeSet<GlobalId>,
842 storage_collections_to_drop: &mut BTreeSet<GlobalId>,
843 storage_collections_to_register: &mut BTreeMap<GlobalId, ShardId>,
844 ) -> Result<Vec<(TemporaryItem, StateDiff)>, AdapterError> {
845 let mut temporary_item_updates = Vec::new();
846
847 match op {
848 Op::CheckClusterState {
849 cluster_id,
850 expected,
851 } => {
852 if !crate::catalog::cluster_state::cluster_matches_expected(
856 state, cluster_id, &expected,
857 ) {
858 return Err(AdapterError::ClusterStateChanged { cluster_id });
859 }
860 }
861 Op::AlterRetainHistory { id, value, window } => {
862 let entry = state.get_entry(&id);
863 if id.is_system() {
864 let name = entry.name();
865 let full_name =
866 state.resolve_full_name(name, session.map(|session| session.conn_id()));
867 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
868 full_name.to_string(),
869 ))));
870 }
871
872 let mut new_entry = entry.clone();
873 let previous = new_entry
874 .item
875 .update_retain_history(value.clone(), window)
876 .map_err(|_| {
877 AdapterError::Catalog(Error::new(ErrorKind::Internal(
878 "planner should have rejected invalid alter retain history item type"
879 .to_string(),
880 )))
881 })?;
882
883 if Self::should_audit_log_item(new_entry.item()) {
884 let details =
885 EventDetails::AlterRetainHistoryV1(mz_audit_log::AlterRetainHistoryV1 {
886 id: id.to_string(),
887 old_history: previous.map(|previous| previous.to_string()),
888 new_history: value.map(|v| v.to_string()),
889 });
890 CatalogState::add_to_audit_log(
891 &state.system_configuration,
892 oracle_write_ts,
893 session,
894 tx,
895 audit_events,
896 EventType::Alter,
897 catalog_type_to_audit_object_type(new_entry.item().typ()),
898 details,
899 )?;
900 }
901
902 tx.update_item(id, new_entry.into())?;
903
904 Self::log_update(state, &id);
905 }
906 Op::AlterSourceTimestampInterval {
907 id,
908 value,
909 interval,
910 } => {
911 let entry = state.get_entry(&id);
912 if id.is_system() {
913 let name = entry.name();
914 let full_name =
915 state.resolve_full_name(name, session.map(|session| session.conn_id()));
916 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
917 full_name.to_string(),
918 ))));
919 }
920
921 let mut new_entry = entry.clone();
922 let previous = new_entry
923 .item
924 .update_timestamp_interval(value.clone(), interval)
925 .map_err(|_| {
926 AdapterError::Catalog(Error::new(ErrorKind::Internal(
927 "planner should have rejected invalid alter timestamp interval item type"
928 .to_string(),
929 )))
930 })?;
931
932 if Self::should_audit_log_item(new_entry.item()) {
933 let details = EventDetails::AlterSourceTimestampIntervalV1(
934 mz_audit_log::AlterSourceTimestampIntervalV1 {
935 id: id.to_string(),
936 old_interval: previous.map(|previous| previous.to_string()),
937 new_interval: value.map(|v| v.to_string()),
938 },
939 );
940 CatalogState::add_to_audit_log(
941 &state.system_configuration,
942 oracle_write_ts,
943 session,
944 tx,
945 audit_events,
946 EventType::Alter,
947 catalog_type_to_audit_object_type(new_entry.item().typ()),
948 details,
949 )?;
950 }
951
952 tx.update_item(id, new_entry.into())?;
953
954 Self::log_update(state, &id);
955 }
956 Op::AlterRole {
957 id,
958 name,
959 attributes,
960 nopassword,
961 vars,
962 } => {
963 state.ensure_not_reserved_role(&id)?;
964
965 let mut existing_role = state.get_role(&id).clone();
966 let password = attributes.password.clone();
967 let scram_iterations = attributes
968 .scram_iterations
969 .unwrap_or_else(|| state.system_config().scram_iterations());
970 existing_role.attributes = attributes.into();
971 existing_role.vars = vars;
972 let password_action = if nopassword {
973 PasswordAction::Clear
974 } else if let Some(password) = password {
975 PasswordAction::Set(PasswordConfig {
976 password,
977 scram_iterations,
978 })
979 } else {
980 PasswordAction::NoChange
981 };
982 tx.update_role(id, existing_role.into(), password_action)?;
983
984 CatalogState::add_to_audit_log(
985 &state.system_configuration,
986 oracle_write_ts,
987 session,
988 tx,
989 audit_events,
990 EventType::Alter,
991 ObjectType::Role,
992 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
993 id: id.to_string(),
994 name: name.clone(),
995 }),
996 )?;
997
998 info!("update role {name} ({id})");
999 }
1000 Op::AlterNetworkPolicy {
1001 id,
1002 rules,
1003 name,
1004 owner_id: _owner_id,
1005 } => {
1006 let existing_policy = state.get_network_policy(&id).clone();
1007 let mut policy: NetworkPolicy = existing_policy.into();
1008 policy.rules = rules;
1009 if is_reserved_name(&name) {
1010 return Err(AdapterError::Catalog(Error::new(
1011 ErrorKind::ReservedNetworkPolicyName(name),
1012 )));
1013 }
1014 tx.update_network_policy(id, policy.clone())?;
1015
1016 CatalogState::add_to_audit_log(
1017 &state.system_configuration,
1018 oracle_write_ts,
1019 session,
1020 tx,
1021 audit_events,
1022 EventType::Alter,
1023 ObjectType::NetworkPolicy,
1024 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1025 id: id.to_string(),
1026 name: name.clone(),
1027 }),
1028 )?;
1029
1030 info!("update network policy {name} ({id})");
1031 }
1032 Op::AlterAddColumn {
1033 id,
1034 new_global_id,
1035 name,
1036 typ,
1037 sql,
1038 } => {
1039 let column_name = name.to_string();
1040 let column_type = typ.to_string();
1041 let nullable = typ.nullable;
1042 let mut new_entry = state.get_entry(&id).clone();
1043 let version = new_entry.item.add_column(name, typ, sql)?;
1044 let shard_id = state
1047 .storage_metadata()
1048 .get_collection_shard(new_entry.latest_global_id())?;
1049
1050 let CatalogItem::Table(table) = &mut new_entry.item else {
1052 return Err(AdapterError::Unsupported("adding columns to non-Table"));
1053 };
1054 table.collections.insert(version, new_global_id);
1055
1056 if Self::should_audit_log_item(new_entry.item()) {
1057 let details = EventDetails::AlterAddColumnV1(mz_audit_log::AlterAddColumnV1 {
1058 id: id.to_string(),
1059 column: column_name,
1060 column_type,
1061 nullable,
1062 });
1063 CatalogState::add_to_audit_log(
1064 &state.system_configuration,
1065 oracle_write_ts,
1066 session,
1067 tx,
1068 audit_events,
1069 EventType::Alter,
1070 catalog_type_to_audit_object_type(new_entry.item().typ()),
1071 details,
1072 )?;
1073 }
1074
1075 tx.update_item(id, new_entry.into())?;
1076 storage_collections_to_register.insert(new_global_id, shard_id);
1077 }
1078 Op::AlterMaterializedViewApplyReplacement { id, replacement_id } => {
1079 let mut new_entry = state.get_entry(&id).clone();
1080 let replacement = state.get_entry(&replacement_id);
1081
1082 let new_audit_events =
1083 apply_replacement_audit_events(state, &new_entry, replacement);
1084
1085 let CatalogItem::MaterializedView(mv) = &mut new_entry.item else {
1086 return Err(AdapterError::internal(
1087 "ALTER MATERIALIZED VIEW ... APPLY REPLACEMENT",
1088 "id must refer to a materialized view",
1089 ));
1090 };
1091 let CatalogItem::MaterializedView(replacement_mv) = &replacement.item else {
1092 return Err(AdapterError::internal(
1093 "ALTER MATERIALIZED VIEW ... APPLY REPLACEMENT",
1094 "replacement_id must refer to a materialized view",
1095 ));
1096 };
1097
1098 mv.apply_replacement(replacement_mv.clone());
1099
1100 tx.remove_item(replacement_id)?;
1101
1102 new_entry.id = replacement_id;
1103 tx_replace_item(tx, state, id, new_entry)?;
1104
1105 let comment_id = CommentObjectId::MaterializedView(replacement_id);
1106 tx.drop_comments(&[comment_id].into())?;
1107
1108 for (event_type, details) in new_audit_events {
1109 CatalogState::add_to_audit_log(
1110 &state.system_configuration,
1111 oracle_write_ts,
1112 session,
1113 tx,
1114 audit_events,
1115 event_type,
1116 ObjectType::MaterializedView,
1117 details,
1118 )?;
1119 }
1120 }
1121 Op::CreateDatabase { name, owner_id } => {
1122 let database_owner_privileges = vec![rbac::owner_privilege(
1123 mz_sql::catalog::ObjectType::Database,
1124 owner_id,
1125 )];
1126 let database_default_privileges = state
1127 .default_privileges
1128 .get_applicable_privileges(
1129 owner_id,
1130 None,
1131 None,
1132 mz_sql::catalog::ObjectType::Database,
1133 )
1134 .map(|item| item.mz_acl_item(owner_id));
1135 let database_privileges: Vec<_> = merge_mz_acl_items(
1136 database_owner_privileges
1137 .into_iter()
1138 .chain(database_default_privileges),
1139 )
1140 .collect();
1141
1142 let schema_owner_privileges = vec![rbac::owner_privilege(
1143 mz_sql::catalog::ObjectType::Schema,
1144 owner_id,
1145 )];
1146 let schema_default_privileges = state
1147 .default_privileges
1148 .get_applicable_privileges(
1149 owner_id,
1150 None,
1151 None,
1152 mz_sql::catalog::ObjectType::Schema,
1153 )
1154 .map(|item| item.mz_acl_item(owner_id))
1155 .chain(std::iter::once(MzAclItem {
1157 grantee: RoleId::Public,
1158 grantor: owner_id,
1159 acl_mode: AclMode::USAGE,
1160 }));
1161 let schema_privileges: Vec<_> = merge_mz_acl_items(
1162 schema_owner_privileges
1163 .into_iter()
1164 .chain(schema_default_privileges),
1165 )
1166 .collect();
1167
1168 let temporary_oids: HashSet<_> = state.get_temporary_oids().collect();
1169 let (database_id, _) = tx.insert_user_database(
1170 &name,
1171 owner_id,
1172 database_privileges.clone(),
1173 &temporary_oids,
1174 )?;
1175 let (schema_id, _) = tx.insert_user_schema(
1176 database_id,
1177 DEFAULT_SCHEMA,
1178 owner_id,
1179 schema_privileges.clone(),
1180 &temporary_oids,
1181 )?;
1182 CatalogState::add_to_audit_log(
1183 &state.system_configuration,
1184 oracle_write_ts,
1185 session,
1186 tx,
1187 audit_events,
1188 EventType::Create,
1189 ObjectType::Database,
1190 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1191 id: database_id.to_string(),
1192 name: name.clone(),
1193 }),
1194 )?;
1195 info!("create database {}", name);
1196
1197 CatalogState::add_to_audit_log(
1198 &state.system_configuration,
1199 oracle_write_ts,
1200 session,
1201 tx,
1202 audit_events,
1203 EventType::Create,
1204 ObjectType::Schema,
1205 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
1206 id: schema_id.to_string(),
1207 name: DEFAULT_SCHEMA.to_string(),
1208 database_name: Some(name),
1209 }),
1210 )?;
1211 }
1212 Op::CreateSchema {
1213 database_id,
1214 schema_name,
1215 owner_id,
1216 } => {
1217 if is_reserved_name(&schema_name) {
1218 return Err(AdapterError::Catalog(Error::new(
1219 ErrorKind::ReservedSchemaName(schema_name),
1220 )));
1221 }
1222 let database_id = match database_id {
1223 ResolvedDatabaseSpecifier::Id(id) => id,
1224 ResolvedDatabaseSpecifier::Ambient => {
1225 return Err(AdapterError::Catalog(Error::new(
1226 ErrorKind::ReadOnlySystemSchema(schema_name),
1227 )));
1228 }
1229 };
1230 let owner_privileges = vec![rbac::owner_privilege(
1231 mz_sql::catalog::ObjectType::Schema,
1232 owner_id,
1233 )];
1234 let default_privileges = state
1235 .default_privileges
1236 .get_applicable_privileges(
1237 owner_id,
1238 Some(database_id),
1239 None,
1240 mz_sql::catalog::ObjectType::Schema,
1241 )
1242 .map(|item| item.mz_acl_item(owner_id));
1243 let privileges: Vec<_> =
1244 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1245 .collect();
1246 let (schema_id, _) = tx.insert_user_schema(
1247 database_id,
1248 &schema_name,
1249 owner_id,
1250 privileges.clone(),
1251 &state.get_temporary_oids().collect(),
1252 )?;
1253 CatalogState::add_to_audit_log(
1254 &state.system_configuration,
1255 oracle_write_ts,
1256 session,
1257 tx,
1258 audit_events,
1259 EventType::Create,
1260 ObjectType::Schema,
1261 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
1262 id: schema_id.to_string(),
1263 name: schema_name.clone(),
1264 database_name: Some(state.database_by_id[&database_id].name.clone()),
1265 }),
1266 )?;
1267 }
1268 Op::CreateRole { name, attributes } => {
1269 if is_reserved_role_name(&name) {
1270 return Err(AdapterError::Catalog(Error::new(
1271 ErrorKind::ReservedRoleName(name),
1272 )));
1273 }
1274 let membership = RoleMembership::new();
1275 let vars = RoleVars::default();
1276 let (id, _) = tx.insert_user_role(
1277 name.clone(),
1278 attributes.clone(),
1279 membership.clone(),
1280 vars.clone(),
1281 &state.get_temporary_oids().collect(),
1282 )?;
1283 CatalogState::add_to_audit_log(
1284 &state.system_configuration,
1285 oracle_write_ts,
1286 session,
1287 tx,
1288 audit_events,
1289 EventType::Create,
1290 ObjectType::Role,
1291 EventDetails::CreateRoleV1(mz_audit_log::CreateRoleV1 {
1292 id: id.to_string(),
1293 name: name.clone(),
1294 auto_provision_source: attributes.auto_provision_source.map(|s| match s {
1295 AutoProvisionSource::Oidc => "oidc".to_string(),
1296 AutoProvisionSource::Frontegg => "frontegg".to_string(),
1297 AutoProvisionSource::None => "none".to_string(),
1298 }),
1299 }),
1300 )?;
1301 info!("create role {}", name);
1302 }
1303 Op::CreateCluster {
1304 id,
1305 name,
1306 introspection_sources,
1307 owner_id,
1308 config,
1309 } => {
1310 if is_reserved_name(&name) {
1311 return Err(AdapterError::Catalog(Error::new(
1312 ErrorKind::ReservedClusterName(name),
1313 )));
1314 }
1315 let owner_privileges = vec![rbac::owner_privilege(
1316 mz_sql::catalog::ObjectType::Cluster,
1317 owner_id,
1318 )];
1319 let default_privileges = state
1320 .default_privileges
1321 .get_applicable_privileges(
1322 owner_id,
1323 None,
1324 None,
1325 mz_sql::catalog::ObjectType::Cluster,
1326 )
1327 .map(|item| item.mz_acl_item(owner_id));
1328 let privileges: Vec<_> =
1329 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1330 .collect();
1331 let introspection_source_ids: Vec<_> = introspection_sources
1332 .iter()
1333 .map(|introspection_source| {
1334 Transaction::allocate_introspection_source_index_id(
1335 &id,
1336 introspection_source.variant,
1337 )
1338 })
1339 .collect();
1340
1341 let introspection_sources = introspection_sources
1342 .into_iter()
1343 .zip_eq(introspection_source_ids)
1344 .map(|(log, (item_id, gid))| (log, item_id, gid))
1345 .collect();
1346
1347 tx.insert_user_cluster(
1348 id,
1349 &name,
1350 introspection_sources,
1351 owner_id,
1352 privileges.clone(),
1353 config.clone().into(),
1354 &state.get_temporary_oids().collect(),
1355 )?;
1356 CatalogState::add_to_audit_log(
1357 &state.system_configuration,
1358 oracle_write_ts,
1359 session,
1360 tx,
1361 audit_events,
1362 EventType::Create,
1363 ObjectType::Cluster,
1364 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1365 id: id.to_string(),
1366 name: name.clone(),
1367 }),
1368 )?;
1369 info!("create cluster {}", name);
1370 }
1371 Op::CreateClusterReplica {
1372 cluster_id,
1373 replica_id,
1374 name,
1375 config,
1376 owner_id,
1377 reason,
1378 } => {
1379 if is_reserved_name(&name) {
1380 return Err(AdapterError::Catalog(Error::new(
1381 ErrorKind::ReservedReplicaName(name),
1382 )));
1383 }
1384 let cluster = state.get_cluster(cluster_id);
1385 tx.insert_cluster_replica_with_id(
1389 cluster_id,
1390 replica_id,
1391 &name,
1392 config.clone().into(),
1393 owner_id,
1394 )?;
1395 if let ReplicaLocation::Managed(ManagedReplicaLocation {
1396 size,
1397 billed_as,
1398 internal,
1399 ..
1400 }) = &config.location
1401 {
1402 let (reason, scheduling_policies) = reason.into_audit_log();
1403 let details = EventDetails::CreateClusterReplicaV4(
1404 mz_audit_log::CreateClusterReplicaV4 {
1405 cluster_id: cluster_id.to_string(),
1406 cluster_name: cluster.name.clone(),
1407 replica_id: Some(replica_id.to_string()),
1408 replica_name: name.clone(),
1409 logical_size: size.clone(),
1410 billed_as: billed_as.clone(),
1411 internal: *internal,
1412 reason,
1413 scheduling_policies,
1414 },
1415 );
1416 CatalogState::add_to_audit_log(
1417 &state.system_configuration,
1418 oracle_write_ts,
1419 session,
1420 tx,
1421 audit_events,
1422 EventType::Create,
1423 ObjectType::ClusterReplica,
1424 details,
1425 )?;
1426 }
1427 }
1428 Op::CreateItem {
1429 id,
1430 name,
1431 item,
1432 owner_id,
1433 } => {
1434 state.check_unstable_dependencies(&item)?;
1435
1436 match &item {
1437 CatalogItem::Table(table) => {
1438 let gids: Vec<_> = table.global_ids().collect();
1439 assert_eq!(gids.len(), 1);
1440 storage_collections_to_create.extend(gids);
1441 }
1442 CatalogItem::Source(source) => {
1443 storage_collections_to_create.insert(source.global_id());
1444 }
1445 CatalogItem::MaterializedView(mv) => {
1446 let mv_gid = mv.global_id_writes();
1447 if let Some(target_id) = mv.replacement_target {
1448 let target_gid = state.get_entry(&target_id).latest_global_id();
1449 let shard_id =
1450 state.storage_metadata().get_collection_shard(target_gid)?;
1451 storage_collections_to_register.insert(mv_gid, shard_id);
1452 } else {
1453 storage_collections_to_create.insert(mv_gid);
1454 }
1455 }
1456 CatalogItem::Sink(sink) => {
1457 storage_collections_to_create.insert(sink.global_id());
1458 }
1459 CatalogItem::Log(_)
1460 | CatalogItem::View(_)
1461 | CatalogItem::Index(_)
1462 | CatalogItem::Type(_)
1463 | CatalogItem::Func(_)
1464 | CatalogItem::Secret(_)
1465 | CatalogItem::Connection(_) => (),
1466 }
1467
1468 let system_user = session.map_or(false, |s| s.user().is_system_user());
1469 if !system_user {
1470 if let Some(id @ ClusterId::System(_)) = item.cluster_id() {
1471 let cluster_name = state.clusters_by_id[&id].name.clone();
1472 return Err(AdapterError::Catalog(Error::new(
1473 ErrorKind::ReadOnlyCluster(cluster_name),
1474 )));
1475 }
1476 }
1477
1478 let owner_privileges = vec![rbac::owner_privilege(item.typ().into(), owner_id)];
1479 let default_privileges = state
1480 .default_privileges
1481 .get_applicable_privileges(
1482 owner_id,
1483 name.qualifiers.database_spec.id(),
1484 Some(name.qualifiers.schema_spec.into()),
1485 item.typ().into(),
1486 )
1487 .map(|item| item.mz_acl_item(owner_id));
1488 let progress_source_privilege = if item.is_progress_source() {
1490 Some(MzAclItem {
1491 grantee: MZ_SUPPORT_ROLE_ID,
1492 grantor: owner_id,
1493 acl_mode: AclMode::SELECT,
1494 })
1495 } else {
1496 None
1497 };
1498 let privileges: Vec<_> = merge_mz_acl_items(
1499 owner_privileges
1500 .into_iter()
1501 .chain(default_privileges)
1502 .chain(progress_source_privilege),
1503 )
1504 .collect();
1505
1506 let temporary_oids = state.get_temporary_oids().collect();
1507
1508 if item.is_temporary() {
1509 if name.qualifiers.database_spec != ResolvedDatabaseSpecifier::Ambient
1510 || name.qualifiers.schema_spec != SchemaSpecifier::Temporary
1511 {
1512 return Err(AdapterError::Catalog(Error::new(
1513 ErrorKind::InvalidTemporarySchema,
1514 )));
1515 }
1516 let oid = tx.allocate_oid(&temporary_oids)?;
1517
1518 let schema_id = name.qualifiers.schema_spec.clone().into();
1519 let item_type = item.typ();
1520 let (create_sql, global_id, versions) = item.to_serialized();
1521
1522 let item = TemporaryItem {
1523 id,
1524 oid,
1525 global_id,
1526 schema_id,
1527 name: name.item.clone(),
1528 create_sql,
1529 conn_id: item.conn_id().cloned(),
1530 owner_id,
1531 privileges: privileges.clone(),
1532 extra_versions: versions,
1533 };
1534 temporary_item_updates.push((item, StateDiff::Addition));
1535
1536 info!(
1537 "create temporary {} {} ({})",
1538 item_type,
1539 state.resolve_full_name(&name, None),
1540 id
1541 );
1542 } else {
1543 if let Some(temp_id) =
1544 item.uses()
1545 .iter()
1546 .find(|id| match state.try_get_entry(*id) {
1547 Some(entry) => entry.item().is_temporary(),
1548 None => temporary_ids.contains(id),
1549 })
1550 {
1551 let temp_item = state.get_entry(temp_id);
1552 return Err(AdapterError::Catalog(Error::new(
1553 ErrorKind::InvalidTemporaryDependency(temp_item.name().item.clone()),
1554 )));
1555 }
1556 if name.qualifiers.database_spec == ResolvedDatabaseSpecifier::Ambient
1557 && !system_user
1558 {
1559 let schema_name = state
1560 .resolve_full_name(&name, session.map(|session| session.conn_id()))
1561 .schema;
1562 return Err(AdapterError::Catalog(Error::new(
1563 ErrorKind::ReadOnlySystemSchema(schema_name),
1564 )));
1565 }
1566 let schema_id = name.qualifiers.schema_spec.clone().into();
1567 let item_type = item.typ();
1568 let (create_sql, global_id, versions) = item.to_serialized();
1569 tx.insert_user_item(
1570 id,
1571 global_id,
1572 schema_id,
1573 &name.item,
1574 create_sql,
1575 owner_id,
1576 privileges.clone(),
1577 &temporary_oids,
1578 versions,
1579 )?;
1580 info!(
1581 "create {} {} ({})",
1582 item_type,
1583 state.resolve_full_name(&name, None),
1584 id
1585 );
1586 }
1587
1588 if Self::should_audit_log_item(&item) {
1589 let name = Self::full_name_detail(
1590 &state.resolve_full_name(&name, session.map(|session| session.conn_id())),
1591 );
1592 let details = match &item {
1593 CatalogItem::Source(s) => {
1594 let cluster_id = match s.data_source {
1595 DataSourceDesc::IngestionExport { ingestion_id, .. } => {
1598 match state.get_entry(&ingestion_id).cluster_id() {
1599 Some(cluster_id) => Some(cluster_id.to_string()),
1600 None => None,
1601 }
1602 }
1603 _ => match item.cluster_id() {
1604 Some(cluster_id) => Some(cluster_id.to_string()),
1605 None => None,
1606 },
1607 };
1608
1609 EventDetails::CreateSourceSinkV4(mz_audit_log::CreateSourceSinkV4 {
1610 id: id.to_string(),
1611 cluster_id,
1612 name,
1613 external_type: s.source_type().to_string(),
1614 })
1615 }
1616 CatalogItem::Sink(s) => {
1617 EventDetails::CreateSourceSinkV4(mz_audit_log::CreateSourceSinkV4 {
1618 id: id.to_string(),
1619 cluster_id: Some(s.cluster_id.to_string()),
1620 name,
1621 external_type: s.sink_type().to_string(),
1622 })
1623 }
1624 CatalogItem::Index(i) => {
1625 EventDetails::CreateIndexV1(mz_audit_log::CreateIndexV1 {
1626 id: id.to_string(),
1627 name,
1628 cluster_id: i.cluster_id.to_string(),
1629 })
1630 }
1631 CatalogItem::MaterializedView(mv) => {
1632 EventDetails::CreateMaterializedViewV1(
1633 mz_audit_log::CreateMaterializedViewV1 {
1634 id: id.to_string(),
1635 name,
1636 cluster_id: mv.cluster_id.to_string(),
1637 replacement_target_id: mv
1638 .replacement_target
1639 .map(|id| id.to_string()),
1640 },
1641 )
1642 }
1643 CatalogItem::Table(_)
1644 | CatalogItem::Log(_)
1645 | CatalogItem::View(_)
1646 | CatalogItem::Type(_)
1647 | CatalogItem::Func(_)
1648 | CatalogItem::Secret(_)
1649 | CatalogItem::Connection(_) => EventDetails::IdFullNameV1(IdFullNameV1 {
1650 id: id.to_string(),
1651 name,
1652 }),
1653 };
1654 CatalogState::add_to_audit_log(
1655 &state.system_configuration,
1656 oracle_write_ts,
1657 session,
1658 tx,
1659 audit_events,
1660 EventType::Create,
1661 catalog_type_to_audit_object_type(item.typ()),
1662 details,
1663 )?;
1664 }
1665 }
1666 Op::CreateNetworkPolicy {
1667 rules,
1668 name,
1669 owner_id,
1670 } => {
1671 if state.network_policies_by_name.contains_key(&name) {
1672 return Err(AdapterError::PlanError(PlanError::Catalog(
1673 SqlCatalogError::NetworkPolicyAlreadyExists(name),
1674 )));
1675 }
1676 if is_reserved_name(&name) {
1677 return Err(AdapterError::Catalog(Error::new(
1678 ErrorKind::ReservedNetworkPolicyName(name),
1679 )));
1680 }
1681
1682 let owner_privileges = vec![rbac::owner_privilege(
1683 mz_sql::catalog::ObjectType::NetworkPolicy,
1684 owner_id,
1685 )];
1686 let default_privileges = state
1687 .default_privileges
1688 .get_applicable_privileges(
1689 owner_id,
1690 None,
1691 None,
1692 mz_sql::catalog::ObjectType::NetworkPolicy,
1693 )
1694 .map(|item| item.mz_acl_item(owner_id));
1695 let privileges: Vec<_> =
1696 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1697 .collect();
1698
1699 let temporary_oids: HashSet<_> = state.get_temporary_oids().collect();
1700 let id = tx.insert_user_network_policy(
1701 name.clone(),
1702 rules,
1703 privileges,
1704 owner_id,
1705 &temporary_oids,
1706 )?;
1707
1708 CatalogState::add_to_audit_log(
1709 &state.system_configuration,
1710 oracle_write_ts,
1711 session,
1712 tx,
1713 audit_events,
1714 EventType::Create,
1715 ObjectType::NetworkPolicy,
1716 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1717 id: id.to_string(),
1718 name: name.clone(),
1719 }),
1720 )?;
1721
1722 info!("created network policy {name} ({id})");
1723 }
1724 Op::Comment {
1725 object_id,
1726 sub_component,
1727 comment,
1728 } => {
1729 tx.update_comment(object_id, sub_component, comment)?;
1730 let entry = state.get_comment_id_entry(&object_id);
1731 let should_log = entry
1732 .map(|entry| Self::should_audit_log_item(entry.item()))
1733 .unwrap_or(true);
1735 if let (Some(conn_id), true) =
1738 (session.map(|session| session.conn_id()), should_log)
1739 {
1740 CatalogState::add_to_audit_log(
1741 &state.system_configuration,
1742 oracle_write_ts,
1743 session,
1744 tx,
1745 audit_events,
1746 EventType::Comment,
1747 comment_id_to_audit_object_type(object_id),
1748 EventDetails::IdNameV1(IdNameV1 {
1749 id: format!("{object_id:?}"),
1751 name: state.comment_id_to_audit_log_name(object_id, conn_id),
1752 }),
1753 )?;
1754 }
1755 }
1756 Op::UpdateSourceReferences {
1757 source_id,
1758 references,
1759 } => {
1760 tx.update_source_references(
1761 source_id,
1762 references
1763 .references
1764 .into_iter()
1765 .map(|reference| reference.into())
1766 .collect(),
1767 references.updated_at,
1768 )?;
1769 }
1770 Op::DropObjects(drop_object_infos) => {
1771 let delta = ObjectsToDrop::generate(drop_object_infos, state, session)?;
1773
1774 tx.drop_comments(&delta.comments)?;
1776
1777 let (durable_items_to_drop, temporary_items_to_drop): (BTreeSet<_>, BTreeSet<_>) =
1779 delta
1780 .items
1781 .iter()
1782 .map(|id| id)
1783 .partition(|id| !state.get_entry(*id).item().is_temporary());
1784 tx.remove_items(&durable_items_to_drop)?;
1785 temporary_item_updates.extend(temporary_items_to_drop.into_iter().map(|id| {
1786 let entry = state.get_entry(&id);
1787 (entry.clone().into(), StateDiff::Retraction)
1788 }));
1789
1790 for item_id in delta.items {
1791 let entry = state.get_entry(&item_id);
1792
1793 if entry.item().is_storage_collection() {
1794 storage_collections_to_drop.extend(entry.global_ids());
1795 }
1796
1797 if state.source_references.contains_key(&item_id) {
1798 tx.remove_source_references(item_id)?;
1799 }
1800
1801 if Self::should_audit_log_item(entry.item()) {
1802 CatalogState::add_to_audit_log(
1803 &state.system_configuration,
1804 oracle_write_ts,
1805 session,
1806 tx,
1807 audit_events,
1808 EventType::Drop,
1809 catalog_type_to_audit_object_type(entry.item().typ()),
1810 EventDetails::IdFullNameV1(IdFullNameV1 {
1811 id: item_id.to_string(),
1812 name: Self::full_name_detail(&state.resolve_full_name(
1813 entry.name(),
1814 session.map(|session| session.conn_id()),
1815 )),
1816 }),
1817 )?;
1818 }
1819 info!(
1820 "drop {} {} ({})",
1821 entry.item_type(),
1822 state.resolve_full_name(entry.name(), entry.conn_id()),
1823 item_id
1824 );
1825 }
1826
1827 let schemas = delta
1829 .schemas
1830 .iter()
1831 .map(|(schema_spec, database_spec)| {
1832 (SchemaId::from(schema_spec), *database_spec)
1833 })
1834 .collect();
1835 tx.remove_schemas(&schemas)?;
1836
1837 for (schema_spec, database_spec) in delta.schemas {
1838 let schema = state.get_schema(
1839 &database_spec,
1840 &schema_spec,
1841 session
1842 .map(|session| session.conn_id())
1843 .unwrap_or(&SYSTEM_CONN_ID),
1844 );
1845
1846 let schema_id = SchemaId::from(schema_spec);
1847 let database_id = match database_spec {
1848 ResolvedDatabaseSpecifier::Ambient => None,
1849 ResolvedDatabaseSpecifier::Id(database_id) => Some(database_id),
1850 };
1851
1852 CatalogState::add_to_audit_log(
1853 &state.system_configuration,
1854 oracle_write_ts,
1855 session,
1856 tx,
1857 audit_events,
1858 EventType::Drop,
1859 ObjectType::Schema,
1860 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
1861 id: schema_id.to_string(),
1862 name: schema.name.schema.to_string(),
1863 database_name: database_id
1864 .map(|database_id| state.database_by_id[&database_id].name.clone()),
1865 }),
1866 )?;
1867 }
1868
1869 tx.remove_databases(&delta.databases)?;
1871
1872 for database_id in delta.databases {
1873 let database = state.get_database(&database_id).clone();
1874
1875 CatalogState::add_to_audit_log(
1876 &state.system_configuration,
1877 oracle_write_ts,
1878 session,
1879 tx,
1880 audit_events,
1881 EventType::Drop,
1882 ObjectType::Database,
1883 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1884 id: database_id.to_string(),
1885 name: database.name.clone(),
1886 }),
1887 )?;
1888 }
1889
1890 tx.remove_user_roles(&delta.roles)?;
1892
1893 for role_id in delta.roles {
1894 let role = state
1895 .roles_by_id
1896 .get(&role_id)
1897 .expect("catalog out of sync");
1898
1899 CatalogState::add_to_audit_log(
1900 &state.system_configuration,
1901 oracle_write_ts,
1902 session,
1903 tx,
1904 audit_events,
1905 EventType::Drop,
1906 ObjectType::Role,
1907 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1908 id: role.id.to_string(),
1909 name: role.name.clone(),
1910 }),
1911 )?;
1912 info!("drop role {}", role.name());
1913 }
1914
1915 tx.remove_network_policies(&delta.network_policies)?;
1917
1918 for network_policy_id in delta.network_policies {
1919 let policy = state
1920 .network_policies_by_id
1921 .get(&network_policy_id)
1922 .expect("catalog out of sync");
1923
1924 CatalogState::add_to_audit_log(
1925 &state.system_configuration,
1926 oracle_write_ts,
1927 session,
1928 tx,
1929 audit_events,
1930 EventType::Drop,
1931 ObjectType::NetworkPolicy,
1932 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1933 id: policy.id.to_string(),
1934 name: policy.name.clone(),
1935 }),
1936 )?;
1937 info!("drop network policy {}", policy.name.clone());
1938 }
1939
1940 let replicas = delta.replicas.keys().copied().collect();
1942 tx.remove_cluster_replicas(&replicas)?;
1943
1944 for (replica_id, (cluster_id, reason)) in delta.replicas {
1945 let cluster = state.get_cluster(cluster_id);
1946 let replica = cluster.replica(replica_id).expect("Must exist");
1947
1948 let (reason, scheduling_policies) = reason.into_audit_log();
1949 let details =
1950 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1951 cluster_id: cluster_id.to_string(),
1952 cluster_name: cluster.name.clone(),
1953 replica_id: Some(replica_id.to_string()),
1954 replica_name: replica.name.clone(),
1955 reason,
1956 scheduling_policies,
1957 });
1958 CatalogState::add_to_audit_log(
1959 &state.system_configuration,
1960 oracle_write_ts,
1961 session,
1962 tx,
1963 audit_events,
1964 EventType::Drop,
1965 ObjectType::ClusterReplica,
1966 details,
1967 )?;
1968 }
1969
1970 tx.remove_clusters(&delta.clusters)?;
1972
1973 for cluster_id in delta.clusters {
1974 let cluster = state.get_cluster(cluster_id);
1975
1976 CatalogState::add_to_audit_log(
1977 &state.system_configuration,
1978 oracle_write_ts,
1979 session,
1980 tx,
1981 audit_events,
1982 EventType::Drop,
1983 ObjectType::Cluster,
1984 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1985 id: cluster.id.to_string(),
1986 name: cluster.name.clone(),
1987 }),
1988 )?;
1989 }
1990 }
1991 Op::GrantRole {
1992 role_id,
1993 member_id,
1994 grantor_id,
1995 } => {
1996 state.ensure_not_reserved_role(&member_id)?;
1997 state.ensure_grantable_role(&role_id)?;
1998 if state.collect_role_membership(&role_id).contains(&member_id) {
1999 let group_role = state.get_role(&role_id);
2000 let member_role = state.get_role(&member_id);
2001 return Err(AdapterError::Catalog(Error::new(
2002 ErrorKind::CircularRoleMembership {
2003 role_name: group_role.name().to_string(),
2004 member_name: member_role.name().to_string(),
2005 },
2006 )));
2007 }
2008 let mut member_role = state.get_role(&member_id).clone();
2009 member_role.membership.map.insert(role_id, grantor_id);
2010 tx.update_role(member_id, member_role.into(), PasswordAction::NoChange)?;
2011
2012 CatalogState::add_to_audit_log(
2013 &state.system_configuration,
2014 oracle_write_ts,
2015 session,
2016 tx,
2017 audit_events,
2018 EventType::Grant,
2019 ObjectType::Role,
2020 EventDetails::GrantRoleV2(mz_audit_log::GrantRoleV2 {
2021 role_id: role_id.to_string(),
2022 member_id: member_id.to_string(),
2023 grantor_id: grantor_id.to_string(),
2024 executed_by: session
2025 .map(|session| session.authenticated_role_id())
2026 .unwrap_or(&MZ_SYSTEM_ROLE_ID)
2027 .to_string(),
2028 }),
2029 )?;
2030 }
2031 Op::RevokeRole {
2032 role_id,
2033 member_id,
2034 grantor_id,
2035 } => {
2036 state.ensure_not_reserved_role(&member_id)?;
2037 state.ensure_grantable_role(&role_id)?;
2038 let mut member_role = state.get_role(&member_id).clone();
2039 member_role.membership.map.remove(&role_id);
2040 tx.update_role(member_id, member_role.into(), PasswordAction::NoChange)?;
2041
2042 CatalogState::add_to_audit_log(
2043 &state.system_configuration,
2044 oracle_write_ts,
2045 session,
2046 tx,
2047 audit_events,
2048 EventType::Revoke,
2049 ObjectType::Role,
2050 EventDetails::RevokeRoleV2(mz_audit_log::RevokeRoleV2 {
2051 role_id: role_id.to_string(),
2052 member_id: member_id.to_string(),
2053 grantor_id: grantor_id.to_string(),
2054 executed_by: session
2055 .map(|session| session.authenticated_role_id())
2056 .unwrap_or(&MZ_SYSTEM_ROLE_ID)
2057 .to_string(),
2058 }),
2059 )?;
2060 }
2061 Op::UpdatePrivilege {
2062 target_id,
2063 privileges,
2064 variant,
2065 } => {
2066 let update_privilege_fn = |target_privileges: &mut PrivilegeMap| {
2067 for privilege in &privileges {
2068 match variant {
2069 UpdatePrivilegeVariant::Grant => {
2070 target_privileges.grant(privilege.clone());
2071 }
2072 UpdatePrivilegeVariant::Revoke => {
2073 target_privileges.revoke(privilege);
2074 }
2075 }
2076 }
2077 };
2078 match &target_id {
2079 SystemObjectId::Object(object_id) => match object_id {
2080 ObjectId::Cluster(id) => {
2081 let mut cluster = state.get_cluster(*id).clone();
2082 update_privilege_fn(&mut cluster.privileges);
2083 tx.update_cluster(*id, cluster.into())?;
2084 }
2085 ObjectId::Database(id) => {
2086 let mut database = state.get_database(id).clone();
2087 update_privilege_fn(&mut database.privileges);
2088 tx.update_database(*id, database.into())?;
2089 }
2090 ObjectId::NetworkPolicy(id) => {
2091 let mut policy = state.get_network_policy(id).clone();
2092 update_privilege_fn(&mut policy.privileges);
2093 tx.update_network_policy(*id, policy.into())?;
2094 }
2095 ObjectId::Schema((database_spec, schema_spec)) => {
2096 let schema_id = schema_spec.clone().into();
2097 let mut schema = state
2098 .get_schema(
2099 database_spec,
2100 schema_spec,
2101 session
2102 .map(|session| session.conn_id())
2103 .unwrap_or(&SYSTEM_CONN_ID),
2104 )
2105 .clone();
2106 update_privilege_fn(&mut schema.privileges);
2107 tx.update_schema(schema_id, schema.into())?;
2108 }
2109 ObjectId::Item(id) => {
2110 let entry = state.get_entry(id);
2111 let mut new_entry = entry.clone();
2112 update_privilege_fn(&mut new_entry.privileges);
2113 if !new_entry.item().is_temporary() {
2114 tx.update_item(*id, new_entry.into())?;
2115 } else {
2116 temporary_item_updates
2117 .push((entry.clone().into(), StateDiff::Retraction));
2118 temporary_item_updates
2119 .push((new_entry.into(), StateDiff::Addition));
2120 }
2121 }
2122 ObjectId::Role(_) | ObjectId::ClusterReplica(_) => {}
2123 },
2124 SystemObjectId::System => {
2125 let mut system_privileges = PrivilegeMap::clone(&state.system_privileges);
2126 update_privilege_fn(&mut system_privileges);
2127 for privilege in &privileges {
2128 let new_privilege = system_privileges
2129 .get_acl_item(&privilege.grantee, &privilege.grantor);
2130 tx.set_system_privilege(
2131 privilege.grantee,
2132 privilege.grantor,
2133 new_privilege.map(|new_privilege| new_privilege.acl_mode),
2134 )?;
2135 }
2136 }
2137 }
2138 let object_type = state.get_system_object_type(&target_id);
2139 let object_id_str = match &target_id {
2140 SystemObjectId::System => "SYSTEM".to_string(),
2141 SystemObjectId::Object(id) => id.to_string(),
2142 };
2143 for privilege in &privileges {
2145 CatalogState::add_to_audit_log(
2146 &state.system_configuration,
2147 oracle_write_ts,
2148 session,
2149 tx,
2150 audit_events,
2151 variant.into(),
2152 system_object_type_to_audit_object_type(&object_type),
2153 EventDetails::UpdatePrivilegeV1(mz_audit_log::UpdatePrivilegeV1 {
2154 object_id: object_id_str.clone(),
2155 grantee_id: privilege.grantee.to_string(),
2156 grantor_id: privilege.grantor.to_string(),
2157 privileges: privilege.acl_mode.to_string(),
2158 }),
2159 )?;
2160 }
2161 }
2162 Op::UpdateDefaultPrivilege {
2163 privilege_object,
2164 privilege_acl_item,
2165 variant,
2166 } => {
2167 let mut default_privileges = DefaultPrivileges::clone(&state.default_privileges);
2168 match variant {
2169 UpdatePrivilegeVariant::Grant => default_privileges
2170 .grant(privilege_object.clone(), privilege_acl_item.clone()),
2171 UpdatePrivilegeVariant::Revoke => {
2172 default_privileges.revoke(&privilege_object, &privilege_acl_item)
2173 }
2174 }
2175 let new_acl_mode = default_privileges
2176 .get_privileges_for_grantee(&privilege_object, &privilege_acl_item.grantee);
2177 tx.set_default_privilege(
2178 privilege_object.role_id,
2179 privilege_object.database_id,
2180 privilege_object.schema_id,
2181 privilege_object.object_type,
2182 privilege_acl_item.grantee,
2183 new_acl_mode.cloned(),
2184 )?;
2185 CatalogState::add_to_audit_log(
2186 &state.system_configuration,
2187 oracle_write_ts,
2188 session,
2189 tx,
2190 audit_events,
2191 variant.into(),
2192 object_type_to_audit_object_type(privilege_object.object_type),
2193 EventDetails::AlterDefaultPrivilegeV1(mz_audit_log::AlterDefaultPrivilegeV1 {
2194 role_id: privilege_object.role_id.to_string(),
2195 database_id: privilege_object.database_id.map(|id| id.to_string()),
2196 schema_id: privilege_object.schema_id.map(|id| id.to_string()),
2197 grantee_id: privilege_acl_item.grantee.to_string(),
2198 privileges: privilege_acl_item.acl_mode.to_string(),
2199 }),
2200 )?;
2201 }
2202 Op::RenameCluster {
2203 id,
2204 name,
2205 to_name,
2206 check_reserved_names,
2207 } => {
2208 if id.is_system() {
2209 return Err(AdapterError::Catalog(Error::new(
2210 ErrorKind::ReadOnlyCluster(name.clone()),
2211 )));
2212 }
2213 if check_reserved_names && is_reserved_name(&to_name) {
2214 return Err(AdapterError::Catalog(Error::new(
2215 ErrorKind::ReservedClusterName(to_name),
2216 )));
2217 }
2218 tx.rename_cluster(id, &name, &to_name)?;
2219 CatalogState::add_to_audit_log(
2220 &state.system_configuration,
2221 oracle_write_ts,
2222 session,
2223 tx,
2224 audit_events,
2225 EventType::Alter,
2226 ObjectType::Cluster,
2227 EventDetails::RenameClusterV1(mz_audit_log::RenameClusterV1 {
2228 id: id.to_string(),
2229 old_name: name.clone(),
2230 new_name: to_name.clone(),
2231 }),
2232 )?;
2233 info!("rename cluster {name} to {to_name}");
2234 }
2235 Op::RenameClusterReplica {
2236 cluster_id,
2237 replica_id,
2238 name,
2239 to_name,
2240 } => {
2241 if is_reserved_name(&to_name) {
2242 return Err(AdapterError::Catalog(Error::new(
2243 ErrorKind::ReservedReplicaName(to_name),
2244 )));
2245 }
2246 tx.rename_cluster_replica(replica_id, &name, &to_name)?;
2247 CatalogState::add_to_audit_log(
2248 &state.system_configuration,
2249 oracle_write_ts,
2250 session,
2251 tx,
2252 audit_events,
2253 EventType::Alter,
2254 ObjectType::ClusterReplica,
2255 EventDetails::RenameClusterReplicaV1(mz_audit_log::RenameClusterReplicaV1 {
2256 cluster_id: cluster_id.to_string(),
2257 replica_id: replica_id.to_string(),
2258 old_name: name.replica.as_str().to_string(),
2259 new_name: to_name.clone(),
2260 }),
2261 )?;
2262 info!("rename cluster replica {name} to {to_name}");
2263 }
2264 Op::RenameItem {
2265 id,
2266 to_name,
2267 current_full_name,
2268 } => {
2269 let mut updates = Vec::new();
2270
2271 let entry = state.get_entry(&id);
2272 if let CatalogItem::Type(_) = entry.item() {
2273 return Err(AdapterError::Catalog(Error::new(ErrorKind::TypeRename(
2274 current_full_name.to_string(),
2275 ))));
2276 }
2277
2278 if entry.id().is_system() {
2279 let name = state
2280 .resolve_full_name(entry.name(), session.map(|session| session.conn_id()));
2281 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
2282 name.to_string(),
2283 ))));
2284 }
2285
2286 let mut to_full_name = current_full_name.clone();
2287 to_full_name.item.clone_from(&to_name);
2288
2289 let mut to_qualified_name = entry.name().clone();
2290 to_qualified_name.item.clone_from(&to_name);
2291
2292 let details = EventDetails::RenameItemV1(mz_audit_log::RenameItemV1 {
2293 id: id.to_string(),
2294 old_name: Self::full_name_detail(¤t_full_name),
2295 new_name: Self::full_name_detail(&to_full_name),
2296 });
2297 if Self::should_audit_log_item(entry.item()) {
2298 CatalogState::add_to_audit_log(
2299 &state.system_configuration,
2300 oracle_write_ts,
2301 session,
2302 tx,
2303 audit_events,
2304 EventType::Alter,
2305 catalog_type_to_audit_object_type(entry.item().typ()),
2306 details,
2307 )?;
2308 }
2309
2310 let mut new_entry = entry.clone();
2312 new_entry.name.item.clone_from(&to_name);
2313 new_entry.item = entry
2314 .item()
2315 .rename_item_refs(current_full_name.clone(), to_full_name.item.clone(), true)
2316 .map_err(|e| {
2317 Error::new(ErrorKind::from(AmbiguousRename {
2318 depender: state
2319 .resolve_full_name(entry.name(), entry.conn_id())
2320 .to_string(),
2321 dependee: state
2322 .resolve_full_name(entry.name(), entry.conn_id())
2323 .to_string(),
2324 message: e,
2325 }))
2326 })?;
2327
2328 for id in entry.referenced_by() {
2329 let dependent_item = state.get_entry(id);
2330 let mut to_entry = dependent_item.clone();
2331 to_entry.item = dependent_item
2332 .item()
2333 .rename_item_refs(
2334 current_full_name.clone(),
2335 to_full_name.item.clone(),
2336 false,
2337 )
2338 .map_err(|e| {
2339 Error::new(ErrorKind::from(AmbiguousRename {
2340 depender: state
2341 .resolve_full_name(
2342 dependent_item.name(),
2343 dependent_item.conn_id(),
2344 )
2345 .to_string(),
2346 dependee: state
2347 .resolve_full_name(entry.name(), entry.conn_id())
2348 .to_string(),
2349 message: e,
2350 }))
2351 })?;
2352
2353 if !to_entry.item().is_temporary() {
2354 tx.update_item(*id, to_entry.into())?;
2355 } else {
2356 temporary_item_updates
2357 .push((dependent_item.clone().into(), StateDiff::Retraction));
2358 temporary_item_updates.push((to_entry.into(), StateDiff::Addition));
2359 }
2360 updates.push(*id);
2361 }
2362 if !new_entry.item().is_temporary() {
2363 tx.update_item(id, new_entry.into())?;
2364 } else {
2365 temporary_item_updates.push((entry.clone().into(), StateDiff::Retraction));
2366 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2367 }
2368
2369 updates.push(id);
2370 for id in updates {
2371 Self::log_update(state, &id);
2372 }
2373 }
2374 Op::RenameSchema {
2375 database_spec,
2376 schema_spec,
2377 new_name,
2378 check_reserved_names,
2379 } => {
2380 if check_reserved_names && is_reserved_name(&new_name) {
2381 return Err(AdapterError::Catalog(Error::new(
2382 ErrorKind::ReservedSchemaName(new_name),
2383 )));
2384 }
2385
2386 let conn_id = session
2387 .map(|session| session.conn_id())
2388 .unwrap_or(&SYSTEM_CONN_ID);
2389
2390 let schema = state.get_schema(&database_spec, &schema_spec, conn_id);
2391 let cur_name = schema.name().schema.clone();
2392
2393 let ResolvedDatabaseSpecifier::Id(database_id) = database_spec else {
2394 return Err(AdapterError::Catalog(Error::new(
2395 ErrorKind::AmbientSchemaRename(cur_name),
2396 )));
2397 };
2398 let database = state.get_database(&database_id);
2399 let database_name = &database.name;
2400
2401 let mut updates: Vec<CatalogItemId> = Vec::new();
2402 let mut items_to_update = BTreeMap::new();
2403 let mut seen: BTreeSet<CatalogItemId> = BTreeSet::new();
2414
2415 let mut update_item = |id: &CatalogItemId| {
2416 if !seen.insert(*id) {
2417 return Ok(());
2418 }
2419
2420 let entry = state.get_entry(id);
2421
2422 let mut new_entry = entry.clone();
2424 new_entry.item = entry
2425 .item
2426 .rename_schema_refs(database_name, &cur_name, &new_name)
2427 .map_err(|(s, _i)| {
2428 Error::new(ErrorKind::from(AmbiguousRename {
2429 depender: state
2430 .resolve_full_name(entry.name(), entry.conn_id())
2431 .to_string(),
2432 dependee: format!("{database_name}.{cur_name}"),
2433 message: format!("ambiguous reference to schema named {s}"),
2434 }))
2435 })?;
2436
2437 if !new_entry.item().is_temporary() {
2439 items_to_update.insert(*id, new_entry.into());
2440 } else {
2441 temporary_item_updates.push((entry.clone().into(), StateDiff::Retraction));
2442 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2443 }
2444 updates.push(*id);
2445
2446 Ok::<_, AdapterError>(())
2447 };
2448
2449 for (_name, item_id) in schema
2454 .items
2455 .iter()
2456 .chain(schema.types.iter())
2457 .chain(schema.functions.iter())
2458 {
2459 update_item(item_id)?;
2461
2462 for id in state.get_entry(item_id).referenced_by() {
2464 update_item(id)?;
2465 }
2466 }
2467 tx.update_items(items_to_update)?;
2470
2471 let SchemaSpecifier::Id(schema_id) = *schema.id() else {
2473 let schema_name = schema.name().schema.clone();
2474 return Err(AdapterError::Catalog(crate::catalog::Error::new(
2475 crate::catalog::ErrorKind::ReadOnlySystemSchema(schema_name),
2476 )));
2477 };
2478
2479 let database_name = database_spec
2481 .id()
2482 .map(|id| state.get_database(&id).name.clone());
2483 let details = EventDetails::RenameSchemaV1(mz_audit_log::RenameSchemaV1 {
2484 id: schema_id.to_string(),
2485 old_name: schema.name().schema.clone(),
2486 new_name: new_name.clone(),
2487 database_name,
2488 });
2489 CatalogState::add_to_audit_log(
2490 &state.system_configuration,
2491 oracle_write_ts,
2492 session,
2493 tx,
2494 audit_events,
2495 EventType::Alter,
2496 mz_audit_log::ObjectType::Schema,
2497 details,
2498 )?;
2499
2500 let mut new_schema = schema.clone();
2502 new_schema.name.schema.clone_from(&new_name);
2503 tx.update_schema(schema_id, new_schema.into())?;
2504
2505 for id in updates {
2506 Self::log_update(state, &id);
2507 }
2508 }
2509 Op::UpdateOwner { id, new_owner } => {
2510 let conn_id = session
2511 .map(|session| session.conn_id())
2512 .unwrap_or(&SYSTEM_CONN_ID);
2513 let old_owner = state
2514 .get_owner_id(&id, conn_id)
2515 .expect("cannot update the owner of an object without an owner");
2516 match &id {
2517 ObjectId::Cluster(id) => {
2518 let mut cluster = state.get_cluster(*id).clone();
2519 if id.is_system() {
2520 return Err(AdapterError::Catalog(Error::new(
2521 ErrorKind::ReadOnlyCluster(cluster.name),
2522 )));
2523 }
2524 Self::update_privilege_owners(
2525 &mut cluster.privileges,
2526 cluster.owner_id,
2527 new_owner,
2528 );
2529 cluster.owner_id = new_owner;
2530 tx.update_cluster(*id, cluster.into())?;
2531 }
2532 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
2533 let cluster = state.get_cluster(*cluster_id);
2534 let mut replica = cluster
2535 .replica(*replica_id)
2536 .expect("catalog out of sync")
2537 .clone();
2538 if replica_id.is_system() {
2539 return Err(AdapterError::Catalog(Error::new(
2540 ErrorKind::ReadOnlyClusterReplica(replica.name),
2541 )));
2542 }
2543 replica.owner_id = new_owner;
2544 tx.update_cluster_replica(*replica_id, replica.into())?;
2545 }
2546 ObjectId::Database(id) => {
2547 let mut database = state.get_database(id).clone();
2548 if id.is_system() {
2549 return Err(AdapterError::Catalog(Error::new(
2550 ErrorKind::ReadOnlyDatabase(database.name),
2551 )));
2552 }
2553 Self::update_privilege_owners(
2554 &mut database.privileges,
2555 database.owner_id,
2556 new_owner,
2557 );
2558 database.owner_id = new_owner;
2559 tx.update_database(*id, database.clone().into())?;
2560 }
2561 ObjectId::Schema((database_spec, schema_spec)) => {
2562 let schema_id: SchemaId = schema_spec.clone().into();
2563 let mut schema = state
2564 .get_schema(database_spec, schema_spec, conn_id)
2565 .clone();
2566 if schema_id.is_system() {
2567 let name = schema.name();
2568 let full_name = state.resolve_full_schema_name(name);
2569 return Err(AdapterError::Catalog(Error::new(
2570 ErrorKind::ReadOnlySystemSchema(full_name.to_string()),
2571 )));
2572 }
2573 Self::update_privilege_owners(
2574 &mut schema.privileges,
2575 schema.owner_id,
2576 new_owner,
2577 );
2578 schema.owner_id = new_owner;
2579 tx.update_schema(schema_id, schema.into())?;
2580 }
2581 ObjectId::Item(id) => {
2582 let entry = state.get_entry(id);
2583 let mut new_entry = entry.clone();
2584 if id.is_system() {
2585 let full_name = state.resolve_full_name(
2586 new_entry.name(),
2587 session.map(|session| session.conn_id()),
2588 );
2589 return Err(AdapterError::Catalog(Error::new(
2590 ErrorKind::ReadOnlyItem(full_name.to_string()),
2591 )));
2592 }
2593 Self::update_privilege_owners(
2594 &mut new_entry.privileges,
2595 new_entry.owner_id,
2596 new_owner,
2597 );
2598 new_entry.owner_id = new_owner;
2599 if !new_entry.item().is_temporary() {
2600 tx.update_item(*id, new_entry.into())?;
2601 } else {
2602 temporary_item_updates
2603 .push((entry.clone().into(), StateDiff::Retraction));
2604 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2605 }
2606 }
2607 ObjectId::NetworkPolicy(id) => {
2608 let mut policy = state.get_network_policy(id).clone();
2609 if id.is_system() {
2610 return Err(AdapterError::Catalog(Error::new(
2611 ErrorKind::ReadOnlyNetworkPolicy(policy.name),
2612 )));
2613 }
2614 Self::update_privilege_owners(
2615 &mut policy.privileges,
2616 policy.owner_id,
2617 new_owner,
2618 );
2619 policy.owner_id = new_owner;
2620 tx.update_network_policy(*id, policy.into())?;
2621 }
2622 ObjectId::Role(_) => unreachable!("roles have no owner"),
2623 }
2624 let object_type = state.get_object_type(&id);
2625 CatalogState::add_to_audit_log(
2626 &state.system_configuration,
2627 oracle_write_ts,
2628 session,
2629 tx,
2630 audit_events,
2631 EventType::Alter,
2632 object_type_to_audit_object_type(object_type),
2633 EventDetails::UpdateOwnerV1(mz_audit_log::UpdateOwnerV1 {
2634 object_id: id.to_string(),
2635 old_owner_id: old_owner.to_string(),
2636 new_owner_id: new_owner.to_string(),
2637 }),
2638 )?;
2639 }
2640 Op::UpdateClusterConfig { id, name, config } => {
2641 let mut cluster = state.get_cluster(id).clone();
2642 cluster.config = config;
2643 tx.update_cluster(id, cluster.into())?;
2644 info!("update cluster {}", name);
2645
2646 CatalogState::add_to_audit_log(
2647 &state.system_configuration,
2648 oracle_write_ts,
2649 session,
2650 tx,
2651 audit_events,
2652 EventType::Alter,
2653 ObjectType::Cluster,
2654 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2655 id: id.to_string(),
2656 name,
2657 }),
2658 )?;
2659 }
2660 Op::UpdateClusterReplicaConfig {
2661 replica_id,
2662 cluster_id,
2663 config,
2664 } => {
2665 let replica = state.get_cluster_replica(cluster_id, replica_id).to_owned();
2666 info!("update replica {}", replica.name);
2667 tx.update_cluster_replica(
2668 replica_id,
2669 mz_catalog::durable::ClusterReplica {
2670 cluster_id,
2671 replica_id,
2672 name: replica.name.clone(),
2673 config: config.clone().into(),
2674 owner_id: replica.owner_id,
2675 },
2676 )?;
2677 }
2678 Op::UpdateItem { id, name, to_item } => {
2679 if !to_item.is_temporary() {
2688 if let Some(temp_id) =
2689 to_item
2690 .uses()
2691 .iter()
2692 .find(|id| match state.try_get_entry(*id) {
2693 Some(entry) => entry.item().is_temporary(),
2694 None => temporary_ids.contains(id),
2695 })
2696 {
2697 let temp_item = state.get_entry(temp_id);
2698 return Err(AdapterError::Catalog(Error::new(
2699 ErrorKind::InvalidTemporaryDependency(temp_item.name().item.clone()),
2700 )));
2701 }
2702 }
2703
2704 let mut entry = state.get_entry(&id).clone();
2705 entry.name = name.clone();
2706 entry.item = to_item.clone();
2707 tx.update_item(id, entry.into())?;
2708
2709 if Self::should_audit_log_item(&to_item) {
2710 let mut full_name = Self::full_name_detail(
2711 &state.resolve_full_name(&name, session.map(|session| session.conn_id())),
2712 );
2713 full_name.item = name.item;
2714
2715 CatalogState::add_to_audit_log(
2716 &state.system_configuration,
2717 oracle_write_ts,
2718 session,
2719 tx,
2720 audit_events,
2721 EventType::Alter,
2722 catalog_type_to_audit_object_type(to_item.typ()),
2723 EventDetails::UpdateItemV1(mz_audit_log::UpdateItemV1 {
2724 id: id.to_string(),
2725 name: full_name,
2726 }),
2727 )?;
2728 }
2729
2730 Self::log_update(state, &id);
2731 }
2732 Op::UpdateSystemConfiguration { name, value } => {
2733 let parsed_value = state.parse_system_configuration(&name, value.borrow())?;
2734 tx.upsert_system_config(&name, parsed_value.clone())?;
2735 if name == WITH_0DT_DEPLOYMENT_MAX_WAIT.name() {
2740 let with_0dt_deployment_max_wait =
2741 Duration::parse(VarInput::Flat(&parsed_value))
2742 .expect("parsing succeeded above");
2743 tx.set_0dt_deployment_max_wait(with_0dt_deployment_max_wait)?;
2744 } else if name == WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.name() {
2745 let with_0dt_deployment_ddl_check_interval =
2746 Duration::parse(VarInput::Flat(&parsed_value))
2747 .expect("parsing succeeded above");
2748 tx.set_0dt_deployment_ddl_check_interval(
2749 with_0dt_deployment_ddl_check_interval,
2750 )?;
2751 } else if name == ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.name() {
2752 let panic_after_timeout =
2753 strconv::parse_bool(&parsed_value).expect("parsing succeeded above");
2754 tx.set_enable_0dt_deployment_panic_after_timeout(panic_after_timeout)?;
2755 }
2756
2757 CatalogState::add_to_audit_log(
2758 &state.system_configuration,
2759 oracle_write_ts,
2760 session,
2761 tx,
2762 audit_events,
2763 EventType::Alter,
2764 ObjectType::System,
2765 EventDetails::SetV1(mz_audit_log::SetV1 {
2766 name,
2767 value: Some(value.borrow().to_vec().join(", ")),
2768 }),
2769 )?;
2770 }
2771 Op::ResetSystemConfiguration { name } => {
2772 tx.remove_system_config(&name);
2773 if name == WITH_0DT_DEPLOYMENT_MAX_WAIT.name() {
2778 tx.reset_0dt_deployment_max_wait()?;
2779 } else if name == WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.name() {
2780 tx.reset_0dt_deployment_ddl_check_interval()?;
2781 } else if name == ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.name() {
2782 tx.reset_enable_0dt_deployment_panic_after_timeout()?;
2783 }
2784
2785 CatalogState::add_to_audit_log(
2786 &state.system_configuration,
2787 oracle_write_ts,
2788 session,
2789 tx,
2790 audit_events,
2791 EventType::Alter,
2792 ObjectType::System,
2793 EventDetails::SetV1(mz_audit_log::SetV1 { name, value: None }),
2794 )?;
2795 }
2796 Op::ResetAllSystemConfiguration => {
2797 tx.clear_system_configs();
2798 tx.reset_0dt_deployment_max_wait()?;
2799 tx.reset_0dt_deployment_ddl_check_interval()?;
2800 tx.reset_enable_0dt_deployment_panic_after_timeout()?;
2801
2802 CatalogState::add_to_audit_log(
2803 &state.system_configuration,
2804 oracle_write_ts,
2805 session,
2806 tx,
2807 audit_events,
2808 EventType::Alter,
2809 ObjectType::System,
2810 EventDetails::ResetAllV1,
2811 )?;
2812 }
2813 Op::UpdateScopedSystemParameters {
2814 scoped,
2815 prune_scope,
2816 } => {
2817 let live_clusters: BTreeSet<ClusterId> =
2830 tx.get_clusters().map(|cluster| cluster.id).collect();
2831 let live_replicas: BTreeSet<ReplicaId> = tx
2832 .get_cluster_replicas()
2833 .map(|replica| replica.replica_id)
2834 .collect();
2835 let prune_cluster = |id: &ClusterId| {
2836 prune_scope
2837 .as_ref()
2838 .map_or(true, |s| s.clusters.contains(id))
2839 };
2840 let prune_replica = |id: &ReplicaId| {
2841 prune_scope
2842 .as_ref()
2843 .map_or(true, |s| s.replicas.contains(id))
2844 };
2845
2846 let existing_cluster: BTreeMap<(ClusterId, String), String> = tx
2848 .get_cluster_system_configurations()
2849 .map(|c| ((c.cluster_id, c.name), c.value))
2850 .collect();
2851 let mut desired_cluster: BTreeSet<(ClusterId, String)> = BTreeSet::new();
2852 for (cluster_id, values) in &scoped.cluster {
2853 if !live_clusters.contains(cluster_id) {
2854 continue;
2855 }
2856 for (name, value) in values {
2857 desired_cluster.insert((*cluster_id, name.clone()));
2858 if existing_cluster.get(&(*cluster_id, name.clone())) != Some(value) {
2859 tx.upsert_cluster_system_config(*cluster_id, name, value.clone())?;
2860 }
2861 }
2862 }
2863 for (cluster_id, name) in existing_cluster.into_keys() {
2864 if (!live_clusters.contains(&cluster_id) || prune_cluster(&cluster_id))
2865 && !desired_cluster.contains(&(cluster_id, name.clone()))
2866 {
2867 tx.remove_cluster_system_config(cluster_id, &name);
2868 }
2869 }
2870
2871 let existing_replica: BTreeMap<(ReplicaId, String), String> = tx
2873 .get_replica_system_configurations()
2874 .map(|r| ((r.replica_id, r.name), r.value))
2875 .collect();
2876 let mut desired_replica: BTreeSet<(ReplicaId, String)> = BTreeSet::new();
2877 for (replica_id, values) in &scoped.replica {
2878 if !live_replicas.contains(replica_id) {
2879 continue;
2880 }
2881 for (name, value) in values {
2882 desired_replica.insert((*replica_id, name.clone()));
2883 if existing_replica.get(&(*replica_id, name.clone())) != Some(value) {
2884 tx.upsert_replica_system_config(*replica_id, name, value.clone())?;
2885 }
2886 }
2887 }
2888 for (replica_id, name) in existing_replica.into_keys() {
2889 if (!live_replicas.contains(&replica_id) || prune_replica(&replica_id))
2890 && !desired_replica.contains(&(replica_id, name.clone()))
2891 {
2892 tx.remove_replica_system_config(replica_id, &name);
2893 }
2894 }
2895 }
2896 Op::InjectAuditEvents { events } => {
2897 for event in events {
2898 let id = tx.allocate_audit_log_id()?;
2899 let ev = VersionedEvent::new(
2900 id,
2901 event.event_type,
2902 event.object_type,
2903 event.details,
2904 event.user,
2905 oracle_write_ts.into(),
2906 );
2907 audit_events.push(ev.clone());
2908 tx.insert_audit_log_event(ev);
2909 }
2910 }
2911 };
2912 Ok(temporary_item_updates)
2913 }
2914
2915 fn log_update(state: &CatalogState, id: &CatalogItemId) {
2916 let entry = state.get_entry(id);
2917 info!(
2918 "update {} {} ({})",
2919 entry.item_type(),
2920 state.resolve_full_name(entry.name(), entry.conn_id()),
2921 id
2922 );
2923 }
2924
2925 fn update_privilege_owners(
2929 privileges: &mut PrivilegeMap,
2930 old_owner: RoleId,
2931 new_owner: RoleId,
2932 ) {
2933 let mut flat_privileges: Vec<_> = privileges.all_values_owned().collect();
2935
2936 let mut new_present = false;
2937 for privilege in flat_privileges.iter_mut() {
2938 if privilege.grantor == old_owner {
2941 privilege.grantor = new_owner;
2942 } else if privilege.grantor == new_owner {
2943 new_present = true;
2944 }
2945 if privilege.grantee == old_owner {
2947 privilege.grantee = new_owner;
2948 } else if privilege.grantee == new_owner {
2949 new_present = true;
2950 }
2951 }
2952
2953 if new_present {
2957 let privilege_map: BTreeMap<_, Vec<_>> =
2959 flat_privileges
2960 .into_iter()
2961 .fold(BTreeMap::new(), |mut accum, privilege| {
2962 accum
2963 .entry((privilege.grantee, privilege.grantor))
2964 .or_default()
2965 .push(privilege);
2966 accum
2967 });
2968
2969 flat_privileges = privilege_map
2971 .into_iter()
2972 .map(|((grantee, grantor), values)|
2973 values.into_iter().fold(
2975 MzAclItem::empty(grantee, grantor),
2976 |mut accum, mz_aclitem| {
2977 accum.acl_mode =
2978 accum.acl_mode.union(mz_aclitem.acl_mode);
2979 accum
2980 },
2981 ))
2982 .collect();
2983 }
2984
2985 *privileges = PrivilegeMap::from_mz_acl_items(flat_privileges);
2986 }
2987}
2988
2989fn tx_replace_item(
2998 tx: &mut Transaction<'_>,
2999 state: &CatalogState,
3000 id: CatalogItemId,
3001 new_entry: CatalogEntry,
3002) -> Result<(), AdapterError> {
3003 let new_id = new_entry.id;
3004
3005 for use_id in new_entry.referenced_by() {
3007 if tx.get_item(use_id).is_none() {
3009 continue;
3010 }
3011
3012 let mut dependent = state.get_entry(use_id).clone();
3013 dependent.item = dependent.item.replace_item_refs(id, new_id);
3014 tx.update_item(*use_id, dependent.into())?;
3015 }
3016
3017 let old_comment_id = state.get_comment_id(ObjectId::Item(id));
3019 let new_comment_id = new_entry.comment_object_id();
3020 if let Some(comments) = state.comments.get_object_comments(old_comment_id) {
3021 tx.drop_comments(&[old_comment_id].into())?;
3022 for (sub, comment) in comments {
3023 tx.update_comment(new_comment_id, *sub, Some(comment.clone()))?;
3024 }
3025 }
3026
3027 let mz_catalog::durable::Item {
3028 id: _,
3029 oid,
3030 global_id,
3031 schema_id,
3032 name,
3033 create_sql,
3034 owner_id,
3035 privileges,
3036 extra_versions,
3037 } = new_entry.into();
3038
3039 tx.remove_item(id)?;
3040 tx.insert_item(
3041 new_id,
3042 oid,
3043 global_id,
3044 schema_id,
3045 &name,
3046 create_sql,
3047 owner_id,
3048 privileges,
3049 extra_versions,
3050 )?;
3051
3052 Ok(())
3053}
3054
3055fn apply_replacement_audit_events(
3057 state: &CatalogState,
3058 target: &CatalogEntry,
3059 replacement: &CatalogEntry,
3060) -> Vec<(EventType, EventDetails)> {
3061 let mut events = Vec::new();
3062
3063 let target_name = state.resolve_full_name(target.name(), target.conn_id());
3064 let target_id_name = IdFullNameV1 {
3065 id: target.id().to_string(),
3066 name: Catalog::full_name_detail(&target_name),
3067 };
3068 let replacement_name = state.resolve_full_name(replacement.name(), replacement.conn_id());
3069 let replacement_id_name = IdFullNameV1 {
3070 id: replacement.id().to_string(),
3071 name: Catalog::full_name_detail(&replacement_name),
3072 };
3073
3074 if Catalog::should_audit_log_item(&replacement.item) {
3075 events.push((
3076 EventType::Drop,
3077 EventDetails::IdFullNameV1(replacement_id_name.clone()),
3078 ));
3079 }
3080
3081 if Catalog::should_audit_log_item(&target.item) {
3082 events.push((
3083 EventType::Alter,
3084 EventDetails::AlterApplyReplacementV1(mz_audit_log::AlterApplyReplacementV1 {
3085 target: target_id_name.clone(),
3086 replacement: replacement_id_name,
3087 }),
3088 ));
3089
3090 if let Some(old_cluster_id) = target.cluster_id()
3091 && let Some(new_cluster_id) = replacement.cluster_id()
3092 && old_cluster_id != new_cluster_id
3093 {
3094 events.push((
3097 EventType::Alter,
3098 EventDetails::AlterSetClusterV1(mz_audit_log::AlterSetClusterV1 {
3099 id: replacement.id().to_string(),
3100 name: target_id_name.name,
3101 old_cluster_id: old_cluster_id.to_string(),
3102 new_cluster_id: new_cluster_id.to_string(),
3103 }),
3104 ));
3105 }
3106 }
3107
3108 events
3109}
3110
3111#[derive(Debug, Default)]
3119pub(crate) struct ObjectsToDrop {
3120 pub comments: BTreeSet<CommentObjectId>,
3121 pub databases: BTreeSet<DatabaseId>,
3122 pub schemas: BTreeMap<SchemaSpecifier, ResolvedDatabaseSpecifier>,
3123 pub clusters: BTreeSet<ClusterId>,
3124 pub replicas: BTreeMap<ReplicaId, (ClusterId, ReplicaCreateDropReason)>,
3125 pub roles: BTreeSet<RoleId>,
3126 pub items: Vec<CatalogItemId>,
3127 pub network_policies: BTreeSet<NetworkPolicyId>,
3128}
3129
3130impl ObjectsToDrop {
3131 pub fn generate(
3132 drop_object_infos: impl IntoIterator<Item = DropObjectInfo>,
3133 state: &CatalogState,
3134 session: Option<&ConnMeta>,
3135 ) -> Result<Self, AdapterError> {
3136 let mut delta = ObjectsToDrop::default();
3137
3138 for drop_object_info in drop_object_infos {
3139 delta.add_item(drop_object_info, state, session)?;
3140 }
3141
3142 Ok(delta)
3143 }
3144
3145 fn add_item(
3146 &mut self,
3147 drop_object_info: DropObjectInfo,
3148 state: &CatalogState,
3149 session: Option<&ConnMeta>,
3150 ) -> Result<(), AdapterError> {
3151 self.comments
3152 .insert(state.get_comment_id(drop_object_info.to_object_id()));
3153
3154 match drop_object_info {
3155 DropObjectInfo::Database(database_id) => {
3156 let database = &state.database_by_id[&database_id];
3157 if database_id.is_system() {
3158 return Err(AdapterError::Catalog(Error::new(
3159 ErrorKind::ReadOnlyDatabase(database.name().to_string()),
3160 )));
3161 }
3162
3163 self.databases.insert(database_id);
3164 }
3165 DropObjectInfo::Schema((database_spec, schema_spec)) => {
3166 let schema = state.get_schema(
3167 &database_spec,
3168 &schema_spec,
3169 session
3170 .map(|session| session.conn_id())
3171 .unwrap_or(&SYSTEM_CONN_ID),
3172 );
3173 let schema_id: SchemaId = schema_spec.into();
3174 if schema_id.is_system() {
3175 let name = schema.name();
3176 let full_name = state.resolve_full_schema_name(name);
3177 return Err(AdapterError::Catalog(Error::new(
3178 ErrorKind::ReadOnlySystemSchema(full_name.to_string()),
3179 )));
3180 }
3181
3182 self.schemas.insert(schema_spec, database_spec);
3183 }
3184 DropObjectInfo::Role(role_id) => {
3185 let name = state.get_role(&role_id).name().to_string();
3186 if role_id.is_system() || role_id.is_predefined() {
3187 return Err(AdapterError::Catalog(Error::new(
3188 ErrorKind::ReservedRoleName(name.clone()),
3189 )));
3190 }
3191 state.ensure_not_reserved_role(&role_id)?;
3192
3193 self.roles.insert(role_id);
3194 }
3195 DropObjectInfo::Cluster(cluster_id) => {
3196 let cluster = state.get_cluster(cluster_id);
3197 let name = &cluster.name;
3198 if cluster_id.is_system() {
3199 return Err(AdapterError::Catalog(Error::new(
3200 ErrorKind::ReadOnlyCluster(name.clone()),
3201 )));
3202 }
3203
3204 self.clusters.insert(cluster_id);
3205 }
3206 DropObjectInfo::ClusterReplica((cluster_id, replica_id, reason)) => {
3207 let cluster = state.get_cluster(cluster_id);
3208 let replica = cluster.replica(replica_id).expect("Must exist");
3209
3210 self.replicas
3211 .insert(replica.replica_id, (cluster.id, reason));
3212
3213 let mut seen: BTreeSet<ObjectId> =
3232 self.items.iter().copied().map(ObjectId::Item).collect();
3233 for dep in state.cluster_replica_dependents(cluster_id, replica_id, &mut seen) {
3234 if let ObjectId::Item(dep_id) = dep {
3235 info!(
3236 "implicitly dropping {} because target replica was dropped",
3237 state.get_entry(&dep_id).name().item,
3238 );
3239 self.comments
3240 .insert(state.get_comment_id(ObjectId::Item(dep_id)));
3241 self.items.push(dep_id);
3242 }
3243 }
3244 }
3245 DropObjectInfo::Item(item_id) => {
3246 let entry = state.get_entry(&item_id);
3247 if item_id.is_system() {
3248 let name = entry.name();
3249 let full_name =
3250 state.resolve_full_name(name, session.map(|session| session.conn_id()));
3251 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
3252 full_name.to_string(),
3253 ))));
3254 }
3255
3256 self.items.push(item_id);
3257 }
3258 DropObjectInfo::NetworkPolicy(network_policy_id) => {
3259 let policy = state.get_network_policy(&network_policy_id);
3260 let name = &policy.name;
3261 if network_policy_id.is_system() {
3262 return Err(AdapterError::Catalog(Error::new(
3263 ErrorKind::ReadOnlyNetworkPolicy(name.clone()),
3264 )));
3265 }
3266
3267 self.network_policies.insert(network_policy_id);
3268 }
3269 }
3270
3271 Ok(())
3272 }
3273}
3274
3275#[cfg(test)]
3276mod tests {
3277 use mz_catalog::SYSTEM_CONN_ID;
3278 use mz_catalog::memory::objects::{CatalogItem, Table, TableDataSource};
3279 use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
3280 use mz_repr::role_id::RoleId;
3281 use mz_repr::{RelationDesc, RelationVersion, VersionedRelationDesc};
3282 use mz_sql::DEFAULT_SCHEMA;
3283 use mz_sql::catalog::CatalogDatabase;
3284 use mz_sql::names::{
3285 ItemQualifiers, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds,
3286 };
3287 use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
3288
3289 use crate::catalog::{Catalog, Op};
3290 use crate::session::DEFAULT_DATABASE_NAME;
3291
3292 #[mz_ore::test]
3293 fn test_replica_create_drop_reason_into_audit_log() {
3294 use mz_audit_log::CreateOrDropClusterReplicaReasonV1;
3295
3296 use crate::catalog::ReplicaCreateDropReason;
3297
3298 let (reason, scheduling_policies) = ReplicaCreateDropReason::Retired.into_audit_log();
3302 assert_eq!(reason, CreateOrDropClusterReplicaReasonV1::Retired);
3303 assert!(scheduling_policies.is_none());
3304 }
3305
3306 #[mz_ore::test]
3307 fn test_update_privilege_owners() {
3308 let old_owner = RoleId::User(1);
3309 let new_owner = RoleId::User(2);
3310 let other_role = RoleId::User(3);
3311
3312 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3314 MzAclItem {
3315 grantee: other_role,
3316 grantor: old_owner,
3317 acl_mode: AclMode::UPDATE,
3318 },
3319 MzAclItem {
3320 grantee: other_role,
3321 grantor: new_owner,
3322 acl_mode: AclMode::SELECT,
3323 },
3324 ]);
3325 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3326 assert_eq!(1, privileges.all_values().count());
3327 assert_eq!(
3328 vec![MzAclItem {
3329 grantee: other_role,
3330 grantor: new_owner,
3331 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3332 }],
3333 privileges.all_values_owned().collect::<Vec<_>>()
3334 );
3335
3336 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3338 MzAclItem {
3339 grantee: old_owner,
3340 grantor: other_role,
3341 acl_mode: AclMode::UPDATE,
3342 },
3343 MzAclItem {
3344 grantee: new_owner,
3345 grantor: other_role,
3346 acl_mode: AclMode::SELECT,
3347 },
3348 ]);
3349 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3350 assert_eq!(1, privileges.all_values().count());
3351 assert_eq!(
3352 vec![MzAclItem {
3353 grantee: new_owner,
3354 grantor: other_role,
3355 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3356 }],
3357 privileges.all_values_owned().collect::<Vec<_>>()
3358 );
3359
3360 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3362 MzAclItem {
3363 grantee: old_owner,
3364 grantor: old_owner,
3365 acl_mode: AclMode::UPDATE,
3366 },
3367 MzAclItem {
3368 grantee: new_owner,
3369 grantor: new_owner,
3370 acl_mode: AclMode::SELECT,
3371 },
3372 ]);
3373 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3374 assert_eq!(1, privileges.all_values().count());
3375 assert_eq!(
3376 vec![MzAclItem {
3377 grantee: new_owner,
3378 grantor: new_owner,
3379 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3380 }],
3381 privileges.all_values_owned().collect::<Vec<_>>()
3382 );
3383 }
3384
3385 #[mz_ore::test(tokio::test)]
3392 #[cfg_attr(miri, ignore)] async fn test_transact_incremental_dry_run_processes_only_new_ops() {
3394 Catalog::with_debug(|catalog| async move {
3395 let database = catalog
3397 .resolve_database(DEFAULT_DATABASE_NAME)
3398 .expect("default database");
3399 let database_name = database.name.clone();
3400 let database_spec = ResolvedDatabaseSpecifier::Id(database.id());
3401 let schema = catalog
3402 .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID)
3403 .expect("default schema");
3404 let schema_name = schema.name.schema.clone();
3405 let schema_spec = schema.id.clone();
3406
3407 let (id_t1, global_id_t1) = catalog
3409 .allocate_user_id_for_test()
3410 .await
3411 .expect("allocate id for t1");
3412 let (id_t2, global_id_t2) = catalog
3413 .allocate_user_id_for_test()
3414 .await
3415 .expect("allocate id for t2");
3416
3417 let oracle_write_ts = catalog.current_upper().await;
3418
3419 let make_table_op = |id, global_id, name: &str| Op::CreateItem {
3421 id,
3422 name: QualifiedItemName {
3423 qualifiers: ItemQualifiers {
3424 database_spec: database_spec.clone(),
3425 schema_spec: schema_spec.clone(),
3426 },
3427 item: name.to_string(),
3428 },
3429 item: CatalogItem::Table(Table {
3430 create_sql: Some(format!(
3431 "CREATE TABLE {database_name}.{schema_name}.{name} ()"
3432 )),
3433 desc: VersionedRelationDesc::new(RelationDesc::empty()),
3434 collections: [(RelationVersion::root(), global_id)].into_iter().collect(),
3435 conn_id: None,
3436 resolved_ids: ResolvedIds::empty(),
3437 custom_logical_compaction_window: None,
3438 is_retained_metrics_object: false,
3439 data_source: TableDataSource::TableWrites { defaults: vec![] },
3440 }),
3441 owner_id: MZ_SYSTEM_ROLE_ID,
3442 };
3443
3444 let op_t1 = make_table_op(id_t1, global_id_t1, "t1");
3445 let op_t2 = make_table_op(id_t2, global_id_t2, "t2");
3446
3447 let base_state = catalog.state().clone();
3448
3449 let (state_after_t1, snapshot_after_t1) = catalog
3453 .transact_incremental_dry_run(
3454 &base_state,
3455 vec![op_t1.clone()],
3456 None,
3457 None,
3458 oracle_write_ts,
3459 )
3460 .await
3461 .expect("first dry run");
3462
3463 assert!(
3465 state_after_t1.try_get_entry(&id_t1).is_some(),
3466 "t1 should exist after first dry run"
3467 );
3468 assert_eq!(
3469 state_after_t1
3470 .try_get_entry(&id_t1)
3471 .expect("t1 entry")
3472 .name()
3473 .item,
3474 "t1"
3475 );
3476 assert!(
3477 state_after_t1.try_get_entry(&id_t2).is_none(),
3478 "t2 should NOT exist after first dry run"
3479 );
3480
3481 let (state_incremental, _) = catalog
3483 .transact_incremental_dry_run(
3484 &state_after_t1,
3485 vec![op_t2.clone()],
3486 None,
3487 Some(snapshot_after_t1),
3488 oracle_write_ts,
3489 )
3490 .await
3491 .expect("second dry run");
3492
3493 assert!(
3495 state_incremental.try_get_entry(&id_t1).is_some(),
3496 "t1 should exist in incremental result"
3497 );
3498 assert!(
3499 state_incremental.try_get_entry(&id_t2).is_some(),
3500 "t2 should exist in incremental result"
3501 );
3502
3503 let (state_all_at_once, _) = catalog
3506 .transact_incremental_dry_run(
3507 &base_state,
3508 vec![op_t1.clone(), op_t2.clone()],
3509 None,
3510 None,
3511 oracle_write_ts,
3512 )
3513 .await
3514 .expect("all-at-once dry run");
3515
3516 assert!(
3517 state_all_at_once.try_get_entry(&id_t1).is_some(),
3518 "t1 should exist in all-at-once result"
3519 );
3520 assert!(
3521 state_all_at_once.try_get_entry(&id_t2).is_some(),
3522 "t2 should exist in all-at-once result"
3523 );
3524
3525 let inc_t1 = state_incremental.try_get_entry(&id_t1).expect("inc t1");
3528 let all_t1 = state_all_at_once.try_get_entry(&id_t1).expect("all t1");
3529 assert_eq!(inc_t1.name(), all_t1.name());
3530 assert_eq!(inc_t1.owner_id, all_t1.owner_id);
3531
3532 let inc_t2 = state_incremental.try_get_entry(&id_t2).expect("inc t2");
3533 let all_t2 = state_all_at_once.try_get_entry(&id_t2).expect("all t2");
3534 assert_eq!(inc_t2.name(), all_t2.name());
3535 assert_eq!(inc_t2.owner_id, all_t2.owner_id);
3536
3537 catalog.expire().await;
3538 })
3539 .await
3540 }
3541
3542 #[mz_ore::test(tokio::test)]
3549 #[cfg_attr(miri, ignore)] async fn test_transact_update_scoped_system_parameters_prune() {
3551 use std::collections::{BTreeMap, BTreeSet};
3552
3553 use mz_catalog::memory::objects::{ClusterConfig, ClusterVariant};
3554 use mz_controller_types::ClusterId;
3555
3556 use crate::catalog::DropObjectInfo;
3557 use crate::config::{ScopedParameters, ScopedParametersScope};
3558
3559 Catalog::with_debug(|mut catalog| async move {
3560 let param = "max_query_result_size".to_string();
3561 let value = "1MB".to_string();
3562
3563 let commit_ts = catalog.current_upper().await;
3566 let cluster_a = catalog
3567 .allocate_user_cluster_id(commit_ts)
3568 .await
3569 .expect("allocate cluster A");
3570 let commit_ts = catalog.current_upper().await;
3571 let cluster_b = catalog
3572 .allocate_user_cluster_id(commit_ts)
3573 .await
3574 .expect("allocate cluster B");
3575
3576 let make_cluster_op = |id: ClusterId, name: &str| Op::CreateCluster {
3577 id,
3578 name: name.to_string(),
3579 introspection_sources: Vec::new(),
3580 owner_id: MZ_SYSTEM_ROLE_ID,
3581 config: ClusterConfig {
3582 variant: ClusterVariant::Unmanaged,
3583 workload_class: None,
3584 },
3585 };
3586
3587 let oracle_write_ts = catalog.current_upper().await;
3588 catalog
3589 .transact(
3590 None,
3591 oracle_write_ts,
3592 None,
3593 vec![
3594 make_cluster_op(cluster_a, "test_cluster_a"),
3595 make_cluster_op(cluster_b, "test_cluster_b"),
3596 ],
3597 )
3598 .await
3599 .expect("create clusters");
3600
3601 async fn rows(catalog: &Catalog) -> BTreeSet<(ClusterId, String, String)> {
3603 let mut storage = catalog.storage().await;
3604 let tx = storage.transaction().await.expect("open transaction");
3605 tx.get_cluster_system_configurations()
3606 .map(|c| (c.cluster_id, c.name, c.value))
3607 .collect()
3608 }
3609
3610 let scope_with = |ids: &[ClusterId]| ScopedParametersScope {
3611 clusters: ids.iter().copied().collect(),
3612 replicas: BTreeSet::new(),
3613 };
3614 let cluster_scoped = |id: ClusterId| {
3615 let mut cluster = BTreeMap::new();
3616 let mut overrides = BTreeMap::new();
3617 overrides.insert(param.clone(), value.clone());
3618 cluster.insert(id, overrides);
3619 ScopedParameters {
3620 cluster,
3621 replica: BTreeMap::new(),
3622 }
3623 };
3624 let empty_scoped = || ScopedParameters {
3625 cluster: BTreeMap::new(),
3626 replica: BTreeMap::new(),
3627 };
3628
3629 let oracle_write_ts = catalog.current_upper().await;
3631 catalog
3632 .transact(
3633 None,
3634 oracle_write_ts,
3635 None,
3636 vec![Op::UpdateScopedSystemParameters {
3637 scoped: cluster_scoped(cluster_a),
3638 prune_scope: Some(scope_with(&[cluster_a])),
3639 }],
3640 )
3641 .await
3642 .expect("upsert A");
3643 assert!(
3644 rows(&catalog)
3645 .await
3646 .contains(&(cluster_a, param.clone(), value.clone())),
3647 "upsert must create the row for A"
3648 );
3649
3650 let oracle_write_ts = catalog.current_upper().await;
3653 catalog
3654 .transact(
3655 None,
3656 oracle_write_ts,
3657 None,
3658 vec![Op::UpdateScopedSystemParameters {
3659 scoped: cluster_scoped(cluster_b),
3660 prune_scope: Some(scope_with(&[cluster_b])),
3661 }],
3662 )
3663 .await
3664 .expect("upsert B");
3665 assert!(
3666 rows(&catalog)
3667 .await
3668 .contains(&(cluster_b, param.clone(), value.clone())),
3669 "setup must create the row for B"
3670 );
3671
3672 let oracle_write_ts = catalog.current_upper().await;
3676 catalog
3677 .transact(
3678 None,
3679 oracle_write_ts,
3680 None,
3681 vec![Op::UpdateScopedSystemParameters {
3682 scoped: empty_scoped(),
3683 prune_scope: Some(scope_with(&[cluster_a])),
3684 }],
3685 )
3686 .await
3687 .expect("bounded prune");
3688 assert!(
3689 rows(&catalog)
3690 .await
3691 .contains(&(cluster_b, param.clone(), value.clone())),
3692 "a live cluster outside prune_scope must keep its row"
3693 );
3694
3695 let oracle_write_ts = catalog.current_upper().await;
3698 catalog
3699 .transact(
3700 None,
3701 oracle_write_ts,
3702 None,
3703 vec![Op::UpdateScopedSystemParameters {
3704 scoped: empty_scoped(),
3705 prune_scope: Some(scope_with(&[cluster_a])),
3706 }],
3707 )
3708 .await
3709 .expect("stale in-scope prune");
3710 assert!(
3711 !rows(&catalog)
3712 .await
3713 .iter()
3714 .any(|(id, _, _)| *id == cluster_a),
3715 "a live in-scope undesired row must be removed"
3716 );
3717
3718 let oracle_write_ts = catalog.current_upper().await;
3723 catalog
3724 .transact(
3725 None,
3726 oracle_write_ts,
3727 None,
3728 vec![Op::UpdateScopedSystemParameters {
3729 scoped: cluster_scoped(cluster_a),
3730 prune_scope: Some(scope_with(&[cluster_a])),
3731 }],
3732 )
3733 .await
3734 .expect("re-create A row");
3735 assert!(
3736 rows(&catalog)
3737 .await
3738 .contains(&(cluster_a, param.clone(), value.clone())),
3739 "re-created row for A must exist"
3740 );
3741
3742 let oracle_write_ts = catalog.current_upper().await;
3743 catalog
3744 .transact(
3745 None,
3746 oracle_write_ts,
3747 None,
3748 vec![Op::DropObjects(vec![DropObjectInfo::Cluster(cluster_a)])],
3749 )
3750 .await
3751 .expect("drop cluster A");
3752
3753 let oracle_write_ts = catalog.current_upper().await;
3756 catalog
3757 .transact(
3758 None,
3759 oracle_write_ts,
3760 None,
3761 vec![Op::UpdateScopedSystemParameters {
3762 scoped: cluster_scoped(cluster_b),
3763 prune_scope: Some(scope_with(&[cluster_b])),
3764 }],
3765 )
3766 .await
3767 .expect("orphan prune");
3768 assert!(
3769 !rows(&catalog)
3770 .await
3771 .iter()
3772 .any(|(id, _, _)| *id == cluster_a),
3773 "orphan row for a dropped cluster must be removed even when absent from prune_scope"
3774 );
3775
3776 catalog.expire().await;
3777 })
3778 .await
3779 }
3780}