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::{
20 BurstAudit, BurstFinishCause, ExpectedClusterState, ReconfigurationAudit,
21};
22use mz_adapter_types::compaction::CompactionWindow;
23use mz_adapter_types::connection::ConnectionId;
24use mz_adapter_types::dyncfgs::{
25 ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT, WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL,
26 WITH_0DT_DEPLOYMENT_MAX_WAIT,
27};
28use mz_audit_log::{
29 AlterClusterReconfigurationV1, BurstFinishCauseV1, ClusterHydrationBurstV1,
30 ClusterReplicaLoggingV1, CreateOrDropClusterReplicaReasonV1, EventDetails, EventType,
31 HydrationBurstLifecycleV1, IdFullNameV1, IdNameV1, ObjectType, ReconfigurationLifecycleV1,
32 SchedulingDecisionsWithReasonsV2, VersionedEvent,
33};
34use mz_catalog::SYSTEM_CONN_ID;
35use mz_catalog::builtin::BuiltinLog;
36use mz_catalog::durable::{NetworkPolicy, Snapshot, Transaction};
37use mz_catalog::expr_cache::LocalExpressions;
38use mz_catalog::memory::error::{AmbiguousRename, Error, ErrorKind};
39use mz_catalog::memory::objects::{
40 CatalogEntry, CatalogItem, ClusterConfig, ClusterVariant, DataSourceDesc, DefaultPrivileges,
41 ReconfigurationState, ReconfigurationStatus, ReconfigurationTarget, SourceReferences,
42 StateDiff, StateUpdate, StateUpdateKind, TemporaryItem,
43};
44use mz_controller::clusters::{ManagedReplicaLocation, ReplicaConfig, ReplicaLocation};
45use mz_controller_types::{ClusterId, ReplicaId};
46use mz_ore::collections::HashSet;
47use mz_ore::instrument;
48use mz_persist_types::ShardId;
49use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap, merge_mz_acl_items};
50use mz_repr::network_policy_id::NetworkPolicyId;
51use mz_repr::optimize::OptimizerFeatures;
52use mz_repr::role_id::RoleId;
53use mz_repr::{CatalogItemId, ColumnName, GlobalId, SqlColumnType, strconv};
54use mz_sql::ast::RawDataType;
55use mz_sql::catalog::{
56 AutoProvisionSource, CatalogDatabase, CatalogError as SqlCatalogError,
57 CatalogItem as SqlCatalogItem, CatalogRole, CatalogSchema, DefaultPrivilegeAclItem,
58 DefaultPrivilegeObject, PasswordAction, PasswordConfig, RoleAttributesRaw, RoleMembership,
59 RoleVars,
60};
61use mz_sql::names::{
62 CommentObjectId, DatabaseId, FullItemName, ObjectId, QualifiedItemName,
63 ResolvedDatabaseSpecifier, SchemaId, SchemaSpecifier, SystemObjectId,
64};
65use mz_sql::plan::{NetworkPolicyRule, PlanError};
66use mz_sql::session::user::{MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID};
67use mz_sql::session::vars::OwnedVarInput;
68use mz_sql::session::vars::{Value as VarValue, VarInput};
69use mz_sql::{DEFAULT_SCHEMA, rbac};
70use mz_sql_parser::ast::{QualifiedReplica, Value};
71use mz_storage_client::storage_collections::StorageCollections;
72use serde::{Deserialize, Serialize};
73use tracing::{info, trace};
74
75use crate::AdapterError;
76use crate::catalog::state::LocalExpressionCache;
77use crate::catalog::{
78 BuiltinTableUpdate, Catalog, CatalogState, UpdatePrivilegeVariant,
79 catalog_type_to_audit_object_type, comment_id_to_audit_object_type, is_reserved_name,
80 is_reserved_role_name, object_type_to_audit_object_type,
81 system_object_type_to_audit_object_type,
82};
83use crate::config::{ScopedParameters, ScopedParametersScope};
84use crate::coord::ConnMeta;
85use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdate;
86use crate::coord::cluster_scheduling::SchedulingDecision;
87use crate::util::ResultExt;
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct InjectedAuditEvent {
95 pub event_type: EventType,
96 pub object_type: ObjectType,
97 pub details: EventDetails,
98 pub user: Option<String>,
99}
100
101#[derive(Debug, Clone)]
102pub enum Op {
103 AlterRetainHistory {
104 id: CatalogItemId,
105 value: Option<Value>,
106 window: CompactionWindow,
107 },
108 AlterSourceTimestampInterval {
109 id: CatalogItemId,
110 value: Option<Value>,
111 interval: Duration,
112 },
113 AlterRole {
114 id: RoleId,
115 name: String,
116 attributes: RoleAttributesRaw,
117 nopassword: bool,
118 vars: RoleVars,
119 },
120 AlterNetworkPolicy {
121 id: NetworkPolicyId,
122 rules: Vec<NetworkPolicyRule>,
123 name: String,
124 owner_id: RoleId,
125 },
126 AlterAddColumn {
127 id: CatalogItemId,
128 new_global_id: GlobalId,
129 name: ColumnName,
130 typ: SqlColumnType,
131 sql: RawDataType,
132 },
133 AlterMaterializedViewApplyReplacement {
134 id: CatalogItemId,
135 replacement_id: CatalogItemId,
136 },
137 CreateDatabase {
138 name: String,
139 owner_id: RoleId,
140 },
141 CreateSchema {
142 database_id: ResolvedDatabaseSpecifier,
143 schema_name: String,
144 owner_id: RoleId,
145 },
146 CreateRole {
147 name: String,
148 attributes: RoleAttributesRaw,
149 },
150 CreateCluster {
151 id: ClusterId,
152 name: String,
153 introspection_sources: Vec<&'static BuiltinLog>,
154 owner_id: RoleId,
155 config: ClusterConfig,
156 },
157 CreateClusterReplica {
158 cluster_id: ClusterId,
159 replica_id: ReplicaId,
160 name: String,
161 config: ReplicaConfig,
162 owner_id: RoleId,
163 reason: ReplicaCreateDropReason,
164 },
165 CreateItem {
166 id: CatalogItemId,
167 name: QualifiedItemName,
168 item: CatalogItem,
169 owner_id: RoleId,
170 },
171 CreateNetworkPolicy {
172 rules: Vec<NetworkPolicyRule>,
173 name: String,
174 owner_id: RoleId,
175 },
176 Comment {
177 object_id: CommentObjectId,
178 sub_component: Option<usize>,
179 comment: Option<String>,
180 },
181 DropObjects(Vec<DropObjectInfo>),
182 GrantRole {
183 role_id: RoleId,
184 member_id: RoleId,
185 grantor_id: RoleId,
186 },
187 RenameCluster {
188 id: ClusterId,
189 name: String,
190 to_name: String,
191 check_reserved_names: bool,
192 },
193 RenameClusterReplica {
194 cluster_id: ClusterId,
195 replica_id: ReplicaId,
196 name: QualifiedReplica,
197 to_name: String,
198 },
199 RenameItem {
200 id: CatalogItemId,
201 current_full_name: FullItemName,
202 to_name: String,
203 },
204 RenameSchema {
205 database_spec: ResolvedDatabaseSpecifier,
206 schema_spec: SchemaSpecifier,
207 new_name: String,
208 check_reserved_names: bool,
209 },
210 UpdateOwner {
211 id: ObjectId,
212 new_owner: RoleId,
213 },
214 UpdatePrivilege {
215 target_id: SystemObjectId,
216 privileges: Vec<MzAclItem>,
220 variant: UpdatePrivilegeVariant,
221 },
222 UpdateDefaultPrivilege {
223 privilege_object: DefaultPrivilegeObject,
224 privilege_acl_item: DefaultPrivilegeAclItem,
225 variant: UpdatePrivilegeVariant,
226 },
227 RevokeRole {
228 role_id: RoleId,
229 member_id: RoleId,
230 grantor_id: RoleId,
231 },
232 UpdateClusterConfig {
233 id: ClusterId,
234 name: String,
235 config: ClusterConfig,
236 reconfiguration_audit: Option<ReconfigurationAudit>,
239 burst_audit: Option<BurstAudit>,
242 },
243 UpdateClusterReplicaConfig {
244 cluster_id: ClusterId,
245 replica_id: ReplicaId,
246 config: ReplicaConfig,
247 },
248 UpdateItem {
249 id: CatalogItemId,
250 name: QualifiedItemName,
251 to_item: CatalogItem,
252 },
253 UpdateSourceReferences {
254 source_id: CatalogItemId,
255 references: SourceReferences,
256 },
257 UpdateSystemConfiguration {
258 name: String,
259 value: OwnedVarInput,
260 },
261 ResetSystemConfiguration {
262 name: String,
263 },
264 ResetAllSystemConfiguration,
265 UpdateScopedSystemParameters {
277 scoped: ScopedParameters,
278 prune_scope: Option<ScopedParametersScope>,
279 },
280 InjectAuditEvents {
286 events: Vec<InjectedAuditEvent>,
287 },
288 CheckClusterState {
295 cluster_id: ClusterId,
296 expected: ExpectedClusterState,
297 },
298}
299
300#[derive(Debug, Clone)]
304pub enum DropObjectInfo {
305 Cluster(ClusterId),
306 ClusterReplica((ClusterId, ReplicaId, ReplicaCreateDropReason)),
307 Database(DatabaseId),
308 Schema((ResolvedDatabaseSpecifier, SchemaSpecifier)),
309 Role(RoleId),
310 Item(CatalogItemId),
311 NetworkPolicy(NetworkPolicyId),
312}
313
314impl DropObjectInfo {
315 pub(crate) fn manual_drop_from_object_id(id: ObjectId) -> Self {
318 match id {
319 ObjectId::Cluster(cluster_id) => DropObjectInfo::Cluster(cluster_id),
320 ObjectId::ClusterReplica((cluster_id, replica_id)) => DropObjectInfo::ClusterReplica((
321 cluster_id,
322 replica_id,
323 ReplicaCreateDropReason::Manual,
324 )),
325 ObjectId::Database(database_id) => DropObjectInfo::Database(database_id),
326 ObjectId::Schema(schema) => DropObjectInfo::Schema(schema),
327 ObjectId::Role(role_id) => DropObjectInfo::Role(role_id),
328 ObjectId::Item(item_id) => DropObjectInfo::Item(item_id),
329 ObjectId::NetworkPolicy(policy_id) => DropObjectInfo::NetworkPolicy(policy_id),
330 }
331 }
332
333 fn to_object_id(&self) -> ObjectId {
336 match &self {
337 DropObjectInfo::Cluster(cluster_id) => ObjectId::Cluster(cluster_id.clone()),
338 DropObjectInfo::ClusterReplica((cluster_id, replica_id, _reason)) => {
339 ObjectId::ClusterReplica((cluster_id.clone(), replica_id.clone()))
340 }
341 DropObjectInfo::Database(database_id) => ObjectId::Database(database_id.clone()),
342 DropObjectInfo::Schema(schema) => ObjectId::Schema(schema.clone()),
343 DropObjectInfo::Role(role_id) => ObjectId::Role(role_id.clone()),
344 DropObjectInfo::Item(item_id) => ObjectId::Item(item_id.clone()),
345 DropObjectInfo::NetworkPolicy(network_policy_id) => {
346 ObjectId::NetworkPolicy(network_policy_id.clone())
347 }
348 }
349 }
350}
351
352#[derive(Debug, Clone)]
354pub enum ReplicaCreateDropReason {
355 Manual,
360 ClusterScheduling(Vec<SchedulingDecision>),
363 GracefulReconfiguration,
367 Retired,
371}
372
373impl ReplicaCreateDropReason {
374 pub fn into_audit_log(
375 self,
376 ) -> (
377 CreateOrDropClusterReplicaReasonV1,
378 Option<SchedulingDecisionsWithReasonsV2>,
379 ) {
380 let (reason, scheduling_policies) = match self {
381 ReplicaCreateDropReason::Manual => (CreateOrDropClusterReplicaReasonV1::Manual, None),
382 ReplicaCreateDropReason::ClusterScheduling(scheduling_decisions) => (
383 CreateOrDropClusterReplicaReasonV1::Schedule,
384 Some(scheduling_decisions),
385 ),
386 ReplicaCreateDropReason::GracefulReconfiguration => {
387 (CreateOrDropClusterReplicaReasonV1::Reconfiguration, None)
388 }
389 ReplicaCreateDropReason::Retired => (CreateOrDropClusterReplicaReasonV1::Retired, None),
390 };
391 (
392 reason,
393 scheduling_policies
394 .as_ref()
395 .map(SchedulingDecision::reasons_to_audit_log_reasons),
396 )
397 }
398}
399
400pub struct TransactionResult {
401 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
402 pub catalog_updates: Vec<ParsedStateUpdate>,
404 pub audit_events: Vec<VersionedEvent>,
405}
406
407#[derive(Debug, Clone, Copy)]
408enum TransactInnerMode {
409 Commit,
413 DryRun,
420}
421
422impl Catalog {
423 fn should_audit_log_item(item: &CatalogItem) -> bool {
424 !item.is_temporary()
425 }
426
427 fn reconfiguration_record_of(
429 config: &ClusterConfig,
430 ) -> Option<&mz_catalog::memory::objects::ReconfigurationState> {
431 match &config.variant {
432 ClusterVariant::Managed(managed) => managed.reconfiguration.as_ref(),
433 ClusterVariant::Unmanaged => None,
434 }
435 }
436
437 fn reconfiguration_lifecycle_moved(
445 old_config: &ClusterConfig,
446 new_config: &ClusterConfig,
447 ) -> bool {
448 match (
449 Self::reconfiguration_record_of(old_config),
450 Self::reconfiguration_record_of(new_config),
451 ) {
452 (None, Some(_)) => true,
453 (Some(old), Some(new)) => old.status != new.status,
454 (Some(old), None) => old.is_in_progress(),
455 (None, None) => false,
456 }
457 }
458
459 fn reconfiguration_audit_details(
468 config: &ClusterConfig,
469 cluster_id: ClusterId,
470 cluster_name: &str,
471 audit: ReconfigurationAudit,
472 ) -> Result<AlterClusterReconfigurationV1, AdapterError> {
473 let record = Self::reconfiguration_record_of(config);
474 let Some(record) = record else {
475 return Err(AdapterError::Internal(format!(
476 "reconfiguration audit transition {audit:?} for cluster {cluster_name} \
477 without a reconfiguration record"
478 )));
479 };
480 let (transition, forced) = match audit {
484 ReconfigurationAudit::Started => (ReconfigurationLifecycleV1::Started, None),
485 ReconfigurationAudit::Cancelled => (ReconfigurationLifecycleV1::Cancelled, None),
486 ReconfigurationAudit::Finalized { forced } => {
487 (ReconfigurationLifecycleV1::Finalized, Some(forced))
488 }
489 ReconfigurationAudit::TimedOut => (ReconfigurationLifecycleV1::TimedOut, None),
490 ReconfigurationAudit::ResourceExhausted => {
491 (ReconfigurationLifecycleV1::ResourceExhausted, None)
492 }
493 };
494 let coherent = matches!(
495 (audit, record.status),
496 (
497 ReconfigurationAudit::Started,
498 ReconfigurationStatus::InProgress
499 ) | (
500 ReconfigurationAudit::Cancelled,
501 ReconfigurationStatus::Cancelled
502 ) | (
503 ReconfigurationAudit::Finalized { .. },
504 ReconfigurationStatus::Finalized
505 ) | (
506 ReconfigurationAudit::TimedOut,
507 ReconfigurationStatus::TimedOut
508 ) | (
509 ReconfigurationAudit::ResourceExhausted,
510 ReconfigurationStatus::ResourceExhausted
511 )
512 );
513 if !coherent {
514 return Err(AdapterError::Internal(format!(
515 "reconfiguration audit transition {audit:?} for cluster {cluster_name} \
516 contradicts the written record status {:?}",
517 record.status
518 )));
519 }
520 let ReconfigurationState {
521 target,
522 deadline,
523 on_timeout: _,
524 status: _,
525 } = record;
526 let ReconfigurationTarget {
527 size,
528 replication_factor,
529 availability_zones,
530 logging,
531 } = target;
532 Ok(AlterClusterReconfigurationV1 {
533 cluster_id: cluster_id.to_string(),
534 cluster_name: cluster_name.to_string(),
535 transition,
536 forced,
537 target_size: size.clone(),
538 target_replication_factor: *replication_factor,
539 target_availability_zones: availability_zones.clone(),
540 target_logging: ClusterReplicaLoggingV1 {
541 log_logging: logging.log_logging,
542 interval: logging.interval,
543 },
544 deadline: Some((*deadline).into()),
545 })
546 }
547
548 fn burst_audit_details(
557 old_config: &ClusterConfig,
558 new_config: &ClusterConfig,
559 cluster_id: ClusterId,
560 cluster_name: &str,
561 audit: BurstAudit,
562 ) -> Result<ClusterHydrationBurstV1, AdapterError> {
563 fn burst(config: &ClusterConfig) -> Option<&mz_catalog::memory::objects::BurstState> {
564 match &config.variant {
565 ClusterVariant::Managed(managed) => managed.burst.as_ref(),
566 ClusterVariant::Unmanaged => None,
567 }
568 }
569 let (transition, finish_cause, record) = match audit {
570 BurstAudit::Started => (HydrationBurstLifecycleV1::Started, None, burst(new_config)),
571 BurstAudit::Finished { cause } => {
572 let cause = match cause {
573 BurstFinishCause::LingerElapsed => BurstFinishCauseV1::LingerElapsed,
574 BurstFinishCause::NoLongerWarranted => BurstFinishCauseV1::NoLongerWarranted,
575 };
576 (
577 HydrationBurstLifecycleV1::Finished,
578 Some(cause),
579 burst(old_config),
580 )
581 }
582 };
583 let Some(record) = record else {
584 return Err(AdapterError::Internal(format!(
585 "burst audit transition {audit:?} for cluster {cluster_name} \
586 without a burst record on the corresponding side of the write"
587 )));
588 };
589 Ok(ClusterHydrationBurstV1 {
590 cluster_id: cluster_id.to_string(),
591 cluster_name: cluster_name.to_string(),
592 transition,
593 finish_cause,
594 burst_size: record.burst_size.clone(),
595 })
596 }
597
598 fn temporary_ids(
601 &self,
602 ops: &[Op],
603 temporary_drops: BTreeSet<(&ConnectionId, String)>,
604 ) -> Result<BTreeSet<CatalogItemId>, Error> {
605 let mut creating = BTreeSet::new();
606 let mut temporary_ids = BTreeSet::new();
607 for op in ops.iter() {
608 if let Op::CreateItem {
609 id,
610 name,
611 item,
612 owner_id: _,
613 } = op
614 {
615 if let Some(conn_id) = item.conn_id() {
616 if self.item_exists_in_temp_schemas(conn_id, &name.item)
617 && !temporary_drops.contains(&(conn_id, name.item.clone()))
618 || creating.contains(&(conn_id, &name.item))
619 {
620 return Err(
621 SqlCatalogError::ItemAlreadyExists(*id, name.item.clone()).into()
622 );
623 } else {
624 creating.insert((conn_id, &name.item));
625 temporary_ids.insert(id.clone());
626 }
627 }
628 }
629 }
630 Ok(temporary_ids)
631 }
632
633 #[instrument(name = "catalog::transact")]
634 pub async fn transact(
635 &mut self,
636 storage_collections: Option<&mut Arc<dyn StorageCollections + Send + Sync>>,
639 oracle_write_ts: mz_repr::Timestamp,
640 session: Option<&ConnMeta>,
641 ops: Vec<Op>,
642 ) -> Result<TransactionResult, AdapterError> {
643 trace!("transact: {:?}", ops);
644 fail::fail_point!("catalog_transact", |arg| {
645 Err(AdapterError::Unstructured(anyhow::anyhow!(
646 "failpoint: {arg:?}"
647 )))
648 });
649
650 let drop_ids: BTreeSet<CatalogItemId> = ops
651 .iter()
652 .filter_map(|op| match op {
653 Op::DropObjects(drop_object_infos) => {
654 let ids = drop_object_infos.iter().map(|info| info.to_object_id());
655 let item_ids = ids.filter_map(|id| match id {
656 ObjectId::Item(id) => Some(id),
657 _ => None,
658 });
659 Some(item_ids)
660 }
661 _ => None,
662 })
663 .flatten()
664 .collect();
665 let temporary_drops = drop_ids
666 .iter()
667 .filter_map(|id| {
668 let entry = self.get_entry(id);
669 match entry.item().conn_id() {
670 Some(conn_id) => Some((conn_id, entry.name().item.clone())),
671 None => None,
672 }
673 })
674 .collect();
675
676 let temporary_ids = self.temporary_ids(&ops, temporary_drops)?;
677 let mut builtin_table_updates = vec![];
678 let mut catalog_updates = vec![];
679 let mut audit_events = vec![];
680 let mut storage = self.storage().await;
681 let mut tx = storage
682 .transaction()
683 .await
684 .unwrap_or_terminate("starting catalog transaction");
685
686 let new_state = Self::transact_inner(
687 TransactInnerMode::Commit,
688 storage_collections,
689 oracle_write_ts,
690 session,
691 ops,
692 temporary_ids,
693 &mut builtin_table_updates,
694 &mut catalog_updates,
695 &mut audit_events,
696 &mut tx,
697 &self.state,
698 )
699 .await?;
700
701 tx.commit(oracle_write_ts)
706 .await
707 .unwrap_or_terminate("catalog storage transaction commit must succeed");
708
709 drop(storage);
712 if let Some(new_state) = new_state {
713 self.transient_revision += 1;
714 self.shared_transient_revision
720 .store(self.transient_revision, atomic::Ordering::SeqCst);
721 self.state = new_state;
722 }
723
724 Ok(TransactionResult {
725 builtin_table_updates,
726 catalog_updates,
727 audit_events,
728 })
729 }
730
731 pub async fn transact_incremental_dry_run(
746 &self,
747 base_state: &CatalogState,
748 ops: Vec<Op>,
749 session: Option<&ConnMeta>,
750 prev_snapshot: Option<Snapshot>,
751 oracle_write_ts: mz_repr::Timestamp,
752 ) -> Result<(CatalogState, Snapshot), AdapterError> {
753 let temporary_ids = self.temporary_ids(&ops, BTreeSet::new())?;
756
757 let mut builtin_table_updates = vec![];
758 let mut catalog_updates = vec![];
759 let mut audit_events = vec![];
760 let mut storage = self.storage().await;
761 let mut tx = if let Some(snapshot) = prev_snapshot {
762 storage
765 .transaction_from_snapshot(snapshot)
766 .unwrap_or_terminate("starting catalog transaction from snapshot")
767 } else {
768 storage
771 .transaction()
772 .await
773 .unwrap_or_terminate("starting catalog transaction")
774 };
775
776 let new_state = Self::transact_inner(
778 TransactInnerMode::DryRun,
779 None,
780 oracle_write_ts,
781 session,
782 ops,
783 temporary_ids,
784 &mut builtin_table_updates,
785 &mut catalog_updates,
786 &mut audit_events,
787 &mut tx,
788 base_state,
789 )
790 .await?;
791
792 let new_snapshot = tx.current_snapshot();
795
796 drop(storage);
798
799 let state = new_state.unwrap_or_else(|| base_state.clone());
801 Ok((state, new_snapshot))
802 }
803
804 fn extract_expressions_from_ops(
808 ops: &[Op],
809 optimizer_features: &OptimizerFeatures,
810 ) -> BTreeMap<GlobalId, LocalExpressions> {
811 let mut exprs = BTreeMap::new();
812
813 for op in ops {
814 if let Op::CreateItem { item, .. } = op {
815 match item {
816 CatalogItem::View(view) => {
817 exprs.insert(
818 view.global_id,
819 LocalExpressions {
820 local_mir: (*view.locally_optimized_expr).clone(),
821 optimizer_features: optimizer_features.clone(),
822 },
823 );
824 }
825 CatalogItem::MaterializedView(mv) => {
826 exprs.insert(
827 mv.global_id_writes(),
828 LocalExpressions {
829 local_mir: (*mv.locally_optimized_expr).clone(),
830 optimizer_features: optimizer_features.clone(),
831 },
832 );
833 }
834 CatalogItem::Table(_)
835 | CatalogItem::Source(_)
836 | CatalogItem::Log(_)
837 | CatalogItem::Sink(_)
838 | CatalogItem::Index(_)
839 | CatalogItem::Type(_)
840 | CatalogItem::Func(_)
841 | CatalogItem::Secret(_)
842 | CatalogItem::Connection(_) => {}
843 }
844 }
845 }
846
847 exprs
848 }
849
850 #[instrument(name = "catalog::transact_inner")]
858 async fn transact_inner(
859 mode: TransactInnerMode,
860 storage_collections: Option<&mut Arc<dyn StorageCollections + Send + Sync>>,
861 oracle_write_ts: mz_repr::Timestamp,
862 session: Option<&ConnMeta>,
863 ops: Vec<Op>,
864 temporary_ids: BTreeSet<CatalogItemId>,
865 builtin_table_updates: &mut Vec<BuiltinTableUpdate>,
866 parsed_catalog_updates: &mut Vec<ParsedStateUpdate>,
867 audit_events: &mut Vec<VersionedEvent>,
868 tx: &mut Transaction<'_>,
869 state: &CatalogState,
870 ) -> Result<Option<CatalogState>, AdapterError> {
871 let mut preliminary_state = Cow::Borrowed(state);
898
899 let mut state = Cow::Borrowed(state);
901
902 if ops.is_empty() {
903 return Ok(None);
904 }
905
906 let optimizer_features = OptimizerFeatures::from(state.system_config());
909 let cached_exprs = Self::extract_expressions_from_ops(&ops, &optimizer_features);
910
911 let mut storage_collections_to_create = BTreeSet::new();
912 let mut storage_collections_to_drop = BTreeSet::new();
913 let mut storage_collections_to_register = BTreeMap::new();
914
915 let mut updates = Vec::new();
916
917 for op in ops {
918 let temporary_item_updates = Self::transact_op(
919 oracle_write_ts,
920 session,
921 op,
922 &temporary_ids,
923 audit_events,
924 tx,
925 &*preliminary_state,
926 &mut storage_collections_to_create,
927 &mut storage_collections_to_drop,
928 &mut storage_collections_to_register,
929 )
930 .await?;
931
932 let upper = tx.upper();
937 let temporary_item_updates =
938 temporary_item_updates
939 .into_iter()
940 .map(|(item, diff)| StateUpdate {
941 kind: StateUpdateKind::TemporaryItem(item),
942 ts: upper,
943 diff,
944 });
945
946 let mut op_updates: Vec<_> = tx.get_and_commit_op_updates();
947 op_updates.extend(temporary_item_updates);
948 if !op_updates.is_empty() {
949 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
952 let (_op_builtin_table_updates, _op_catalog_updates) = preliminary_state
953 .to_mut()
954 .apply_updates(op_updates.clone(), &mut local_expr_cache)
955 .await;
956 }
957 updates.append(&mut op_updates);
958 }
959
960 if !updates.is_empty() {
961 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
962 let (op_builtin_table_updates, op_catalog_updates) = state
963 .to_mut()
964 .apply_updates(updates.clone(), &mut local_expr_cache)
965 .await;
966 let op_builtin_table_updates = state
967 .to_mut()
968 .resolve_builtin_table_updates(op_builtin_table_updates);
969 builtin_table_updates.extend(op_builtin_table_updates);
970 parsed_catalog_updates.extend(op_catalog_updates);
971 }
972
973 match mode {
974 TransactInnerMode::Commit => {
975 if let Some(c) = storage_collections {
977 c.prepare_state(
978 tx,
979 storage_collections_to_create,
980 storage_collections_to_drop,
981 storage_collections_to_register,
982 )
983 .await?;
984 }
985 }
986 TransactInnerMode::DryRun => {
987 debug_assert!(
988 storage_collections.is_none(),
989 "dry-run mode must not prepare storage state"
990 );
991 }
992 }
993
994 let updates = tx.get_and_commit_op_updates();
995 if !updates.is_empty() {
996 let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone());
997 let (op_builtin_table_updates, op_catalog_updates) = state
998 .to_mut()
999 .apply_updates(updates.clone(), &mut local_expr_cache)
1000 .await;
1001 let op_builtin_table_updates = state
1002 .to_mut()
1003 .resolve_builtin_table_updates(op_builtin_table_updates);
1004 builtin_table_updates.extend(op_builtin_table_updates);
1005 parsed_catalog_updates.extend(op_catalog_updates);
1006 }
1007
1008 match state {
1009 Cow::Owned(state) => Ok(Some(state)),
1010 Cow::Borrowed(_) => Ok(None),
1011 }
1012 }
1013
1014 #[instrument]
1022 async fn transact_op(
1023 oracle_write_ts: mz_repr::Timestamp,
1024 session: Option<&ConnMeta>,
1025 op: Op,
1026 temporary_ids: &BTreeSet<CatalogItemId>,
1027 audit_events: &mut Vec<VersionedEvent>,
1028 tx: &mut Transaction<'_>,
1029 state: &CatalogState,
1030 storage_collections_to_create: &mut BTreeSet<GlobalId>,
1031 storage_collections_to_drop: &mut BTreeSet<GlobalId>,
1032 storage_collections_to_register: &mut BTreeMap<GlobalId, ShardId>,
1033 ) -> Result<Vec<(TemporaryItem, StateDiff)>, AdapterError> {
1034 let mut temporary_item_updates = Vec::new();
1035
1036 match op {
1037 Op::CheckClusterState {
1038 cluster_id,
1039 expected,
1040 } => {
1041 if !crate::catalog::cluster_state::cluster_matches_expected(
1045 state, cluster_id, &expected,
1046 ) {
1047 return Err(AdapterError::ClusterStateChanged { cluster_id });
1048 }
1049 }
1050 Op::AlterRetainHistory { id, value, window } => {
1051 let entry = state.get_entry(&id);
1052 if id.is_system() {
1053 let name = entry.name();
1054 let full_name =
1055 state.resolve_full_name(name, session.map(|session| session.conn_id()));
1056 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
1057 full_name.to_string(),
1058 ))));
1059 }
1060
1061 let mut new_entry = entry.clone();
1062 let previous = new_entry
1063 .item
1064 .update_retain_history(value.clone(), window)
1065 .map_err(|_| {
1066 AdapterError::Catalog(Error::new(ErrorKind::Internal(
1067 "planner should have rejected invalid alter retain history item type"
1068 .to_string(),
1069 )))
1070 })?;
1071
1072 if Self::should_audit_log_item(new_entry.item()) {
1073 let details =
1074 EventDetails::AlterRetainHistoryV1(mz_audit_log::AlterRetainHistoryV1 {
1075 id: id.to_string(),
1076 old_history: previous.map(|previous| previous.to_string()),
1077 new_history: value.map(|v| v.to_string()),
1078 });
1079 CatalogState::add_to_audit_log(
1080 &state.system_configuration,
1081 oracle_write_ts,
1082 session,
1083 tx,
1084 audit_events,
1085 EventType::Alter,
1086 catalog_type_to_audit_object_type(new_entry.item().typ()),
1087 details,
1088 )?;
1089 }
1090
1091 tx.update_item(id, new_entry.into())?;
1092
1093 Self::log_update(state, &id);
1094 }
1095 Op::AlterSourceTimestampInterval {
1096 id,
1097 value,
1098 interval,
1099 } => {
1100 let entry = state.get_entry(&id);
1101 if id.is_system() {
1102 let name = entry.name();
1103 let full_name =
1104 state.resolve_full_name(name, session.map(|session| session.conn_id()));
1105 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
1106 full_name.to_string(),
1107 ))));
1108 }
1109
1110 let mut new_entry = entry.clone();
1111 let previous = new_entry
1112 .item
1113 .update_timestamp_interval(value.clone(), interval)
1114 .map_err(|_| {
1115 AdapterError::Catalog(Error::new(ErrorKind::Internal(
1116 "planner should have rejected invalid alter timestamp interval item type"
1117 .to_string(),
1118 )))
1119 })?;
1120
1121 if Self::should_audit_log_item(new_entry.item()) {
1122 let details = EventDetails::AlterSourceTimestampIntervalV1(
1123 mz_audit_log::AlterSourceTimestampIntervalV1 {
1124 id: id.to_string(),
1125 old_interval: previous.map(|previous| previous.to_string()),
1126 new_interval: value.map(|v| v.to_string()),
1127 },
1128 );
1129 CatalogState::add_to_audit_log(
1130 &state.system_configuration,
1131 oracle_write_ts,
1132 session,
1133 tx,
1134 audit_events,
1135 EventType::Alter,
1136 catalog_type_to_audit_object_type(new_entry.item().typ()),
1137 details,
1138 )?;
1139 }
1140
1141 tx.update_item(id, new_entry.into())?;
1142
1143 Self::log_update(state, &id);
1144 }
1145 Op::AlterRole {
1146 id,
1147 name,
1148 attributes,
1149 nopassword,
1150 vars,
1151 } => {
1152 state.ensure_not_reserved_role(&id)?;
1153
1154 let mut existing_role = state.get_role(&id).clone();
1155 let password = attributes.password.clone();
1156 let scram_iterations = attributes
1157 .scram_iterations
1158 .unwrap_or_else(|| state.system_config().scram_iterations());
1159 existing_role.attributes = attributes.into();
1160 existing_role.vars = vars;
1161 let password_action = if nopassword {
1162 PasswordAction::Clear
1163 } else if let Some(password) = password {
1164 PasswordAction::Set(PasswordConfig {
1165 password,
1166 scram_iterations,
1167 })
1168 } else {
1169 PasswordAction::NoChange
1170 };
1171 tx.update_role(id, existing_role.into(), password_action)?;
1172
1173 CatalogState::add_to_audit_log(
1174 &state.system_configuration,
1175 oracle_write_ts,
1176 session,
1177 tx,
1178 audit_events,
1179 EventType::Alter,
1180 ObjectType::Role,
1181 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1182 id: id.to_string(),
1183 name: name.clone(),
1184 }),
1185 )?;
1186
1187 info!("update role {name} ({id})");
1188 }
1189 Op::AlterNetworkPolicy {
1190 id,
1191 rules,
1192 name,
1193 owner_id: _owner_id,
1194 } => {
1195 let existing_policy = state.get_network_policy(&id).clone();
1196 let mut policy: NetworkPolicy = existing_policy.into();
1197 policy.rules = rules;
1198 if is_reserved_name(&name) {
1199 return Err(AdapterError::Catalog(Error::new(
1200 ErrorKind::ReservedNetworkPolicyName(name),
1201 )));
1202 }
1203 tx.update_network_policy(id, policy.clone())?;
1204
1205 CatalogState::add_to_audit_log(
1206 &state.system_configuration,
1207 oracle_write_ts,
1208 session,
1209 tx,
1210 audit_events,
1211 EventType::Alter,
1212 ObjectType::NetworkPolicy,
1213 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1214 id: id.to_string(),
1215 name: name.clone(),
1216 }),
1217 )?;
1218
1219 info!("update network policy {name} ({id})");
1220 }
1221 Op::AlterAddColumn {
1222 id,
1223 new_global_id,
1224 name,
1225 typ,
1226 sql,
1227 } => {
1228 let column_name = name.to_string();
1229 let column_type = typ.to_string();
1230 let nullable = typ.nullable;
1231 let mut new_entry = state.get_entry(&id).clone();
1232 let version = new_entry.item.add_column(name, typ, sql)?;
1233 let shard_id = state
1236 .storage_metadata()
1237 .get_collection_shard(new_entry.latest_global_id())?;
1238
1239 let CatalogItem::Table(table) = &mut new_entry.item else {
1241 return Err(AdapterError::Unsupported("adding columns to non-Table"));
1242 };
1243 table.collections.insert(version, new_global_id);
1244
1245 if Self::should_audit_log_item(new_entry.item()) {
1246 let details = EventDetails::AlterAddColumnV1(mz_audit_log::AlterAddColumnV1 {
1247 id: id.to_string(),
1248 column: column_name,
1249 column_type,
1250 nullable,
1251 });
1252 CatalogState::add_to_audit_log(
1253 &state.system_configuration,
1254 oracle_write_ts,
1255 session,
1256 tx,
1257 audit_events,
1258 EventType::Alter,
1259 catalog_type_to_audit_object_type(new_entry.item().typ()),
1260 details,
1261 )?;
1262 }
1263
1264 tx.update_item(id, new_entry.into())?;
1265 storage_collections_to_register.insert(new_global_id, shard_id);
1266 }
1267 Op::AlterMaterializedViewApplyReplacement { id, replacement_id } => {
1268 let mut new_entry = state.get_entry(&id).clone();
1269 let replacement = state.get_entry(&replacement_id);
1270
1271 let new_audit_events =
1272 apply_replacement_audit_events(state, &new_entry, replacement);
1273
1274 let CatalogItem::MaterializedView(mv) = &mut new_entry.item else {
1275 return Err(AdapterError::internal(
1276 "ALTER MATERIALIZED VIEW ... APPLY REPLACEMENT",
1277 "id must refer to a materialized view",
1278 ));
1279 };
1280 let CatalogItem::MaterializedView(replacement_mv) = &replacement.item else {
1281 return Err(AdapterError::internal(
1282 "ALTER MATERIALIZED VIEW ... APPLY REPLACEMENT",
1283 "replacement_id must refer to a materialized view",
1284 ));
1285 };
1286
1287 mv.apply_replacement(replacement_mv.clone());
1288
1289 tx.remove_item(replacement_id)?;
1290
1291 new_entry.id = replacement_id;
1292 tx_replace_item(tx, state, id, new_entry, &mut temporary_item_updates)?;
1293
1294 let comment_id = CommentObjectId::MaterializedView(replacement_id);
1295 tx.drop_comments(&[comment_id].into())?;
1296
1297 for (event_type, details) in new_audit_events {
1298 CatalogState::add_to_audit_log(
1299 &state.system_configuration,
1300 oracle_write_ts,
1301 session,
1302 tx,
1303 audit_events,
1304 event_type,
1305 ObjectType::MaterializedView,
1306 details,
1307 )?;
1308 }
1309 }
1310 Op::CreateDatabase { name, owner_id } => {
1311 let database_owner_privileges = vec![rbac::owner_privilege(
1312 mz_sql::catalog::ObjectType::Database,
1313 owner_id,
1314 )];
1315 let database_default_privileges = state
1316 .default_privileges
1317 .get_applicable_privileges(
1318 owner_id,
1319 None,
1320 None,
1321 mz_sql::catalog::ObjectType::Database,
1322 )
1323 .map(|item| item.mz_acl_item(owner_id));
1324 let database_privileges: Vec<_> = merge_mz_acl_items(
1325 database_owner_privileges
1326 .into_iter()
1327 .chain(database_default_privileges),
1328 )
1329 .collect();
1330
1331 let schema_owner_privileges = vec![rbac::owner_privilege(
1332 mz_sql::catalog::ObjectType::Schema,
1333 owner_id,
1334 )];
1335 let schema_default_privileges = state
1336 .default_privileges
1337 .get_applicable_privileges(
1338 owner_id,
1339 None,
1340 None,
1341 mz_sql::catalog::ObjectType::Schema,
1342 )
1343 .map(|item| item.mz_acl_item(owner_id))
1344 .chain(std::iter::once(MzAclItem {
1346 grantee: RoleId::Public,
1347 grantor: owner_id,
1348 acl_mode: AclMode::USAGE,
1349 }));
1350 let schema_privileges: Vec<_> = merge_mz_acl_items(
1351 schema_owner_privileges
1352 .into_iter()
1353 .chain(schema_default_privileges),
1354 )
1355 .collect();
1356
1357 let temporary_oids: HashSet<_> = state.get_temporary_oids().collect();
1358 let (database_id, _) = tx.insert_user_database(
1359 &name,
1360 owner_id,
1361 database_privileges.clone(),
1362 &temporary_oids,
1363 )?;
1364 let (schema_id, _) = tx.insert_user_schema(
1365 database_id,
1366 DEFAULT_SCHEMA,
1367 owner_id,
1368 schema_privileges.clone(),
1369 &temporary_oids,
1370 )?;
1371 CatalogState::add_to_audit_log(
1372 &state.system_configuration,
1373 oracle_write_ts,
1374 session,
1375 tx,
1376 audit_events,
1377 EventType::Create,
1378 ObjectType::Database,
1379 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1380 id: database_id.to_string(),
1381 name: name.clone(),
1382 }),
1383 )?;
1384 info!("create database {}", name);
1385
1386 CatalogState::add_to_audit_log(
1387 &state.system_configuration,
1388 oracle_write_ts,
1389 session,
1390 tx,
1391 audit_events,
1392 EventType::Create,
1393 ObjectType::Schema,
1394 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
1395 id: schema_id.to_string(),
1396 name: DEFAULT_SCHEMA.to_string(),
1397 database_name: Some(name),
1398 }),
1399 )?;
1400 }
1401 Op::CreateSchema {
1402 database_id,
1403 schema_name,
1404 owner_id,
1405 } => {
1406 if is_reserved_name(&schema_name) {
1407 return Err(AdapterError::Catalog(Error::new(
1408 ErrorKind::ReservedSchemaName(schema_name),
1409 )));
1410 }
1411 let database_id = match database_id {
1412 ResolvedDatabaseSpecifier::Id(id) => id,
1413 ResolvedDatabaseSpecifier::Ambient => {
1414 return Err(AdapterError::Catalog(Error::new(
1415 ErrorKind::ReadOnlySystemSchema(schema_name),
1416 )));
1417 }
1418 };
1419 let owner_privileges = vec![rbac::owner_privilege(
1420 mz_sql::catalog::ObjectType::Schema,
1421 owner_id,
1422 )];
1423 let default_privileges = state
1424 .default_privileges
1425 .get_applicable_privileges(
1426 owner_id,
1427 Some(database_id),
1428 None,
1429 mz_sql::catalog::ObjectType::Schema,
1430 )
1431 .map(|item| item.mz_acl_item(owner_id));
1432 let privileges: Vec<_> =
1433 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1434 .collect();
1435 let (schema_id, _) = tx.insert_user_schema(
1436 database_id,
1437 &schema_name,
1438 owner_id,
1439 privileges.clone(),
1440 &state.get_temporary_oids().collect(),
1441 )?;
1442 CatalogState::add_to_audit_log(
1443 &state.system_configuration,
1444 oracle_write_ts,
1445 session,
1446 tx,
1447 audit_events,
1448 EventType::Create,
1449 ObjectType::Schema,
1450 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
1451 id: schema_id.to_string(),
1452 name: schema_name.clone(),
1453 database_name: Some(state.database_by_id[&database_id].name.clone()),
1454 }),
1455 )?;
1456 }
1457 Op::CreateRole { name, attributes } => {
1458 if is_reserved_role_name(&name) {
1459 return Err(AdapterError::Catalog(Error::new(
1460 ErrorKind::ReservedRoleName(name),
1461 )));
1462 }
1463 let membership = RoleMembership::new();
1464 let vars = RoleVars::default();
1465 let (id, _) = tx.insert_user_role(
1466 name.clone(),
1467 attributes.clone(),
1468 membership.clone(),
1469 vars.clone(),
1470 &state.get_temporary_oids().collect(),
1471 )?;
1472 CatalogState::add_to_audit_log(
1473 &state.system_configuration,
1474 oracle_write_ts,
1475 session,
1476 tx,
1477 audit_events,
1478 EventType::Create,
1479 ObjectType::Role,
1480 EventDetails::CreateRoleV1(mz_audit_log::CreateRoleV1 {
1481 id: id.to_string(),
1482 name: name.clone(),
1483 auto_provision_source: attributes.auto_provision_source.map(|s| match s {
1484 AutoProvisionSource::Oidc => "oidc".to_string(),
1485 AutoProvisionSource::Frontegg => "frontegg".to_string(),
1486 AutoProvisionSource::None => "none".to_string(),
1487 }),
1488 }),
1489 )?;
1490 info!("create role {}", name);
1491 }
1492 Op::CreateCluster {
1493 id,
1494 name,
1495 introspection_sources,
1496 owner_id,
1497 config,
1498 } => {
1499 if is_reserved_name(&name) {
1500 return Err(AdapterError::Catalog(Error::new(
1501 ErrorKind::ReservedClusterName(name),
1502 )));
1503 }
1504 let owner_privileges = vec![rbac::owner_privilege(
1505 mz_sql::catalog::ObjectType::Cluster,
1506 owner_id,
1507 )];
1508 let default_privileges = state
1509 .default_privileges
1510 .get_applicable_privileges(
1511 owner_id,
1512 None,
1513 None,
1514 mz_sql::catalog::ObjectType::Cluster,
1515 )
1516 .map(|item| item.mz_acl_item(owner_id));
1517 let privileges: Vec<_> =
1518 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1519 .collect();
1520 let introspection_source_ids: Vec<_> = introspection_sources
1521 .iter()
1522 .map(|introspection_source| {
1523 Transaction::allocate_introspection_source_index_id(
1524 &id,
1525 introspection_source.variant,
1526 )
1527 })
1528 .collect();
1529
1530 let introspection_sources = introspection_sources
1531 .into_iter()
1532 .zip_eq(introspection_source_ids)
1533 .map(|(log, (item_id, gid))| (log, item_id, gid))
1534 .collect();
1535
1536 tx.insert_user_cluster(
1537 id,
1538 &name,
1539 introspection_sources,
1540 owner_id,
1541 privileges.clone(),
1542 config.clone().into(),
1543 &state.get_temporary_oids().collect(),
1544 )?;
1545 CatalogState::add_to_audit_log(
1546 &state.system_configuration,
1547 oracle_write_ts,
1548 session,
1549 tx,
1550 audit_events,
1551 EventType::Create,
1552 ObjectType::Cluster,
1553 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1554 id: id.to_string(),
1555 name: name.clone(),
1556 }),
1557 )?;
1558 info!("create cluster {}", name);
1559 }
1560 Op::CreateClusterReplica {
1561 cluster_id,
1562 replica_id,
1563 name,
1564 config,
1565 owner_id,
1566 reason,
1567 } => {
1568 if is_reserved_name(&name) {
1569 return Err(AdapterError::Catalog(Error::new(
1570 ErrorKind::ReservedReplicaName(name),
1571 )));
1572 }
1573 let cluster = state.get_cluster(cluster_id);
1574 tx.insert_cluster_replica_with_id(
1578 cluster_id,
1579 replica_id,
1580 &name,
1581 config.clone().into(),
1582 owner_id,
1583 )?;
1584 if let ReplicaLocation::Managed(ManagedReplicaLocation {
1585 size,
1586 billed_as,
1587 internal,
1588 ..
1589 }) = &config.location
1590 {
1591 let (reason, scheduling_policies) = reason.into_audit_log();
1592 let details = EventDetails::CreateClusterReplicaV4(
1593 mz_audit_log::CreateClusterReplicaV4 {
1594 cluster_id: cluster_id.to_string(),
1595 cluster_name: cluster.name.clone(),
1596 replica_id: Some(replica_id.to_string()),
1597 replica_name: name.clone(),
1598 logical_size: size.clone(),
1599 billed_as: billed_as.clone(),
1600 internal: *internal,
1601 reason,
1602 scheduling_policies,
1603 },
1604 );
1605 CatalogState::add_to_audit_log(
1606 &state.system_configuration,
1607 oracle_write_ts,
1608 session,
1609 tx,
1610 audit_events,
1611 EventType::Create,
1612 ObjectType::ClusterReplica,
1613 details,
1614 )?;
1615 }
1616 }
1617 Op::CreateItem {
1618 id,
1619 name,
1620 item,
1621 owner_id,
1622 } => {
1623 state.check_unstable_dependencies(&item)?;
1624
1625 match &item {
1626 CatalogItem::Table(table) => {
1627 let gids: Vec<_> = table.global_ids().collect();
1628 assert_eq!(gids.len(), 1);
1629 storage_collections_to_create.extend(gids);
1630 }
1631 CatalogItem::Source(source) => {
1632 storage_collections_to_create.insert(source.global_id());
1633 }
1634 CatalogItem::MaterializedView(mv) => {
1635 let mv_gid = mv.global_id_writes();
1636 if let Some(target_id) = mv.replacement_target {
1637 let target_gid = state.get_entry(&target_id).latest_global_id();
1638 let shard_id =
1639 state.storage_metadata().get_collection_shard(target_gid)?;
1640 storage_collections_to_register.insert(mv_gid, shard_id);
1641 } else {
1642 storage_collections_to_create.insert(mv_gid);
1643 }
1644 }
1645 CatalogItem::Sink(sink) => {
1646 storage_collections_to_create.insert(sink.global_id());
1647 }
1648 CatalogItem::Log(_)
1649 | CatalogItem::View(_)
1650 | CatalogItem::Index(_)
1651 | CatalogItem::Type(_)
1652 | CatalogItem::Func(_)
1653 | CatalogItem::Secret(_)
1654 | CatalogItem::Connection(_) => (),
1655 }
1656
1657 let system_user = session.map_or(false, |s| s.user().is_system_user());
1658 if !system_user {
1659 if let Some(id @ ClusterId::System(_)) = item.cluster_id() {
1660 let cluster_name = state.clusters_by_id[&id].name.clone();
1661 return Err(AdapterError::Catalog(Error::new(
1662 ErrorKind::ReadOnlyCluster(cluster_name),
1663 )));
1664 }
1665 }
1666
1667 let owner_privileges = vec![rbac::owner_privilege(item.typ().into(), owner_id)];
1668 let default_privileges = state
1669 .default_privileges
1670 .get_applicable_privileges(
1671 owner_id,
1672 name.qualifiers.database_spec.id(),
1673 Some(name.qualifiers.schema_spec.into()),
1674 item.typ().into(),
1675 )
1676 .map(|item| item.mz_acl_item(owner_id));
1677 let progress_source_privilege = if item.is_progress_source() {
1679 Some(MzAclItem {
1680 grantee: MZ_SUPPORT_ROLE_ID,
1681 grantor: owner_id,
1682 acl_mode: AclMode::SELECT,
1683 })
1684 } else {
1685 None
1686 };
1687 let privileges: Vec<_> = merge_mz_acl_items(
1688 owner_privileges
1689 .into_iter()
1690 .chain(default_privileges)
1691 .chain(progress_source_privilege),
1692 )
1693 .collect();
1694
1695 let temporary_oids = state.get_temporary_oids().collect();
1696
1697 if item.is_temporary() {
1698 if name.qualifiers.database_spec != ResolvedDatabaseSpecifier::Ambient
1699 || name.qualifiers.schema_spec != SchemaSpecifier::Temporary
1700 {
1701 return Err(AdapterError::Catalog(Error::new(
1702 ErrorKind::InvalidTemporarySchema,
1703 )));
1704 }
1705 let oid = tx.allocate_oid(&temporary_oids)?;
1706
1707 let schema_id = name.qualifiers.schema_spec.clone().into();
1708 let item_type = item.typ();
1709 let (create_sql, global_id, versions) = item.to_serialized();
1710
1711 let item = TemporaryItem {
1712 id,
1713 oid,
1714 global_id,
1715 schema_id,
1716 name: name.item.clone(),
1717 create_sql,
1718 conn_id: item.conn_id().cloned(),
1719 owner_id,
1720 privileges: privileges.clone(),
1721 extra_versions: versions,
1722 };
1723 temporary_item_updates.push((item, StateDiff::Addition));
1724
1725 info!(
1726 "create temporary {} {} ({})",
1727 item_type,
1728 state.resolve_full_name(&name, None),
1729 id
1730 );
1731 } else {
1732 if let Some(temp_id) =
1733 item.uses()
1734 .iter()
1735 .find(|id| match state.try_get_entry(*id) {
1736 Some(entry) => entry.item().is_temporary(),
1737 None => temporary_ids.contains(id),
1738 })
1739 {
1740 let temp_item = state.get_entry(temp_id);
1741 return Err(AdapterError::Catalog(Error::new(
1742 ErrorKind::InvalidTemporaryDependency(temp_item.name().item.clone()),
1743 )));
1744 }
1745 if name.qualifiers.database_spec == ResolvedDatabaseSpecifier::Ambient
1746 && !system_user
1747 {
1748 let schema_name = state
1749 .resolve_full_name(&name, session.map(|session| session.conn_id()))
1750 .schema;
1751 return Err(AdapterError::Catalog(Error::new(
1752 ErrorKind::ReadOnlySystemSchema(schema_name),
1753 )));
1754 }
1755 let schema_id = name.qualifiers.schema_spec.clone().into();
1756 let item_type = item.typ();
1757 let (create_sql, global_id, versions) = item.to_serialized();
1758 tx.insert_user_item(
1759 id,
1760 global_id,
1761 schema_id,
1762 &name.item,
1763 create_sql,
1764 owner_id,
1765 privileges.clone(),
1766 &temporary_oids,
1767 versions,
1768 )?;
1769 info!(
1770 "create {} {} ({})",
1771 item_type,
1772 state.resolve_full_name(&name, None),
1773 id
1774 );
1775 }
1776
1777 if Self::should_audit_log_item(&item) {
1778 let name = Self::full_name_detail(
1779 &state.resolve_full_name(&name, session.map(|session| session.conn_id())),
1780 );
1781 let details = match &item {
1782 CatalogItem::Source(s) => {
1783 let cluster_id = match s.data_source {
1784 DataSourceDesc::IngestionExport { ingestion_id, .. } => {
1787 match state.get_entry(&ingestion_id).cluster_id() {
1788 Some(cluster_id) => Some(cluster_id.to_string()),
1789 None => None,
1790 }
1791 }
1792 _ => match item.cluster_id() {
1793 Some(cluster_id) => Some(cluster_id.to_string()),
1794 None => None,
1795 },
1796 };
1797
1798 EventDetails::CreateSourceSinkV4(mz_audit_log::CreateSourceSinkV4 {
1799 id: id.to_string(),
1800 cluster_id,
1801 name,
1802 external_type: s.source_type().to_string(),
1803 })
1804 }
1805 CatalogItem::Sink(s) => {
1806 EventDetails::CreateSourceSinkV4(mz_audit_log::CreateSourceSinkV4 {
1807 id: id.to_string(),
1808 cluster_id: Some(s.cluster_id.to_string()),
1809 name,
1810 external_type: s.sink_type().to_string(),
1811 })
1812 }
1813 CatalogItem::Index(i) => {
1814 EventDetails::CreateIndexV1(mz_audit_log::CreateIndexV1 {
1815 id: id.to_string(),
1816 name,
1817 cluster_id: i.cluster_id.to_string(),
1818 })
1819 }
1820 CatalogItem::MaterializedView(mv) => {
1821 EventDetails::CreateMaterializedViewV1(
1822 mz_audit_log::CreateMaterializedViewV1 {
1823 id: id.to_string(),
1824 name,
1825 cluster_id: mv.cluster_id.to_string(),
1826 replacement_target_id: mv
1827 .replacement_target
1828 .map(|id| id.to_string()),
1829 },
1830 )
1831 }
1832 CatalogItem::Table(_)
1833 | CatalogItem::Log(_)
1834 | CatalogItem::View(_)
1835 | CatalogItem::Type(_)
1836 | CatalogItem::Func(_)
1837 | CatalogItem::Secret(_)
1838 | CatalogItem::Connection(_) => EventDetails::IdFullNameV1(IdFullNameV1 {
1839 id: id.to_string(),
1840 name,
1841 }),
1842 };
1843 CatalogState::add_to_audit_log(
1844 &state.system_configuration,
1845 oracle_write_ts,
1846 session,
1847 tx,
1848 audit_events,
1849 EventType::Create,
1850 catalog_type_to_audit_object_type(item.typ()),
1851 details,
1852 )?;
1853 }
1854 }
1855 Op::CreateNetworkPolicy {
1856 rules,
1857 name,
1858 owner_id,
1859 } => {
1860 if state.network_policies_by_name.contains_key(&name) {
1861 return Err(AdapterError::PlanError(PlanError::Catalog(
1862 SqlCatalogError::NetworkPolicyAlreadyExists(name),
1863 )));
1864 }
1865 if is_reserved_name(&name) {
1866 return Err(AdapterError::Catalog(Error::new(
1867 ErrorKind::ReservedNetworkPolicyName(name),
1868 )));
1869 }
1870
1871 let owner_privileges = vec![rbac::owner_privilege(
1872 mz_sql::catalog::ObjectType::NetworkPolicy,
1873 owner_id,
1874 )];
1875 let default_privileges = state
1876 .default_privileges
1877 .get_applicable_privileges(
1878 owner_id,
1879 None,
1880 None,
1881 mz_sql::catalog::ObjectType::NetworkPolicy,
1882 )
1883 .map(|item| item.mz_acl_item(owner_id));
1884 let privileges: Vec<_> =
1885 merge_mz_acl_items(owner_privileges.into_iter().chain(default_privileges))
1886 .collect();
1887
1888 let temporary_oids: HashSet<_> = state.get_temporary_oids().collect();
1889 let id = tx.insert_user_network_policy(
1890 name.clone(),
1891 rules,
1892 privileges,
1893 owner_id,
1894 &temporary_oids,
1895 )?;
1896
1897 CatalogState::add_to_audit_log(
1898 &state.system_configuration,
1899 oracle_write_ts,
1900 session,
1901 tx,
1902 audit_events,
1903 EventType::Create,
1904 ObjectType::NetworkPolicy,
1905 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1906 id: id.to_string(),
1907 name: name.clone(),
1908 }),
1909 )?;
1910
1911 info!("created network policy {name} ({id})");
1912 }
1913 Op::Comment {
1914 object_id,
1915 sub_component,
1916 comment,
1917 } => {
1918 tx.update_comment(object_id, sub_component, comment)?;
1919 let entry = state.get_comment_id_entry(&object_id);
1920 let should_log = entry
1921 .map(|entry| Self::should_audit_log_item(entry.item()))
1922 .unwrap_or(true);
1924 if let (Some(conn_id), true) =
1927 (session.map(|session| session.conn_id()), should_log)
1928 {
1929 CatalogState::add_to_audit_log(
1930 &state.system_configuration,
1931 oracle_write_ts,
1932 session,
1933 tx,
1934 audit_events,
1935 EventType::Comment,
1936 comment_id_to_audit_object_type(object_id),
1937 EventDetails::IdNameV1(IdNameV1 {
1938 id: format!("{object_id:?}"),
1940 name: state.comment_id_to_audit_log_name(object_id, conn_id),
1941 }),
1942 )?;
1943 }
1944 }
1945 Op::UpdateSourceReferences {
1946 source_id,
1947 references,
1948 } => {
1949 tx.update_source_references(
1950 source_id,
1951 references
1952 .references
1953 .into_iter()
1954 .map(|reference| reference.into())
1955 .collect(),
1956 references.updated_at,
1957 )?;
1958 }
1959 Op::DropObjects(drop_object_infos) => {
1960 let delta = ObjectsToDrop::generate(drop_object_infos, state, session)?;
1962
1963 tx.drop_comments(&delta.comments)?;
1965
1966 let (durable_items_to_drop, temporary_items_to_drop): (BTreeSet<_>, BTreeSet<_>) =
1968 delta
1969 .items
1970 .iter()
1971 .map(|id| id)
1972 .partition(|id| !state.get_entry(*id).item().is_temporary());
1973 tx.remove_items(&durable_items_to_drop)?;
1974 temporary_item_updates.extend(temporary_items_to_drop.into_iter().map(|id| {
1975 let entry = state.get_entry(&id);
1976 (entry.clone().into(), StateDiff::Retraction)
1977 }));
1978
1979 for item_id in delta.items {
1980 let entry = state.get_entry(&item_id);
1981
1982 if entry.item().is_storage_collection() {
1983 storage_collections_to_drop.extend(entry.global_ids());
1984 }
1985
1986 if state.source_references.contains_key(&item_id) {
1987 tx.remove_source_references(item_id)?;
1988 }
1989
1990 if Self::should_audit_log_item(entry.item()) {
1991 CatalogState::add_to_audit_log(
1992 &state.system_configuration,
1993 oracle_write_ts,
1994 session,
1995 tx,
1996 audit_events,
1997 EventType::Drop,
1998 catalog_type_to_audit_object_type(entry.item().typ()),
1999 EventDetails::IdFullNameV1(IdFullNameV1 {
2000 id: item_id.to_string(),
2001 name: Self::full_name_detail(&state.resolve_full_name(
2002 entry.name(),
2003 session.map(|session| session.conn_id()),
2004 )),
2005 }),
2006 )?;
2007 }
2008 info!(
2009 "drop {} {} ({})",
2010 entry.item_type(),
2011 state.resolve_full_name(entry.name(), entry.conn_id()),
2012 item_id
2013 );
2014 }
2015
2016 let schemas = delta
2018 .schemas
2019 .iter()
2020 .map(|(schema_spec, database_spec)| {
2021 (SchemaId::from(schema_spec), *database_spec)
2022 })
2023 .collect();
2024 tx.remove_schemas(&schemas)?;
2025
2026 for (schema_spec, database_spec) in delta.schemas {
2027 let schema = state.get_schema(
2028 &database_spec,
2029 &schema_spec,
2030 session
2031 .map(|session| session.conn_id())
2032 .unwrap_or(&SYSTEM_CONN_ID),
2033 );
2034
2035 let schema_id = SchemaId::from(schema_spec);
2036 let database_id = match database_spec {
2037 ResolvedDatabaseSpecifier::Ambient => None,
2038 ResolvedDatabaseSpecifier::Id(database_id) => Some(database_id),
2039 };
2040
2041 CatalogState::add_to_audit_log(
2042 &state.system_configuration,
2043 oracle_write_ts,
2044 session,
2045 tx,
2046 audit_events,
2047 EventType::Drop,
2048 ObjectType::Schema,
2049 EventDetails::SchemaV2(mz_audit_log::SchemaV2 {
2050 id: schema_id.to_string(),
2051 name: schema.name.schema.to_string(),
2052 database_name: database_id
2053 .map(|database_id| state.database_by_id[&database_id].name.clone()),
2054 }),
2055 )?;
2056 }
2057
2058 tx.remove_databases(&delta.databases)?;
2060
2061 for database_id in delta.databases {
2062 let database = state.get_database(&database_id).clone();
2063
2064 CatalogState::add_to_audit_log(
2065 &state.system_configuration,
2066 oracle_write_ts,
2067 session,
2068 tx,
2069 audit_events,
2070 EventType::Drop,
2071 ObjectType::Database,
2072 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2073 id: database_id.to_string(),
2074 name: database.name.clone(),
2075 }),
2076 )?;
2077 }
2078
2079 tx.remove_user_roles(&delta.roles)?;
2081
2082 for role_id in delta.roles {
2083 let role = state
2084 .roles_by_id
2085 .get(&role_id)
2086 .expect("catalog out of sync");
2087
2088 CatalogState::add_to_audit_log(
2089 &state.system_configuration,
2090 oracle_write_ts,
2091 session,
2092 tx,
2093 audit_events,
2094 EventType::Drop,
2095 ObjectType::Role,
2096 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2097 id: role.id.to_string(),
2098 name: role.name.clone(),
2099 }),
2100 )?;
2101 info!("drop role {}", role.name());
2102 }
2103
2104 tx.remove_network_policies(&delta.network_policies)?;
2106
2107 for network_policy_id in delta.network_policies {
2108 let policy = state
2109 .network_policies_by_id
2110 .get(&network_policy_id)
2111 .expect("catalog out of sync");
2112
2113 CatalogState::add_to_audit_log(
2114 &state.system_configuration,
2115 oracle_write_ts,
2116 session,
2117 tx,
2118 audit_events,
2119 EventType::Drop,
2120 ObjectType::NetworkPolicy,
2121 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2122 id: policy.id.to_string(),
2123 name: policy.name.clone(),
2124 }),
2125 )?;
2126 info!("drop network policy {}", policy.name.clone());
2127 }
2128
2129 let replicas = delta.replicas.keys().copied().collect();
2131 tx.remove_cluster_replicas(&replicas)?;
2132
2133 for (replica_id, (cluster_id, reason)) in delta.replicas {
2134 let cluster = state.get_cluster(cluster_id);
2135 let replica = cluster.replica(replica_id).expect("Must exist");
2136
2137 let (reason, scheduling_policies) = reason.into_audit_log();
2138 let details =
2139 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
2140 cluster_id: cluster_id.to_string(),
2141 cluster_name: cluster.name.clone(),
2142 replica_id: Some(replica_id.to_string()),
2143 replica_name: replica.name.clone(),
2144 reason,
2145 scheduling_policies,
2146 });
2147 CatalogState::add_to_audit_log(
2148 &state.system_configuration,
2149 oracle_write_ts,
2150 session,
2151 tx,
2152 audit_events,
2153 EventType::Drop,
2154 ObjectType::ClusterReplica,
2155 details,
2156 )?;
2157 }
2158
2159 tx.remove_clusters(&delta.clusters)?;
2161
2162 for cluster_id in delta.clusters {
2163 let cluster = state.get_cluster(cluster_id);
2164
2165 CatalogState::add_to_audit_log(
2166 &state.system_configuration,
2167 oracle_write_ts,
2168 session,
2169 tx,
2170 audit_events,
2171 EventType::Drop,
2172 ObjectType::Cluster,
2173 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2174 id: cluster.id.to_string(),
2175 name: cluster.name.clone(),
2176 }),
2177 )?;
2178 }
2179 }
2180 Op::GrantRole {
2181 role_id,
2182 member_id,
2183 grantor_id,
2184 } => {
2185 state.ensure_not_reserved_role(&member_id)?;
2186 state.ensure_grantable_role(&role_id)?;
2187 if state.collect_role_membership(&role_id).contains(&member_id) {
2188 let group_role = state.get_role(&role_id);
2189 let member_role = state.get_role(&member_id);
2190 return Err(AdapterError::Catalog(Error::new(
2191 ErrorKind::CircularRoleMembership {
2192 role_name: group_role.name().to_string(),
2193 member_name: member_role.name().to_string(),
2194 },
2195 )));
2196 }
2197 let mut member_role = state.get_role(&member_id).clone();
2198 member_role.membership.map.insert(role_id, grantor_id);
2199 tx.update_role(member_id, member_role.into(), PasswordAction::NoChange)?;
2200
2201 CatalogState::add_to_audit_log(
2202 &state.system_configuration,
2203 oracle_write_ts,
2204 session,
2205 tx,
2206 audit_events,
2207 EventType::Grant,
2208 ObjectType::Role,
2209 EventDetails::GrantRoleV2(mz_audit_log::GrantRoleV2 {
2210 role_id: role_id.to_string(),
2211 member_id: member_id.to_string(),
2212 grantor_id: grantor_id.to_string(),
2213 executed_by: session
2214 .map(|session| session.authenticated_role_id())
2215 .unwrap_or(&MZ_SYSTEM_ROLE_ID)
2216 .to_string(),
2217 }),
2218 )?;
2219 }
2220 Op::RevokeRole {
2221 role_id,
2222 member_id,
2223 grantor_id,
2224 } => {
2225 state.ensure_not_reserved_role(&member_id)?;
2226 state.ensure_grantable_role(&role_id)?;
2227 let mut member_role = state.get_role(&member_id).clone();
2228 member_role.membership.map.remove(&role_id);
2229 tx.update_role(member_id, member_role.into(), PasswordAction::NoChange)?;
2230
2231 CatalogState::add_to_audit_log(
2232 &state.system_configuration,
2233 oracle_write_ts,
2234 session,
2235 tx,
2236 audit_events,
2237 EventType::Revoke,
2238 ObjectType::Role,
2239 EventDetails::RevokeRoleV2(mz_audit_log::RevokeRoleV2 {
2240 role_id: role_id.to_string(),
2241 member_id: member_id.to_string(),
2242 grantor_id: grantor_id.to_string(),
2243 executed_by: session
2244 .map(|session| session.authenticated_role_id())
2245 .unwrap_or(&MZ_SYSTEM_ROLE_ID)
2246 .to_string(),
2247 }),
2248 )?;
2249 }
2250 Op::UpdatePrivilege {
2251 target_id,
2252 privileges,
2253 variant,
2254 } => {
2255 let update_privilege_fn = |target_privileges: &mut PrivilegeMap| {
2256 for privilege in &privileges {
2257 match variant {
2258 UpdatePrivilegeVariant::Grant => {
2259 target_privileges.grant(privilege.clone());
2260 }
2261 UpdatePrivilegeVariant::Revoke => {
2262 target_privileges.revoke(privilege);
2263 }
2264 }
2265 }
2266 };
2267 match &target_id {
2268 SystemObjectId::Object(object_id) => match object_id {
2269 ObjectId::Cluster(id) => {
2270 let mut cluster = state.get_cluster(*id).clone();
2271 update_privilege_fn(&mut cluster.privileges);
2272 tx.update_cluster(*id, cluster.into())?;
2273 }
2274 ObjectId::Database(id) => {
2275 let mut database = state.get_database(id).clone();
2276 update_privilege_fn(&mut database.privileges);
2277 tx.update_database(*id, database.into())?;
2278 }
2279 ObjectId::NetworkPolicy(id) => {
2280 let mut policy = state.get_network_policy(id).clone();
2281 update_privilege_fn(&mut policy.privileges);
2282 tx.update_network_policy(*id, policy.into())?;
2283 }
2284 ObjectId::Schema((database_spec, schema_spec)) => {
2285 let schema_id = schema_spec.clone().into();
2286 let mut schema = state
2287 .get_schema(
2288 database_spec,
2289 schema_spec,
2290 session
2291 .map(|session| session.conn_id())
2292 .unwrap_or(&SYSTEM_CONN_ID),
2293 )
2294 .clone();
2295 update_privilege_fn(&mut schema.privileges);
2296 tx.update_schema(schema_id, schema.into())?;
2297 }
2298 ObjectId::Item(id) => {
2299 let entry = state.get_entry(id);
2300 let mut new_entry = entry.clone();
2301 update_privilege_fn(&mut new_entry.privileges);
2302 if !new_entry.item().is_temporary() {
2303 tx.update_item(*id, new_entry.into())?;
2304 } else {
2305 temporary_item_updates
2306 .push((entry.clone().into(), StateDiff::Retraction));
2307 temporary_item_updates
2308 .push((new_entry.into(), StateDiff::Addition));
2309 }
2310 }
2311 ObjectId::Role(_) | ObjectId::ClusterReplica(_) => {}
2312 },
2313 SystemObjectId::System => {
2314 let mut system_privileges = PrivilegeMap::clone(&state.system_privileges);
2315 update_privilege_fn(&mut system_privileges);
2316 for privilege in &privileges {
2317 let new_privilege = system_privileges
2318 .get_acl_item(&privilege.grantee, &privilege.grantor);
2319 tx.set_system_privilege(
2320 privilege.grantee,
2321 privilege.grantor,
2322 new_privilege.map(|new_privilege| new_privilege.acl_mode),
2323 )?;
2324 }
2325 }
2326 }
2327 let object_type = state.get_system_object_type(&target_id);
2328 let object_id_str = match &target_id {
2329 SystemObjectId::System => "SYSTEM".to_string(),
2330 SystemObjectId::Object(id) => id.to_string(),
2331 };
2332 for privilege in &privileges {
2334 CatalogState::add_to_audit_log(
2335 &state.system_configuration,
2336 oracle_write_ts,
2337 session,
2338 tx,
2339 audit_events,
2340 variant.into(),
2341 system_object_type_to_audit_object_type(&object_type),
2342 EventDetails::UpdatePrivilegeV1(mz_audit_log::UpdatePrivilegeV1 {
2343 object_id: object_id_str.clone(),
2344 grantee_id: privilege.grantee.to_string(),
2345 grantor_id: privilege.grantor.to_string(),
2346 privileges: privilege.acl_mode.to_string(),
2347 }),
2348 )?;
2349 }
2350 }
2351 Op::UpdateDefaultPrivilege {
2352 privilege_object,
2353 privilege_acl_item,
2354 variant,
2355 } => {
2356 let mut default_privileges = DefaultPrivileges::clone(&state.default_privileges);
2357 match variant {
2358 UpdatePrivilegeVariant::Grant => default_privileges
2359 .grant(privilege_object.clone(), privilege_acl_item.clone()),
2360 UpdatePrivilegeVariant::Revoke => {
2361 default_privileges.revoke(&privilege_object, &privilege_acl_item)
2362 }
2363 }
2364 let new_acl_mode = default_privileges
2365 .get_privileges_for_grantee(&privilege_object, &privilege_acl_item.grantee);
2366 tx.set_default_privilege(
2367 privilege_object.role_id,
2368 privilege_object.database_id,
2369 privilege_object.schema_id,
2370 privilege_object.object_type,
2371 privilege_acl_item.grantee,
2372 new_acl_mode.cloned(),
2373 )?;
2374 CatalogState::add_to_audit_log(
2375 &state.system_configuration,
2376 oracle_write_ts,
2377 session,
2378 tx,
2379 audit_events,
2380 variant.into(),
2381 object_type_to_audit_object_type(privilege_object.object_type),
2382 EventDetails::AlterDefaultPrivilegeV1(mz_audit_log::AlterDefaultPrivilegeV1 {
2383 role_id: privilege_object.role_id.to_string(),
2384 database_id: privilege_object.database_id.map(|id| id.to_string()),
2385 schema_id: privilege_object.schema_id.map(|id| id.to_string()),
2386 grantee_id: privilege_acl_item.grantee.to_string(),
2387 privileges: privilege_acl_item.acl_mode.to_string(),
2388 }),
2389 )?;
2390 }
2391 Op::RenameCluster {
2392 id,
2393 name,
2394 to_name,
2395 check_reserved_names,
2396 } => {
2397 if id.is_system() {
2398 return Err(AdapterError::Catalog(Error::new(
2399 ErrorKind::ReadOnlyCluster(name.clone()),
2400 )));
2401 }
2402 if check_reserved_names && is_reserved_name(&to_name) {
2403 return Err(AdapterError::Catalog(Error::new(
2404 ErrorKind::ReservedClusterName(to_name),
2405 )));
2406 }
2407 tx.rename_cluster(id, &name, &to_name)?;
2408 CatalogState::add_to_audit_log(
2409 &state.system_configuration,
2410 oracle_write_ts,
2411 session,
2412 tx,
2413 audit_events,
2414 EventType::Alter,
2415 ObjectType::Cluster,
2416 EventDetails::RenameClusterV1(mz_audit_log::RenameClusterV1 {
2417 id: id.to_string(),
2418 old_name: name.clone(),
2419 new_name: to_name.clone(),
2420 }),
2421 )?;
2422 info!("rename cluster {name} to {to_name}");
2423 }
2424 Op::RenameClusterReplica {
2425 cluster_id,
2426 replica_id,
2427 name,
2428 to_name,
2429 } => {
2430 if is_reserved_name(&to_name) {
2431 return Err(AdapterError::Catalog(Error::new(
2432 ErrorKind::ReservedReplicaName(to_name),
2433 )));
2434 }
2435 tx.rename_cluster_replica(replica_id, &name, &to_name)?;
2436 CatalogState::add_to_audit_log(
2437 &state.system_configuration,
2438 oracle_write_ts,
2439 session,
2440 tx,
2441 audit_events,
2442 EventType::Alter,
2443 ObjectType::ClusterReplica,
2444 EventDetails::RenameClusterReplicaV1(mz_audit_log::RenameClusterReplicaV1 {
2445 cluster_id: cluster_id.to_string(),
2446 replica_id: replica_id.to_string(),
2447 old_name: name.replica.as_str().to_string(),
2448 new_name: to_name.clone(),
2449 }),
2450 )?;
2451 info!("rename cluster replica {name} to {to_name}");
2452 }
2453 Op::RenameItem {
2454 id,
2455 to_name,
2456 current_full_name,
2457 } => {
2458 let mut updates = Vec::new();
2459
2460 let entry = state.get_entry(&id);
2461 if let CatalogItem::Type(_) = entry.item() {
2462 return Err(AdapterError::Catalog(Error::new(ErrorKind::TypeRename(
2463 current_full_name.to_string(),
2464 ))));
2465 }
2466
2467 if entry.id().is_system() {
2468 let name = state
2469 .resolve_full_name(entry.name(), session.map(|session| session.conn_id()));
2470 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
2471 name.to_string(),
2472 ))));
2473 }
2474
2475 let mut to_full_name = current_full_name.clone();
2476 to_full_name.item.clone_from(&to_name);
2477
2478 let mut to_qualified_name = entry.name().clone();
2479 to_qualified_name.item.clone_from(&to_name);
2480
2481 let details = EventDetails::RenameItemV1(mz_audit_log::RenameItemV1 {
2482 id: id.to_string(),
2483 old_name: Self::full_name_detail(¤t_full_name),
2484 new_name: Self::full_name_detail(&to_full_name),
2485 });
2486 if Self::should_audit_log_item(entry.item()) {
2487 CatalogState::add_to_audit_log(
2488 &state.system_configuration,
2489 oracle_write_ts,
2490 session,
2491 tx,
2492 audit_events,
2493 EventType::Alter,
2494 catalog_type_to_audit_object_type(entry.item().typ()),
2495 details,
2496 )?;
2497 }
2498
2499 let mut new_entry = entry.clone();
2501 new_entry.name.item.clone_from(&to_name);
2502 new_entry.item = entry
2503 .item()
2504 .rename_item_refs(current_full_name.clone(), to_full_name.item.clone(), true)
2505 .map_err(|e| {
2506 Error::new(ErrorKind::from(AmbiguousRename {
2507 depender: state
2508 .resolve_full_name(entry.name(), entry.conn_id())
2509 .to_string(),
2510 dependee: state
2511 .resolve_full_name(entry.name(), entry.conn_id())
2512 .to_string(),
2513 message: e,
2514 }))
2515 })?;
2516
2517 for id in entry.referenced_by() {
2518 let dependent_item = state.get_entry(id);
2519 let mut to_entry = dependent_item.clone();
2520 to_entry.item = dependent_item
2521 .item()
2522 .rename_item_refs(
2523 current_full_name.clone(),
2524 to_full_name.item.clone(),
2525 false,
2526 )
2527 .map_err(|e| {
2528 Error::new(ErrorKind::from(AmbiguousRename {
2529 depender: state
2530 .resolve_full_name(
2531 dependent_item.name(),
2532 dependent_item.conn_id(),
2533 )
2534 .to_string(),
2535 dependee: state
2536 .resolve_full_name(entry.name(), entry.conn_id())
2537 .to_string(),
2538 message: e,
2539 }))
2540 })?;
2541
2542 if !to_entry.item().is_temporary() {
2543 tx.update_item(*id, to_entry.into())?;
2544 } else {
2545 temporary_item_updates
2546 .push((dependent_item.clone().into(), StateDiff::Retraction));
2547 temporary_item_updates.push((to_entry.into(), StateDiff::Addition));
2548 }
2549 updates.push(*id);
2550 }
2551 if !new_entry.item().is_temporary() {
2552 tx.update_item(id, new_entry.into())?;
2553 } else {
2554 temporary_item_updates.push((entry.clone().into(), StateDiff::Retraction));
2555 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2556 }
2557
2558 updates.push(id);
2559 for id in updates {
2560 Self::log_update(state, &id);
2561 }
2562 }
2563 Op::RenameSchema {
2564 database_spec,
2565 schema_spec,
2566 new_name,
2567 check_reserved_names,
2568 } => {
2569 if check_reserved_names && is_reserved_name(&new_name) {
2570 return Err(AdapterError::Catalog(Error::new(
2571 ErrorKind::ReservedSchemaName(new_name),
2572 )));
2573 }
2574
2575 let conn_id = session
2576 .map(|session| session.conn_id())
2577 .unwrap_or(&SYSTEM_CONN_ID);
2578
2579 let schema = state.get_schema(&database_spec, &schema_spec, conn_id);
2580 let cur_name = schema.name().schema.clone();
2581
2582 let ResolvedDatabaseSpecifier::Id(database_id) = database_spec else {
2583 return Err(AdapterError::Catalog(Error::new(
2584 ErrorKind::AmbientSchemaRename(cur_name),
2585 )));
2586 };
2587 let database = state.get_database(&database_id);
2588 let database_name = &database.name;
2589
2590 let mut updates: Vec<CatalogItemId> = Vec::new();
2591 let mut items_to_update = BTreeMap::new();
2592 let mut seen: BTreeSet<CatalogItemId> = BTreeSet::new();
2603
2604 let mut update_item = |id: &CatalogItemId| {
2605 if !seen.insert(*id) {
2606 return Ok(());
2607 }
2608
2609 let entry = state.get_entry(id);
2610
2611 let mut new_entry = entry.clone();
2613 new_entry.item = entry
2614 .item
2615 .rename_schema_refs(database_name, &cur_name, &new_name)
2616 .map_err(|(s, _i)| {
2617 Error::new(ErrorKind::from(AmbiguousRename {
2618 depender: state
2619 .resolve_full_name(entry.name(), entry.conn_id())
2620 .to_string(),
2621 dependee: format!("{database_name}.{cur_name}"),
2622 message: format!("ambiguous reference to schema named {s}"),
2623 }))
2624 })?;
2625
2626 if !new_entry.item().is_temporary() {
2628 items_to_update.insert(*id, new_entry.into());
2629 } else {
2630 temporary_item_updates.push((entry.clone().into(), StateDiff::Retraction));
2631 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2632 }
2633 updates.push(*id);
2634
2635 Ok::<_, AdapterError>(())
2636 };
2637
2638 for (_name, item_id) in schema
2643 .items
2644 .iter()
2645 .chain(schema.types.iter())
2646 .chain(schema.functions.iter())
2647 {
2648 update_item(item_id)?;
2650
2651 for id in state.get_entry(item_id).referenced_by() {
2653 update_item(id)?;
2654 }
2655 }
2656 tx.update_items(items_to_update)?;
2659
2660 let SchemaSpecifier::Id(schema_id) = *schema.id() else {
2662 let schema_name = schema.name().schema.clone();
2663 return Err(AdapterError::Catalog(crate::catalog::Error::new(
2664 crate::catalog::ErrorKind::ReadOnlySystemSchema(schema_name),
2665 )));
2666 };
2667
2668 let database_name = database_spec
2670 .id()
2671 .map(|id| state.get_database(&id).name.clone());
2672 let details = EventDetails::RenameSchemaV1(mz_audit_log::RenameSchemaV1 {
2673 id: schema_id.to_string(),
2674 old_name: schema.name().schema.clone(),
2675 new_name: new_name.clone(),
2676 database_name,
2677 });
2678 CatalogState::add_to_audit_log(
2679 &state.system_configuration,
2680 oracle_write_ts,
2681 session,
2682 tx,
2683 audit_events,
2684 EventType::Alter,
2685 mz_audit_log::ObjectType::Schema,
2686 details,
2687 )?;
2688
2689 let mut new_schema = schema.clone();
2691 new_schema.name.schema.clone_from(&new_name);
2692 tx.update_schema(schema_id, new_schema.into())?;
2693
2694 for id in updates {
2695 Self::log_update(state, &id);
2696 }
2697 }
2698 Op::UpdateOwner { id, new_owner } => {
2699 let conn_id = session
2700 .map(|session| session.conn_id())
2701 .unwrap_or(&SYSTEM_CONN_ID);
2702 let old_owner = state
2703 .get_owner_id(&id, conn_id)
2704 .expect("cannot update the owner of an object without an owner");
2705 match &id {
2706 ObjectId::Cluster(id) => {
2707 let mut cluster = state.get_cluster(*id).clone();
2708 if id.is_system() {
2709 return Err(AdapterError::Catalog(Error::new(
2710 ErrorKind::ReadOnlyCluster(cluster.name),
2711 )));
2712 }
2713 Self::update_privilege_owners(
2714 &mut cluster.privileges,
2715 cluster.owner_id,
2716 new_owner,
2717 );
2718 cluster.owner_id = new_owner;
2719 tx.update_cluster(*id, cluster.into())?;
2720 }
2721 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
2722 let cluster = state.get_cluster(*cluster_id);
2723 let mut replica = cluster
2724 .replica(*replica_id)
2725 .expect("catalog out of sync")
2726 .clone();
2727 if replica_id.is_system() {
2728 return Err(AdapterError::Catalog(Error::new(
2729 ErrorKind::ReadOnlyClusterReplica(replica.name),
2730 )));
2731 }
2732 replica.owner_id = new_owner;
2733 tx.update_cluster_replica(*replica_id, replica.into())?;
2734 }
2735 ObjectId::Database(id) => {
2736 let mut database = state.get_database(id).clone();
2737 if id.is_system() {
2738 return Err(AdapterError::Catalog(Error::new(
2739 ErrorKind::ReadOnlyDatabase(database.name),
2740 )));
2741 }
2742 Self::update_privilege_owners(
2743 &mut database.privileges,
2744 database.owner_id,
2745 new_owner,
2746 );
2747 database.owner_id = new_owner;
2748 tx.update_database(*id, database.clone().into())?;
2749 }
2750 ObjectId::Schema((database_spec, schema_spec)) => {
2751 let schema_id: SchemaId = schema_spec.clone().into();
2752 let mut schema = state
2753 .get_schema(database_spec, schema_spec, conn_id)
2754 .clone();
2755 if schema_id.is_system() {
2756 let name = schema.name();
2757 let full_name = state.resolve_full_schema_name(name);
2758 return Err(AdapterError::Catalog(Error::new(
2759 ErrorKind::ReadOnlySystemSchema(full_name.to_string()),
2760 )));
2761 }
2762 Self::update_privilege_owners(
2763 &mut schema.privileges,
2764 schema.owner_id,
2765 new_owner,
2766 );
2767 schema.owner_id = new_owner;
2768 tx.update_schema(schema_id, schema.into())?;
2769 }
2770 ObjectId::Item(id) => {
2771 let entry = state.get_entry(id);
2772 let mut new_entry = entry.clone();
2773 if id.is_system() {
2774 let full_name = state.resolve_full_name(
2775 new_entry.name(),
2776 session.map(|session| session.conn_id()),
2777 );
2778 return Err(AdapterError::Catalog(Error::new(
2779 ErrorKind::ReadOnlyItem(full_name.to_string()),
2780 )));
2781 }
2782 Self::update_privilege_owners(
2783 &mut new_entry.privileges,
2784 new_entry.owner_id,
2785 new_owner,
2786 );
2787 new_entry.owner_id = new_owner;
2788 if !new_entry.item().is_temporary() {
2789 tx.update_item(*id, new_entry.into())?;
2790 } else {
2791 temporary_item_updates
2792 .push((entry.clone().into(), StateDiff::Retraction));
2793 temporary_item_updates.push((new_entry.into(), StateDiff::Addition));
2794 }
2795 }
2796 ObjectId::NetworkPolicy(id) => {
2797 let mut policy = state.get_network_policy(id).clone();
2798 if id.is_system() {
2799 return Err(AdapterError::Catalog(Error::new(
2800 ErrorKind::ReadOnlyNetworkPolicy(policy.name),
2801 )));
2802 }
2803 Self::update_privilege_owners(
2804 &mut policy.privileges,
2805 policy.owner_id,
2806 new_owner,
2807 );
2808 policy.owner_id = new_owner;
2809 tx.update_network_policy(*id, policy.into())?;
2810 }
2811 ObjectId::Role(_) => unreachable!("roles have no owner"),
2812 }
2813 let object_type = state.get_object_type(&id);
2814 CatalogState::add_to_audit_log(
2815 &state.system_configuration,
2816 oracle_write_ts,
2817 session,
2818 tx,
2819 audit_events,
2820 EventType::Alter,
2821 object_type_to_audit_object_type(object_type),
2822 EventDetails::UpdateOwnerV1(mz_audit_log::UpdateOwnerV1 {
2823 object_id: id.to_string(),
2824 old_owner_id: old_owner.to_string(),
2825 new_owner_id: new_owner.to_string(),
2826 }),
2827 )?;
2828 }
2829 Op::UpdateClusterConfig {
2830 id,
2831 name,
2832 config,
2833 reconfiguration_audit,
2834 burst_audit,
2835 } => {
2836 let mut cluster = state.get_cluster(id).clone();
2837 if reconfiguration_audit.is_none()
2842 && Self::reconfiguration_lifecycle_moved(&cluster.config, &config)
2843 {
2844 return Err(AdapterError::Internal(format!(
2845 "cluster {name} reconfiguration record moved without a declared \
2846 audit intent"
2847 )));
2848 }
2849 let reconfiguration_event = reconfiguration_audit
2850 .map(|audit| Self::reconfiguration_audit_details(&config, id, &name, audit))
2851 .transpose()?;
2852 let burst_event = burst_audit
2853 .map(|audit| {
2854 Self::burst_audit_details(&cluster.config, &config, id, &name, audit)
2855 })
2856 .transpose()?;
2857 cluster.config = config;
2858 tx.update_cluster(id, cluster.into())?;
2859 info!("update cluster {}", name);
2860
2861 CatalogState::add_to_audit_log(
2862 &state.system_configuration,
2863 oracle_write_ts,
2864 session,
2865 tx,
2866 audit_events,
2867 EventType::Alter,
2868 ObjectType::Cluster,
2869 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
2870 id: id.to_string(),
2871 name,
2872 }),
2873 )?;
2874
2875 if let Some(details) = reconfiguration_event {
2876 CatalogState::add_to_audit_log(
2877 &state.system_configuration,
2878 oracle_write_ts,
2879 session,
2880 tx,
2881 audit_events,
2882 EventType::Alter,
2883 ObjectType::Cluster,
2884 EventDetails::AlterClusterReconfigurationV1(details),
2885 )?;
2886 }
2887
2888 if let Some(details) = burst_event {
2889 CatalogState::add_to_audit_log(
2890 &state.system_configuration,
2891 oracle_write_ts,
2892 session,
2893 tx,
2894 audit_events,
2895 EventType::Alter,
2896 ObjectType::Cluster,
2897 EventDetails::ClusterHydrationBurstV1(details),
2898 )?;
2899 }
2900 }
2901 Op::UpdateClusterReplicaConfig {
2902 replica_id,
2903 cluster_id,
2904 config,
2905 } => {
2906 let replica = state.get_cluster_replica(cluster_id, replica_id).to_owned();
2907 info!("update replica {}", replica.name);
2908 tx.update_cluster_replica(
2909 replica_id,
2910 mz_catalog::durable::ClusterReplica {
2911 cluster_id,
2912 replica_id,
2913 name: replica.name.clone(),
2914 config: config.clone().into(),
2915 owner_id: replica.owner_id,
2916 },
2917 )?;
2918 }
2919 Op::UpdateItem { id, name, to_item } => {
2920 if !to_item.is_temporary() {
2929 if let Some(temp_id) =
2930 to_item
2931 .uses()
2932 .iter()
2933 .find(|id| match state.try_get_entry(*id) {
2934 Some(entry) => entry.item().is_temporary(),
2935 None => temporary_ids.contains(id),
2936 })
2937 {
2938 let temp_item = state.get_entry(temp_id);
2939 return Err(AdapterError::Catalog(Error::new(
2940 ErrorKind::InvalidTemporaryDependency(temp_item.name().item.clone()),
2941 )));
2942 }
2943 }
2944
2945 let mut entry = state.get_entry(&id).clone();
2946 entry.name = name.clone();
2947 entry.item = to_item.clone();
2948 tx.update_item(id, entry.into())?;
2949
2950 if Self::should_audit_log_item(&to_item) {
2951 let mut full_name = Self::full_name_detail(
2952 &state.resolve_full_name(&name, session.map(|session| session.conn_id())),
2953 );
2954 full_name.item = name.item;
2955
2956 CatalogState::add_to_audit_log(
2957 &state.system_configuration,
2958 oracle_write_ts,
2959 session,
2960 tx,
2961 audit_events,
2962 EventType::Alter,
2963 catalog_type_to_audit_object_type(to_item.typ()),
2964 EventDetails::UpdateItemV1(mz_audit_log::UpdateItemV1 {
2965 id: id.to_string(),
2966 name: full_name,
2967 }),
2968 )?;
2969 }
2970
2971 Self::log_update(state, &id);
2972 }
2973 Op::UpdateSystemConfiguration { name, value } => {
2974 let parsed_value = state.parse_system_configuration(&name, value.borrow())?;
2975 tx.upsert_system_config(&name, parsed_value.clone())?;
2976 if name == WITH_0DT_DEPLOYMENT_MAX_WAIT.name() {
2981 let with_0dt_deployment_max_wait =
2982 Duration::parse(VarInput::Flat(&parsed_value))
2983 .expect("parsing succeeded above");
2984 tx.set_0dt_deployment_max_wait(with_0dt_deployment_max_wait)?;
2985 } else if name == WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.name() {
2986 let with_0dt_deployment_ddl_check_interval =
2987 Duration::parse(VarInput::Flat(&parsed_value))
2988 .expect("parsing succeeded above");
2989 tx.set_0dt_deployment_ddl_check_interval(
2990 with_0dt_deployment_ddl_check_interval,
2991 )?;
2992 } else if name == ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.name() {
2993 let panic_after_timeout =
2994 strconv::parse_bool(&parsed_value).expect("parsing succeeded above");
2995 tx.set_enable_0dt_deployment_panic_after_timeout(panic_after_timeout)?;
2996 }
2997
2998 CatalogState::add_to_audit_log(
2999 &state.system_configuration,
3000 oracle_write_ts,
3001 session,
3002 tx,
3003 audit_events,
3004 EventType::Alter,
3005 ObjectType::System,
3006 EventDetails::SetV1(mz_audit_log::SetV1 {
3007 name,
3008 value: Some(value.borrow().to_vec().join(", ")),
3009 }),
3010 )?;
3011 }
3012 Op::ResetSystemConfiguration { name } => {
3013 tx.remove_system_config(&name);
3014 if name == WITH_0DT_DEPLOYMENT_MAX_WAIT.name() {
3019 tx.reset_0dt_deployment_max_wait()?;
3020 } else if name == WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.name() {
3021 tx.reset_0dt_deployment_ddl_check_interval()?;
3022 } else if name == ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.name() {
3023 tx.reset_enable_0dt_deployment_panic_after_timeout()?;
3024 }
3025
3026 CatalogState::add_to_audit_log(
3027 &state.system_configuration,
3028 oracle_write_ts,
3029 session,
3030 tx,
3031 audit_events,
3032 EventType::Alter,
3033 ObjectType::System,
3034 EventDetails::SetV1(mz_audit_log::SetV1 { name, value: None }),
3035 )?;
3036 }
3037 Op::ResetAllSystemConfiguration => {
3038 tx.clear_system_configs();
3039 tx.reset_0dt_deployment_max_wait()?;
3040 tx.reset_0dt_deployment_ddl_check_interval()?;
3041 tx.reset_enable_0dt_deployment_panic_after_timeout()?;
3042
3043 CatalogState::add_to_audit_log(
3044 &state.system_configuration,
3045 oracle_write_ts,
3046 session,
3047 tx,
3048 audit_events,
3049 EventType::Alter,
3050 ObjectType::System,
3051 EventDetails::ResetAllV1,
3052 )?;
3053 }
3054 Op::UpdateScopedSystemParameters {
3055 scoped,
3056 prune_scope,
3057 } => {
3058 let live_clusters: BTreeSet<ClusterId> =
3071 tx.get_clusters().map(|cluster| cluster.id).collect();
3072 let live_replicas: BTreeSet<ReplicaId> = tx
3073 .get_cluster_replicas()
3074 .map(|replica| replica.replica_id)
3075 .collect();
3076 let prune_cluster = |id: &ClusterId| {
3077 prune_scope
3078 .as_ref()
3079 .map_or(true, |s| s.clusters.contains(id))
3080 };
3081 let prune_replica = |id: &ReplicaId| {
3082 prune_scope
3083 .as_ref()
3084 .map_or(true, |s| s.replicas.contains(id))
3085 };
3086
3087 let existing_cluster: BTreeMap<(ClusterId, String), String> = tx
3089 .get_cluster_system_configurations()
3090 .map(|c| ((c.cluster_id, c.name), c.value))
3091 .collect();
3092 let mut desired_cluster: BTreeSet<(ClusterId, String)> = BTreeSet::new();
3093 for (cluster_id, values) in &scoped.cluster {
3094 if !live_clusters.contains(cluster_id) {
3095 continue;
3096 }
3097 for (name, value) in values {
3098 desired_cluster.insert((*cluster_id, name.clone()));
3099 if existing_cluster.get(&(*cluster_id, name.clone())) != Some(value) {
3100 tx.upsert_cluster_system_config(*cluster_id, name, value.clone())?;
3101 }
3102 }
3103 }
3104 for (cluster_id, name) in existing_cluster.into_keys() {
3105 if (!live_clusters.contains(&cluster_id) || prune_cluster(&cluster_id))
3106 && !desired_cluster.contains(&(cluster_id, name.clone()))
3107 {
3108 tx.remove_cluster_system_config(cluster_id, &name);
3109 }
3110 }
3111
3112 let existing_replica: BTreeMap<(ReplicaId, String), String> = tx
3114 .get_replica_system_configurations()
3115 .map(|r| ((r.replica_id, r.name), r.value))
3116 .collect();
3117 let mut desired_replica: BTreeSet<(ReplicaId, String)> = BTreeSet::new();
3118 for (replica_id, values) in &scoped.replica {
3119 if !live_replicas.contains(replica_id) {
3120 continue;
3121 }
3122 for (name, value) in values {
3123 desired_replica.insert((*replica_id, name.clone()));
3124 if existing_replica.get(&(*replica_id, name.clone())) != Some(value) {
3125 tx.upsert_replica_system_config(*replica_id, name, value.clone())?;
3126 }
3127 }
3128 }
3129 for (replica_id, name) in existing_replica.into_keys() {
3130 if (!live_replicas.contains(&replica_id) || prune_replica(&replica_id))
3131 && !desired_replica.contains(&(replica_id, name.clone()))
3132 {
3133 tx.remove_replica_system_config(replica_id, &name);
3134 }
3135 }
3136 }
3137 Op::InjectAuditEvents { events } => {
3138 for event in events {
3139 let id = tx.allocate_audit_log_id()?;
3140 let ev = VersionedEvent::new(
3141 id,
3142 event.event_type,
3143 event.object_type,
3144 event.details,
3145 event.user,
3146 oracle_write_ts.into(),
3147 );
3148 audit_events.push(ev.clone());
3149 tx.insert_audit_log_event(ev);
3150 }
3151 }
3152 };
3153 Ok(temporary_item_updates)
3154 }
3155
3156 fn log_update(state: &CatalogState, id: &CatalogItemId) {
3157 let entry = state.get_entry(id);
3158 info!(
3159 "update {} {} ({})",
3160 entry.item_type(),
3161 state.resolve_full_name(entry.name(), entry.conn_id()),
3162 id
3163 );
3164 }
3165
3166 fn update_privilege_owners(
3170 privileges: &mut PrivilegeMap,
3171 old_owner: RoleId,
3172 new_owner: RoleId,
3173 ) {
3174 let mut flat_privileges: Vec<_> = privileges.all_values_owned().collect();
3176
3177 let mut new_present = false;
3178 for privilege in flat_privileges.iter_mut() {
3179 if privilege.grantor == old_owner {
3182 privilege.grantor = new_owner;
3183 } else if privilege.grantor == new_owner {
3184 new_present = true;
3185 }
3186 if privilege.grantee == old_owner {
3188 privilege.grantee = new_owner;
3189 } else if privilege.grantee == new_owner {
3190 new_present = true;
3191 }
3192 }
3193
3194 if new_present {
3198 let privilege_map: BTreeMap<_, Vec<_>> =
3200 flat_privileges
3201 .into_iter()
3202 .fold(BTreeMap::new(), |mut accum, privilege| {
3203 accum
3204 .entry((privilege.grantee, privilege.grantor))
3205 .or_default()
3206 .push(privilege);
3207 accum
3208 });
3209
3210 flat_privileges = privilege_map
3212 .into_iter()
3213 .map(|((grantee, grantor), values)|
3214 values.into_iter().fold(
3216 MzAclItem::empty(grantee, grantor),
3217 |mut accum, mz_aclitem| {
3218 accum.acl_mode =
3219 accum.acl_mode.union(mz_aclitem.acl_mode);
3220 accum
3221 },
3222 ))
3223 .collect();
3224 }
3225
3226 *privileges = PrivilegeMap::from_mz_acl_items(flat_privileges);
3227 }
3228}
3229
3230fn tx_replace_item(
3239 tx: &mut Transaction<'_>,
3240 state: &CatalogState,
3241 id: CatalogItemId,
3242 new_entry: CatalogEntry,
3243 temporary_item_updates: &mut Vec<(TemporaryItem, StateDiff)>,
3244) -> Result<(), AdapterError> {
3245 let new_id = new_entry.id;
3246
3247 for use_id in new_entry.referenced_by() {
3249 let dependent = state.get_entry(use_id);
3250
3251 if dependent.item().is_temporary() {
3257 let mut rewritten = dependent.clone();
3258 rewritten.item = rewritten.item.replace_item_refs(id, new_id);
3259 temporary_item_updates.push((dependent.clone().into(), StateDiff::Retraction));
3260 temporary_item_updates.push((rewritten.into(), StateDiff::Addition));
3261 continue;
3262 }
3263
3264 if tx.get_item(use_id).is_none() {
3266 continue;
3267 }
3268
3269 let mut dependent = dependent.clone();
3270 dependent.item = dependent.item.replace_item_refs(id, new_id);
3271 tx.update_item(*use_id, dependent.into())?;
3272 }
3273
3274 let old_comment_id = state.get_comment_id(ObjectId::Item(id));
3276 let new_comment_id = new_entry.comment_object_id();
3277 if let Some(comments) = state.comments.get_object_comments(old_comment_id) {
3278 tx.drop_comments(&[old_comment_id].into())?;
3279 for (sub, comment) in comments {
3280 tx.update_comment(new_comment_id, *sub, Some(comment.clone()))?;
3281 }
3282 }
3283
3284 let mz_catalog::durable::Item {
3285 id: _,
3286 oid,
3287 global_id,
3288 schema_id,
3289 name,
3290 create_sql,
3291 owner_id,
3292 privileges,
3293 extra_versions,
3294 } = new_entry.into();
3295
3296 tx.remove_item(id)?;
3297 tx.insert_item(
3298 new_id,
3299 oid,
3300 global_id,
3301 schema_id,
3302 &name,
3303 create_sql,
3304 owner_id,
3305 privileges,
3306 extra_versions,
3307 )?;
3308
3309 Ok(())
3310}
3311
3312fn apply_replacement_audit_events(
3314 state: &CatalogState,
3315 target: &CatalogEntry,
3316 replacement: &CatalogEntry,
3317) -> Vec<(EventType, EventDetails)> {
3318 let mut events = Vec::new();
3319
3320 let target_name = state.resolve_full_name(target.name(), target.conn_id());
3321 let target_id_name = IdFullNameV1 {
3322 id: target.id().to_string(),
3323 name: Catalog::full_name_detail(&target_name),
3324 };
3325 let replacement_name = state.resolve_full_name(replacement.name(), replacement.conn_id());
3326 let replacement_id_name = IdFullNameV1 {
3327 id: replacement.id().to_string(),
3328 name: Catalog::full_name_detail(&replacement_name),
3329 };
3330
3331 if Catalog::should_audit_log_item(&replacement.item) {
3332 events.push((
3333 EventType::Drop,
3334 EventDetails::IdFullNameV1(replacement_id_name.clone()),
3335 ));
3336 }
3337
3338 if Catalog::should_audit_log_item(&target.item) {
3339 events.push((
3340 EventType::Alter,
3341 EventDetails::AlterApplyReplacementV1(mz_audit_log::AlterApplyReplacementV1 {
3342 target: target_id_name.clone(),
3343 replacement: replacement_id_name,
3344 }),
3345 ));
3346
3347 if let Some(old_cluster_id) = target.cluster_id()
3348 && let Some(new_cluster_id) = replacement.cluster_id()
3349 && old_cluster_id != new_cluster_id
3350 {
3351 events.push((
3354 EventType::Alter,
3355 EventDetails::AlterSetClusterV1(mz_audit_log::AlterSetClusterV1 {
3356 id: replacement.id().to_string(),
3357 name: target_id_name.name,
3358 old_cluster_id: old_cluster_id.to_string(),
3359 new_cluster_id: new_cluster_id.to_string(),
3360 }),
3361 ));
3362 }
3363 }
3364
3365 events
3366}
3367
3368#[derive(Debug, Default)]
3376pub(crate) struct ObjectsToDrop {
3377 pub comments: BTreeSet<CommentObjectId>,
3378 pub databases: BTreeSet<DatabaseId>,
3379 pub schemas: BTreeMap<SchemaSpecifier, ResolvedDatabaseSpecifier>,
3380 pub clusters: BTreeSet<ClusterId>,
3381 pub replicas: BTreeMap<ReplicaId, (ClusterId, ReplicaCreateDropReason)>,
3382 pub roles: BTreeSet<RoleId>,
3383 pub items: Vec<CatalogItemId>,
3384 pub network_policies: BTreeSet<NetworkPolicyId>,
3385}
3386
3387impl ObjectsToDrop {
3388 pub fn generate(
3389 drop_object_infos: impl IntoIterator<Item = DropObjectInfo>,
3390 state: &CatalogState,
3391 session: Option<&ConnMeta>,
3392 ) -> Result<Self, AdapterError> {
3393 let mut delta = ObjectsToDrop::default();
3394
3395 for drop_object_info in drop_object_infos {
3396 delta.add_item(drop_object_info, state, session)?;
3397 }
3398
3399 Ok(delta)
3400 }
3401
3402 fn add_item(
3403 &mut self,
3404 drop_object_info: DropObjectInfo,
3405 state: &CatalogState,
3406 session: Option<&ConnMeta>,
3407 ) -> Result<(), AdapterError> {
3408 self.comments
3409 .insert(state.get_comment_id(drop_object_info.to_object_id()));
3410
3411 match drop_object_info {
3412 DropObjectInfo::Database(database_id) => {
3413 let database = &state.database_by_id[&database_id];
3414 if database_id.is_system() {
3415 return Err(AdapterError::Catalog(Error::new(
3416 ErrorKind::ReadOnlyDatabase(database.name().to_string()),
3417 )));
3418 }
3419
3420 self.databases.insert(database_id);
3421 }
3422 DropObjectInfo::Schema((database_spec, schema_spec)) => {
3423 let schema = state.get_schema(
3424 &database_spec,
3425 &schema_spec,
3426 session
3427 .map(|session| session.conn_id())
3428 .unwrap_or(&SYSTEM_CONN_ID),
3429 );
3430 let schema_id: SchemaId = schema_spec.into();
3431 if schema_id.is_system() {
3432 let name = schema.name();
3433 let full_name = state.resolve_full_schema_name(name);
3434 return Err(AdapterError::Catalog(Error::new(
3435 ErrorKind::ReadOnlySystemSchema(full_name.to_string()),
3436 )));
3437 }
3438
3439 self.schemas.insert(schema_spec, database_spec);
3440 }
3441 DropObjectInfo::Role(role_id) => {
3442 let name = state.get_role(&role_id).name().to_string();
3443 if role_id.is_system() || role_id.is_predefined() {
3444 return Err(AdapterError::Catalog(Error::new(
3445 ErrorKind::ReservedRoleName(name.clone()),
3446 )));
3447 }
3448 state.ensure_not_reserved_role(&role_id)?;
3449
3450 self.roles.insert(role_id);
3451 }
3452 DropObjectInfo::Cluster(cluster_id) => {
3453 let cluster = state.get_cluster(cluster_id);
3454 let name = &cluster.name;
3455 if cluster_id.is_system() {
3456 return Err(AdapterError::Catalog(Error::new(
3457 ErrorKind::ReadOnlyCluster(name.clone()),
3458 )));
3459 }
3460
3461 self.clusters.insert(cluster_id);
3462 }
3463 DropObjectInfo::ClusterReplica((cluster_id, replica_id, reason)) => {
3464 let cluster = state.get_cluster(cluster_id);
3465 let replica = cluster.replica(replica_id).expect("Must exist");
3466
3467 self.replicas
3468 .insert(replica.replica_id, (cluster.id, reason));
3469
3470 let mut seen: BTreeSet<ObjectId> =
3489 self.items.iter().copied().map(ObjectId::Item).collect();
3490 for dep in state.cluster_replica_dependents(cluster_id, replica_id, &mut seen) {
3491 if let ObjectId::Item(dep_id) = dep {
3492 info!(
3493 "implicitly dropping {} because target replica was dropped",
3494 state.get_entry(&dep_id).name().item,
3495 );
3496 self.comments
3497 .insert(state.get_comment_id(ObjectId::Item(dep_id)));
3498 self.items.push(dep_id);
3499 }
3500 }
3501 }
3502 DropObjectInfo::Item(item_id) => {
3503 let entry = state.get_entry(&item_id);
3504 if item_id.is_system() {
3505 let name = entry.name();
3506 let full_name =
3507 state.resolve_full_name(name, session.map(|session| session.conn_id()));
3508 return Err(AdapterError::Catalog(Error::new(ErrorKind::ReadOnlyItem(
3509 full_name.to_string(),
3510 ))));
3511 }
3512
3513 self.items.push(item_id);
3514 }
3515 DropObjectInfo::NetworkPolicy(network_policy_id) => {
3516 let policy = state.get_network_policy(&network_policy_id);
3517 let name = &policy.name;
3518 if network_policy_id.is_system() {
3519 return Err(AdapterError::Catalog(Error::new(
3520 ErrorKind::ReadOnlyNetworkPolicy(name.clone()),
3521 )));
3522 }
3523
3524 self.network_policies.insert(network_policy_id);
3525 }
3526 }
3527
3528 Ok(())
3529 }
3530}
3531
3532#[cfg(test)]
3533mod tests {
3534 use mz_catalog::SYSTEM_CONN_ID;
3535 use mz_catalog::memory::objects::{CatalogItem, Table, TableDataSource};
3536 use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap};
3537 use mz_repr::role_id::RoleId;
3538 use mz_repr::{RelationDesc, RelationVersion, VersionedRelationDesc};
3539 use mz_sql::DEFAULT_SCHEMA;
3540 use mz_sql::catalog::CatalogDatabase;
3541 use mz_sql::names::{
3542 ItemQualifiers, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds,
3543 };
3544 use mz_sql::session::user::MZ_SYSTEM_ROLE_ID;
3545
3546 use crate::catalog::{Catalog, Op};
3547 use crate::session::DEFAULT_DATABASE_NAME;
3548
3549 #[mz_ore::test]
3550 fn test_replica_create_drop_reason_into_audit_log() {
3551 use mz_audit_log::CreateOrDropClusterReplicaReasonV1;
3552
3553 use crate::catalog::ReplicaCreateDropReason;
3554
3555 let (reason, scheduling_policies) = ReplicaCreateDropReason::Retired.into_audit_log();
3559 assert_eq!(reason, CreateOrDropClusterReplicaReasonV1::Retired);
3560 assert!(scheduling_policies.is_none());
3561 }
3562
3563 #[mz_ore::test]
3564 fn test_reconfiguration_audit_details() {
3565 use std::time::Duration;
3566
3567 use mz_adapter_types::cluster_state::ReconfigurationAudit;
3568 use mz_audit_log::ReconfigurationLifecycleV1;
3569 use mz_catalog::memory::objects::{
3570 ClusterConfig, ClusterVariant, ClusterVariantManaged, ReconfigurationState,
3571 ReconfigurationStatus, ReconfigurationTarget,
3572 };
3573 use mz_controller::clusters::ReplicaLogging;
3574 use mz_controller_types::ClusterId;
3575 use mz_repr::Timestamp;
3576 use mz_repr::optimize::OptimizerFeatureOverrides;
3577
3578 let cluster_id = ClusterId::user(1).expect("valid id");
3579 let logging = ReplicaLogging {
3580 log_logging: false,
3581 interval: None,
3582 };
3583 let managed = |reconfiguration: Option<ReconfigurationState>| ClusterConfig {
3584 variant: ClusterVariant::Managed(ClusterVariantManaged {
3585 size: "small".into(),
3586 availability_zones: Vec::new(),
3587 logging: logging.clone(),
3588 replication_factor: 1,
3589 optimizer_feature_overrides: OptimizerFeatureOverrides::default(),
3590 schedule: Default::default(),
3591 auto_scaling_strategy: None,
3592 reconfiguration,
3593 burst: None,
3594 }),
3595 workload_class: None,
3596 };
3597 let record = |status| ReconfigurationState {
3598 target: ReconfigurationTarget {
3599 size: "large".into(),
3600 replication_factor: 2,
3601 availability_zones: vec!["az1".into(), "az2".into()],
3602 logging: ReplicaLogging {
3603 log_logging: true,
3604 interval: Some(Duration::from_secs(5)),
3605 },
3606 },
3607 deadline: Timestamp::from(400u64),
3608 on_timeout: mz_sql::plan::OnTimeoutAction::Rollback,
3609 status,
3610 };
3611
3612 let details = Catalog::reconfiguration_audit_details(
3613 &managed(Some(record(ReconfigurationStatus::InProgress))),
3614 cluster_id,
3615 "c",
3616 ReconfigurationAudit::Started,
3617 )
3618 .expect("record supplies audit details");
3619 assert_eq!(details.cluster_id, cluster_id.to_string());
3620 assert_eq!(details.cluster_name, "c");
3621 assert_eq!(details.transition, ReconfigurationLifecycleV1::Started);
3622 assert_eq!(details.forced, None);
3623 assert_eq!(details.target_size, "large");
3624 assert_eq!(details.target_replication_factor, 2);
3625 assert_eq!(
3626 details.target_availability_zones,
3627 vec!["az1".to_string(), "az2".to_string()]
3628 );
3629 assert_eq!(details.target_logging.log_logging, true);
3630 assert_eq!(
3631 details.target_logging.interval,
3632 Some(Duration::from_secs(5))
3633 );
3634 assert_eq!(details.deadline, Some(400));
3635
3636 let forced = Catalog::reconfiguration_audit_details(
3640 &managed(Some(record(ReconfigurationStatus::Finalized))),
3641 cluster_id,
3642 "c",
3643 ReconfigurationAudit::Finalized { forced: true },
3644 )
3645 .expect("a coherent finalize supplies audit details");
3646 assert_eq!(forced.transition, ReconfigurationLifecycleV1::Finalized);
3647 assert_eq!(forced.forced, Some(true));
3648
3649 let incoherent = Catalog::reconfiguration_audit_details(
3652 &managed(Some(record(ReconfigurationStatus::TimedOut))),
3653 cluster_id,
3654 "c",
3655 ReconfigurationAudit::ResourceExhausted,
3656 );
3657 assert!(incoherent.is_err());
3658
3659 let missing = Catalog::reconfiguration_audit_details(
3660 &managed(None),
3661 cluster_id,
3662 "c",
3663 ReconfigurationAudit::Started,
3664 );
3665 assert!(missing.is_err());
3666 }
3667
3668 #[mz_ore::test]
3669 fn test_reconfiguration_lifecycle_moved() {
3670 use std::time::Duration;
3671
3672 use mz_catalog::memory::objects::{
3673 ClusterConfig, ClusterVariant, ClusterVariantManaged, ReconfigurationState,
3674 ReconfigurationStatus, ReconfigurationTarget,
3675 };
3676 use mz_controller::clusters::ReplicaLogging;
3677 use mz_repr::Timestamp;
3678 use mz_repr::optimize::OptimizerFeatureOverrides;
3679
3680 let managed = |reconfiguration: Option<ReconfigurationState>| ClusterConfig {
3681 variant: ClusterVariant::Managed(ClusterVariantManaged {
3682 size: "small".into(),
3683 availability_zones: Vec::new(),
3684 logging: ReplicaLogging {
3685 log_logging: false,
3686 interval: None,
3687 },
3688 replication_factor: 1,
3689 optimizer_feature_overrides: OptimizerFeatureOverrides::default(),
3690 schedule: Default::default(),
3691 auto_scaling_strategy: None,
3692 reconfiguration,
3693 burst: None,
3694 }),
3695 workload_class: None,
3696 };
3697 let unmanaged = ClusterConfig {
3698 variant: ClusterVariant::Unmanaged,
3699 workload_class: None,
3700 };
3701 let record = |status| ReconfigurationState {
3702 target: ReconfigurationTarget {
3703 size: "large".into(),
3704 replication_factor: 2,
3705 availability_zones: Vec::new(),
3706 logging: ReplicaLogging {
3707 log_logging: false,
3708 interval: Some(Duration::from_secs(1)),
3709 },
3710 },
3711 deadline: Timestamp::from(400u64),
3712 on_timeout: mz_sql::plan::OnTimeoutAction::Rollback,
3713 status,
3714 };
3715
3716 assert!(Catalog::reconfiguration_lifecycle_moved(
3719 &managed(None),
3720 &managed(Some(record(ReconfigurationStatus::InProgress))),
3721 ));
3722 assert!(Catalog::reconfiguration_lifecycle_moved(
3724 &managed(Some(record(ReconfigurationStatus::InProgress))),
3725 &managed(Some(record(ReconfigurationStatus::Finalized))),
3726 ));
3727 assert!(Catalog::reconfiguration_lifecycle_moved(
3730 &managed(Some(record(ReconfigurationStatus::InProgress))),
3731 &unmanaged,
3732 ));
3733
3734 assert!(!Catalog::reconfiguration_lifecycle_moved(
3737 &managed(None),
3738 &managed(None),
3739 ));
3740 assert!(!Catalog::reconfiguration_lifecycle_moved(
3741 &managed(Some(record(ReconfigurationStatus::InProgress))),
3742 &managed(Some(record(ReconfigurationStatus::InProgress))),
3743 ));
3744 assert!(!Catalog::reconfiguration_lifecycle_moved(
3745 &managed(Some(record(ReconfigurationStatus::Cancelled))),
3746 &unmanaged,
3747 ));
3748 }
3749
3750 #[mz_ore::test]
3751 fn test_burst_audit_details() {
3752 use std::time::Duration;
3753
3754 use mz_adapter_types::cluster_state::{BurstAudit, BurstFinishCause};
3755 use mz_audit_log::{BurstFinishCauseV1, HydrationBurstLifecycleV1};
3756 use mz_catalog::memory::objects::{
3757 BurstState, ClusterConfig, ClusterVariant, ClusterVariantManaged,
3758 };
3759 use mz_controller::clusters::ReplicaLogging;
3760 use mz_controller_types::ClusterId;
3761 use mz_repr::optimize::OptimizerFeatureOverrides;
3762
3763 let cluster_id = ClusterId::user(1).expect("valid id");
3764 let managed = |burst: Option<BurstState>| ClusterConfig {
3765 variant: ClusterVariant::Managed(ClusterVariantManaged {
3766 size: "small".into(),
3767 availability_zones: Vec::new(),
3768 logging: ReplicaLogging {
3769 log_logging: false,
3770 interval: None,
3771 },
3772 replication_factor: 1,
3773 optimizer_feature_overrides: OptimizerFeatureOverrides::default(),
3774 schedule: Default::default(),
3775 auto_scaling_strategy: None,
3776 reconfiguration: None,
3777 burst,
3778 }),
3779 workload_class: None,
3780 };
3781 let record = || BurstState {
3782 burst_size: "large".into(),
3783 linger_duration: Duration::from_secs(60),
3784 steady_hydrated_at: None,
3785 };
3786
3787 let started = Catalog::burst_audit_details(
3789 &managed(None),
3790 &managed(Some(record())),
3791 cluster_id,
3792 "c",
3793 BurstAudit::Started,
3794 )
3795 .expect("armed record supplies audit details");
3796 assert_eq!(started.transition, HydrationBurstLifecycleV1::Started);
3797 assert_eq!(started.finish_cause, None);
3798 assert_eq!(started.burst_size, "large");
3799
3800 let finished = Catalog::burst_audit_details(
3803 &managed(Some(record())),
3804 &managed(None),
3805 cluster_id,
3806 "c",
3807 BurstAudit::Finished {
3808 cause: BurstFinishCause::LingerElapsed,
3809 },
3810 )
3811 .expect("cleared record supplies audit details");
3812 assert_eq!(finished.transition, HydrationBurstLifecycleV1::Finished);
3813 assert_eq!(
3814 finished.finish_cause,
3815 Some(BurstFinishCauseV1::LingerElapsed)
3816 );
3817 assert_eq!(finished.burst_size, "large");
3818
3819 let incoherent = Catalog::burst_audit_details(
3822 &managed(None),
3823 &managed(None),
3824 cluster_id,
3825 "c",
3826 BurstAudit::Started,
3827 );
3828 assert!(incoherent.is_err());
3829 }
3830
3831 #[mz_ore::test]
3832 fn test_update_privilege_owners() {
3833 let old_owner = RoleId::User(1);
3834 let new_owner = RoleId::User(2);
3835 let other_role = RoleId::User(3);
3836
3837 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3839 MzAclItem {
3840 grantee: other_role,
3841 grantor: old_owner,
3842 acl_mode: AclMode::UPDATE,
3843 },
3844 MzAclItem {
3845 grantee: other_role,
3846 grantor: new_owner,
3847 acl_mode: AclMode::SELECT,
3848 },
3849 ]);
3850 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3851 assert_eq!(1, privileges.all_values().count());
3852 assert_eq!(
3853 vec![MzAclItem {
3854 grantee: other_role,
3855 grantor: new_owner,
3856 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3857 }],
3858 privileges.all_values_owned().collect::<Vec<_>>()
3859 );
3860
3861 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3863 MzAclItem {
3864 grantee: old_owner,
3865 grantor: other_role,
3866 acl_mode: AclMode::UPDATE,
3867 },
3868 MzAclItem {
3869 grantee: new_owner,
3870 grantor: other_role,
3871 acl_mode: AclMode::SELECT,
3872 },
3873 ]);
3874 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3875 assert_eq!(1, privileges.all_values().count());
3876 assert_eq!(
3877 vec![MzAclItem {
3878 grantee: new_owner,
3879 grantor: other_role,
3880 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3881 }],
3882 privileges.all_values_owned().collect::<Vec<_>>()
3883 );
3884
3885 let mut privileges = PrivilegeMap::from_mz_acl_items(vec![
3887 MzAclItem {
3888 grantee: old_owner,
3889 grantor: old_owner,
3890 acl_mode: AclMode::UPDATE,
3891 },
3892 MzAclItem {
3893 grantee: new_owner,
3894 grantor: new_owner,
3895 acl_mode: AclMode::SELECT,
3896 },
3897 ]);
3898 Catalog::update_privilege_owners(&mut privileges, old_owner, new_owner);
3899 assert_eq!(1, privileges.all_values().count());
3900 assert_eq!(
3901 vec![MzAclItem {
3902 grantee: new_owner,
3903 grantor: new_owner,
3904 acl_mode: AclMode::SELECT.union(AclMode::UPDATE)
3905 }],
3906 privileges.all_values_owned().collect::<Vec<_>>()
3907 );
3908 }
3909
3910 #[mz_ore::test(tokio::test)]
3917 #[cfg_attr(miri, ignore)] async fn test_transact_incremental_dry_run_processes_only_new_ops() {
3919 Catalog::with_debug(|catalog| async move {
3920 let database = catalog
3922 .resolve_database(DEFAULT_DATABASE_NAME)
3923 .expect("default database");
3924 let database_name = database.name.clone();
3925 let database_spec = ResolvedDatabaseSpecifier::Id(database.id());
3926 let schema = catalog
3927 .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID)
3928 .expect("default schema");
3929 let schema_name = schema.name.schema.clone();
3930 let schema_spec = schema.id.clone();
3931
3932 let (id_t1, global_id_t1) = catalog
3934 .allocate_user_id_for_test()
3935 .await
3936 .expect("allocate id for t1");
3937 let (id_t2, global_id_t2) = catalog
3938 .allocate_user_id_for_test()
3939 .await
3940 .expect("allocate id for t2");
3941
3942 let oracle_write_ts = catalog.current_upper().await;
3943
3944 let make_table_op = |id, global_id, name: &str| Op::CreateItem {
3946 id,
3947 name: QualifiedItemName {
3948 qualifiers: ItemQualifiers {
3949 database_spec: database_spec.clone(),
3950 schema_spec: schema_spec.clone(),
3951 },
3952 item: name.to_string(),
3953 },
3954 item: CatalogItem::Table(Table {
3955 create_sql: Some(format!(
3956 "CREATE TABLE {database_name}.{schema_name}.{name} ()"
3957 )),
3958 desc: VersionedRelationDesc::new(RelationDesc::empty()),
3959 collections: [(RelationVersion::root(), global_id)].into_iter().collect(),
3960 conn_id: None,
3961 resolved_ids: ResolvedIds::empty(),
3962 custom_logical_compaction_window: None,
3963 is_retained_metrics_object: false,
3964 data_source: TableDataSource::TableWrites { defaults: vec![] },
3965 }),
3966 owner_id: MZ_SYSTEM_ROLE_ID,
3967 };
3968
3969 let op_t1 = make_table_op(id_t1, global_id_t1, "t1");
3970 let op_t2 = make_table_op(id_t2, global_id_t2, "t2");
3971
3972 let base_state = catalog.state().clone();
3973
3974 let (state_after_t1, snapshot_after_t1) = catalog
3978 .transact_incremental_dry_run(
3979 &base_state,
3980 vec![op_t1.clone()],
3981 None,
3982 None,
3983 oracle_write_ts,
3984 )
3985 .await
3986 .expect("first dry run");
3987
3988 assert!(
3990 state_after_t1.try_get_entry(&id_t1).is_some(),
3991 "t1 should exist after first dry run"
3992 );
3993 assert_eq!(
3994 state_after_t1
3995 .try_get_entry(&id_t1)
3996 .expect("t1 entry")
3997 .name()
3998 .item,
3999 "t1"
4000 );
4001 assert!(
4002 state_after_t1.try_get_entry(&id_t2).is_none(),
4003 "t2 should NOT exist after first dry run"
4004 );
4005
4006 let (state_incremental, _) = catalog
4008 .transact_incremental_dry_run(
4009 &state_after_t1,
4010 vec![op_t2.clone()],
4011 None,
4012 Some(snapshot_after_t1),
4013 oracle_write_ts,
4014 )
4015 .await
4016 .expect("second dry run");
4017
4018 assert!(
4020 state_incremental.try_get_entry(&id_t1).is_some(),
4021 "t1 should exist in incremental result"
4022 );
4023 assert!(
4024 state_incremental.try_get_entry(&id_t2).is_some(),
4025 "t2 should exist in incremental result"
4026 );
4027
4028 let (state_all_at_once, _) = catalog
4031 .transact_incremental_dry_run(
4032 &base_state,
4033 vec![op_t1.clone(), op_t2.clone()],
4034 None,
4035 None,
4036 oracle_write_ts,
4037 )
4038 .await
4039 .expect("all-at-once dry run");
4040
4041 assert!(
4042 state_all_at_once.try_get_entry(&id_t1).is_some(),
4043 "t1 should exist in all-at-once result"
4044 );
4045 assert!(
4046 state_all_at_once.try_get_entry(&id_t2).is_some(),
4047 "t2 should exist in all-at-once result"
4048 );
4049
4050 let inc_t1 = state_incremental.try_get_entry(&id_t1).expect("inc t1");
4053 let all_t1 = state_all_at_once.try_get_entry(&id_t1).expect("all t1");
4054 assert_eq!(inc_t1.name(), all_t1.name());
4055 assert_eq!(inc_t1.owner_id, all_t1.owner_id);
4056
4057 let inc_t2 = state_incremental.try_get_entry(&id_t2).expect("inc t2");
4058 let all_t2 = state_all_at_once.try_get_entry(&id_t2).expect("all t2");
4059 assert_eq!(inc_t2.name(), all_t2.name());
4060 assert_eq!(inc_t2.owner_id, all_t2.owner_id);
4061
4062 catalog.expire().await;
4063 })
4064 .await
4065 }
4066
4067 #[mz_ore::test(tokio::test)]
4074 #[cfg_attr(miri, ignore)] async fn test_transact_update_scoped_system_parameters_prune() {
4076 use std::collections::{BTreeMap, BTreeSet};
4077
4078 use mz_catalog::memory::objects::{ClusterConfig, ClusterVariant};
4079 use mz_controller_types::ClusterId;
4080
4081 use crate::catalog::DropObjectInfo;
4082 use crate::config::{ScopedParameters, ScopedParametersScope};
4083
4084 Catalog::with_debug(|mut catalog| async move {
4085 let param = "max_query_result_size".to_string();
4086 let value = "1MB".to_string();
4087
4088 let commit_ts = catalog.current_upper().await;
4091 let cluster_a = catalog
4092 .allocate_user_cluster_id(commit_ts)
4093 .await
4094 .expect("allocate cluster A");
4095 let commit_ts = catalog.current_upper().await;
4096 let cluster_b = catalog
4097 .allocate_user_cluster_id(commit_ts)
4098 .await
4099 .expect("allocate cluster B");
4100
4101 let make_cluster_op = |id: ClusterId, name: &str| Op::CreateCluster {
4102 id,
4103 name: name.to_string(),
4104 introspection_sources: Vec::new(),
4105 owner_id: MZ_SYSTEM_ROLE_ID,
4106 config: ClusterConfig {
4107 variant: ClusterVariant::Unmanaged,
4108 workload_class: None,
4109 },
4110 };
4111
4112 let oracle_write_ts = catalog.current_upper().await;
4113 catalog
4114 .transact(
4115 None,
4116 oracle_write_ts,
4117 None,
4118 vec![
4119 make_cluster_op(cluster_a, "test_cluster_a"),
4120 make_cluster_op(cluster_b, "test_cluster_b"),
4121 ],
4122 )
4123 .await
4124 .expect("create clusters");
4125
4126 async fn rows(catalog: &Catalog) -> BTreeSet<(ClusterId, String, String)> {
4128 let mut storage = catalog.storage().await;
4129 let tx = storage.transaction().await.expect("open transaction");
4130 tx.get_cluster_system_configurations()
4131 .map(|c| (c.cluster_id, c.name, c.value))
4132 .collect()
4133 }
4134
4135 let scope_with = |ids: &[ClusterId]| ScopedParametersScope {
4136 clusters: ids.iter().copied().collect(),
4137 replicas: BTreeSet::new(),
4138 };
4139 let cluster_scoped = |id: ClusterId| {
4140 let mut cluster = BTreeMap::new();
4141 let mut overrides = BTreeMap::new();
4142 overrides.insert(param.clone(), value.clone());
4143 cluster.insert(id, overrides);
4144 ScopedParameters {
4145 cluster,
4146 replica: BTreeMap::new(),
4147 }
4148 };
4149 let empty_scoped = || ScopedParameters {
4150 cluster: BTreeMap::new(),
4151 replica: BTreeMap::new(),
4152 };
4153
4154 let oracle_write_ts = catalog.current_upper().await;
4156 catalog
4157 .transact(
4158 None,
4159 oracle_write_ts,
4160 None,
4161 vec![Op::UpdateScopedSystemParameters {
4162 scoped: cluster_scoped(cluster_a),
4163 prune_scope: Some(scope_with(&[cluster_a])),
4164 }],
4165 )
4166 .await
4167 .expect("upsert A");
4168 assert!(
4169 rows(&catalog)
4170 .await
4171 .contains(&(cluster_a, param.clone(), value.clone())),
4172 "upsert must create the row for A"
4173 );
4174
4175 let oracle_write_ts = catalog.current_upper().await;
4178 catalog
4179 .transact(
4180 None,
4181 oracle_write_ts,
4182 None,
4183 vec![Op::UpdateScopedSystemParameters {
4184 scoped: cluster_scoped(cluster_b),
4185 prune_scope: Some(scope_with(&[cluster_b])),
4186 }],
4187 )
4188 .await
4189 .expect("upsert B");
4190 assert!(
4191 rows(&catalog)
4192 .await
4193 .contains(&(cluster_b, param.clone(), value.clone())),
4194 "setup must create the row for B"
4195 );
4196
4197 let oracle_write_ts = catalog.current_upper().await;
4201 catalog
4202 .transact(
4203 None,
4204 oracle_write_ts,
4205 None,
4206 vec![Op::UpdateScopedSystemParameters {
4207 scoped: empty_scoped(),
4208 prune_scope: Some(scope_with(&[cluster_a])),
4209 }],
4210 )
4211 .await
4212 .expect("bounded prune");
4213 assert!(
4214 rows(&catalog)
4215 .await
4216 .contains(&(cluster_b, param.clone(), value.clone())),
4217 "a live cluster outside prune_scope must keep its row"
4218 );
4219
4220 let oracle_write_ts = catalog.current_upper().await;
4223 catalog
4224 .transact(
4225 None,
4226 oracle_write_ts,
4227 None,
4228 vec![Op::UpdateScopedSystemParameters {
4229 scoped: empty_scoped(),
4230 prune_scope: Some(scope_with(&[cluster_a])),
4231 }],
4232 )
4233 .await
4234 .expect("stale in-scope prune");
4235 assert!(
4236 !rows(&catalog)
4237 .await
4238 .iter()
4239 .any(|(id, _, _)| *id == cluster_a),
4240 "a live in-scope undesired row must be removed"
4241 );
4242
4243 let oracle_write_ts = catalog.current_upper().await;
4248 catalog
4249 .transact(
4250 None,
4251 oracle_write_ts,
4252 None,
4253 vec![Op::UpdateScopedSystemParameters {
4254 scoped: cluster_scoped(cluster_a),
4255 prune_scope: Some(scope_with(&[cluster_a])),
4256 }],
4257 )
4258 .await
4259 .expect("re-create A row");
4260 assert!(
4261 rows(&catalog)
4262 .await
4263 .contains(&(cluster_a, param.clone(), value.clone())),
4264 "re-created row for A must exist"
4265 );
4266
4267 let oracle_write_ts = catalog.current_upper().await;
4268 catalog
4269 .transact(
4270 None,
4271 oracle_write_ts,
4272 None,
4273 vec![Op::DropObjects(vec![DropObjectInfo::Cluster(cluster_a)])],
4274 )
4275 .await
4276 .expect("drop cluster A");
4277
4278 let oracle_write_ts = catalog.current_upper().await;
4281 catalog
4282 .transact(
4283 None,
4284 oracle_write_ts,
4285 None,
4286 vec![Op::UpdateScopedSystemParameters {
4287 scoped: cluster_scoped(cluster_b),
4288 prune_scope: Some(scope_with(&[cluster_b])),
4289 }],
4290 )
4291 .await
4292 .expect("orphan prune");
4293 assert!(
4294 !rows(&catalog)
4295 .await
4296 .iter()
4297 .any(|(id, _, _)| *id == cluster_a),
4298 "orphan row for a dropped cluster must be removed even when absent from prune_scope"
4299 );
4300
4301 catalog.expire().await;
4302 })
4303 .await
4304 }
4305}