Skip to main content

mz_catalog/durable/
transaction.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::{BTreeMap, BTreeSet};
11use std::fmt::Debug;
12use std::num::NonZeroU32;
13use std::time::Duration;
14
15use anyhow::anyhow;
16use derivative::Derivative;
17use itertools::Itertools;
18use mz_audit_log::VersionedEvent;
19use mz_compute_client::logging::{ComputeLog, DifferentialLog, LogVariant, TimelyLog};
20use mz_controller_types::{ClusterId, ReplicaId};
21use mz_ore::cast::{u64_to_usize, usize_to_u64};
22use mz_ore::collections::{CollectionExt, HashSet};
23use mz_ore::now::SYSTEM_TIME;
24use mz_ore::vec::VecExt;
25use mz_ore::{soft_assert_no_log, soft_assert_or_log, soft_panic_or_log};
26use mz_persist_types::ShardId;
27use mz_pgrepr::oid::FIRST_USER_OID;
28use mz_proto::{RustType, TryFromProtoError};
29use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem};
30use mz_repr::network_policy_id::NetworkPolicyId;
31use mz_repr::role_id::RoleId;
32use mz_repr::{CatalogItemId, Diff, GlobalId, RelationVersion};
33use mz_sql::catalog::{
34    CatalogError as SqlCatalogError, CatalogItemType, ObjectType, PasswordAction,
35    RoleAttributesRaw, RoleMembership, RoleVars,
36};
37use mz_sql::names::{CommentObjectId, DatabaseId, ResolvedDatabaseSpecifier, SchemaId};
38use mz_sql::plan::NetworkPolicyRule;
39use mz_sql_parser::ast::QualifiedReplica;
40use mz_storage_client::controller::StorageTxn;
41use mz_storage_types::controller::StorageError;
42use tracing::warn;
43use uuid::Uuid;
44
45use crate::builtin::BuiltinLog;
46use crate::durable::initialize::{
47    ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT, SYSTEM_CONFIG_SYNCED_KEY,
48    WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL, WITH_0DT_DEPLOYMENT_MAX_WAIT,
49};
50use crate::durable::objects::serialization::proto;
51use crate::durable::objects::{
52    AuditLogKey, Cluster, ClusterConfig, ClusterIntrospectionSourceIndexKey,
53    ClusterIntrospectionSourceIndexValue, ClusterKey, ClusterReplica, ClusterReplicaKey,
54    ClusterReplicaValue, ClusterSystemConfiguration, ClusterSystemConfigurationKey,
55    ClusterSystemConfigurationValue, ClusterValue, CommentKey, CommentValue, Config, ConfigKey,
56    ConfigValue, Database, DatabaseKey, DatabaseValue, DefaultPrivilegesKey,
57    DefaultPrivilegesValue, DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue,
58    IntrospectionSourceIndex, Item, ItemKey, ItemValue, NetworkPolicyKey, NetworkPolicyValue,
59    ReplicaConfig, ReplicaSystemConfiguration, ReplicaSystemConfigurationKey,
60    ReplicaSystemConfigurationValue, Role, RoleKey, RoleValue, Schema, SchemaKey, SchemaValue,
61    ServerConfigurationKey, ServerConfigurationValue, SettingKey, SettingValue, SourceReference,
62    SourceReferencesKey, SourceReferencesValue, StorageCollectionMetadataKey,
63    StorageCollectionMetadataValue, SystemObjectDescription, SystemObjectMapping,
64    SystemPrivilegesKey, SystemPrivilegesValue, TxnWalShardValue, UnfinalizedShardKey,
65};
66use crate::durable::{
67    AUDIT_LOG_ID_ALLOC_KEY, BUILTIN_MIGRATION_SHARD_KEY, CATALOG_CONTENT_VERSION_KEY, CatalogError,
68    DATABASE_ID_ALLOC_KEY, DefaultPrivilege, DurableCatalogError, DurableCatalogState,
69    EXPRESSION_CACHE_SHARD_KEY, MOCK_AUTHENTICATION_NONCE_KEY, NetworkPolicy, OID_ALLOC_KEY,
70    SCHEMA_ID_ALLOC_KEY, SYSTEM_CLUSTER_ID_ALLOC_KEY, SYSTEM_ITEM_ALLOC_KEY,
71    SYSTEM_REPLICA_ID_ALLOC_KEY, Snapshot, SystemConfiguration, USER_ITEM_ALLOC_KEY,
72    USER_NETWORK_POLICY_ID_ALLOC_KEY, USER_ROLE_ID_ALLOC_KEY,
73};
74use crate::memory::objects::{StateDiff, StateUpdate, StateUpdateKind};
75
76type Timestamp = u64;
77
78#[derive(Debug, PartialEq)]
79struct CommitCapability;
80
81/// A [`Transaction`] batches multiple catalog operations together and commits them atomically.
82/// An operation also logically groups multiple catalog updates together.
83#[derive(Derivative)]
84#[derivative(Debug)]
85pub struct Transaction<'a> {
86    #[derivative(Debug = "ignore")]
87    #[derivative(PartialEq = "ignore")]
88    durable_catalog: &'a mut dyn DurableCatalogState,
89    databases: TableTransaction<DatabaseKey, DatabaseValue>,
90    schemas: TableTransaction<SchemaKey, SchemaValue>,
91    items: TableTransaction<ItemKey, ItemValue>,
92    comments: TableTransaction<CommentKey, CommentValue>,
93    roles: TableTransaction<RoleKey, RoleValue>,
94    role_auth: TableTransaction<RoleAuthKey, RoleAuthValue>,
95    clusters: TableTransaction<ClusterKey, ClusterValue>,
96    cluster_replicas: TableTransaction<ClusterReplicaKey, ClusterReplicaValue>,
97    introspection_sources:
98        TableTransaction<ClusterIntrospectionSourceIndexKey, ClusterIntrospectionSourceIndexValue>,
99    id_allocator: TableTransaction<IdAllocKey, IdAllocValue>,
100    configs: TableTransaction<ConfigKey, ConfigValue>,
101    settings: TableTransaction<SettingKey, SettingValue>,
102    system_gid_mapping: TableTransaction<GidMappingKey, GidMappingValue>,
103    system_configurations: TableTransaction<ServerConfigurationKey, ServerConfigurationValue>,
104    cluster_system_configurations:
105        TableTransaction<ClusterSystemConfigurationKey, ClusterSystemConfigurationValue>,
106    replica_system_configurations:
107        TableTransaction<ReplicaSystemConfigurationKey, ReplicaSystemConfigurationValue>,
108    default_privileges: TableTransaction<DefaultPrivilegesKey, DefaultPrivilegesValue>,
109    source_references: TableTransaction<SourceReferencesKey, SourceReferencesValue>,
110    system_privileges: TableTransaction<SystemPrivilegesKey, SystemPrivilegesValue>,
111    network_policies: TableTransaction<NetworkPolicyKey, NetworkPolicyValue>,
112    storage_collection_metadata:
113        TableTransaction<StorageCollectionMetadataKey, StorageCollectionMetadataValue>,
114    unfinalized_shards: TableTransaction<UnfinalizedShardKey, ()>,
115    txn_wal_shard: TableTransaction<(), TxnWalShardValue>,
116    // Don't make this a table transaction so that it's not read into the
117    // in-memory cache.
118    audit_log_updates: Vec<(AuditLogKey, Diff, Timestamp)>,
119    /// The upper of `durable_catalog` at the start of the transaction.
120    upper: mz_repr::Timestamp,
121    /// The ID of the current operation of this transaction.
122    op_id: Timestamp,
123    // `DryRunTransaction` removes this token. Commit entry points check it.
124    // Decomposition transfers it to `TransactionBatch` before exposing the
125    // durable handle.
126    commit_capability: Option<CommitCapability>,
127}
128
129/// A catalog transaction that can be evaluated but cannot be committed.
130#[derive(Debug)]
131pub struct DryRunTransaction<'a> {
132    transaction: Transaction<'a>,
133}
134
135impl<'a> DryRunTransaction<'a> {
136    /// Permanently revokes commit permission and returns a dry-run transaction.
137    pub fn new(mut transaction: Transaction<'a>) -> Self {
138        transaction.commit_capability = None;
139        Self { transaction }
140    }
141
142    /// Returns a mutable view for evaluating catalog operations.
143    pub fn transaction_mut(&mut self) -> &mut Transaction<'a> {
144        &mut self.transaction
145    }
146
147    /// Exports the current dry-run state as a [`Snapshot`].
148    pub fn current_snapshot(&self) -> Snapshot {
149        self.transaction.current_snapshot()
150    }
151}
152
153impl<'a> Transaction<'a> {
154    pub(super) fn new(
155        durable_catalog: &'a mut dyn DurableCatalogState,
156        Snapshot {
157            databases,
158            schemas,
159            roles,
160            role_auth,
161            items,
162            comments,
163            clusters,
164            network_policies,
165            cluster_replicas,
166            introspection_sources,
167            id_allocator,
168            configs,
169            settings,
170            source_references,
171            system_object_mappings,
172            system_configurations,
173            cluster_system_configurations,
174            replica_system_configurations,
175            default_privileges,
176            system_privileges,
177            storage_collection_metadata,
178            unfinalized_shards,
179            txn_wal_shard,
180        }: Snapshot,
181        upper: mz_repr::Timestamp,
182    ) -> Result<Transaction<'a>, CatalogError> {
183        // For these collections uniqueness is plain equality of the name fields, so the same
184        // predicate answers both "do these two conflict?" and "did this update keep the same key?".
185        let database_unique_fn: fn(&DatabaseValue, &DatabaseValue) -> bool =
186            |a, b| a.name == b.name;
187        let schema_unique_fn: fn(&SchemaValue, &SchemaValue) -> bool =
188            |a, b| a.database_id == b.database_id && a.name == b.name;
189        let role_key: fn(&RoleValue, &RoleValue) -> bool = |a, b| a.name == b.name;
190        let cluster_unique_fn: fn(&ClusterValue, &ClusterValue) -> bool = |a, b| a.name == b.name;
191        let network_policy_unique_fn: fn(&NetworkPolicyValue, &NetworkPolicyValue) -> bool =
192            |a, b| a.name == b.name;
193        let cluster_replica_unique_fn: fn(&ClusterReplicaValue, &ClusterReplicaValue) -> bool =
194            |a, b| a.cluster_id == b.cluster_id && a.name == b.name;
195
196        Ok(Transaction {
197            durable_catalog,
198            databases: TableTransaction::new_with_uniqueness_fn(
199                databases,
200                database_unique_fn,
201                database_unique_fn,
202            )?,
203            schemas: TableTransaction::new_with_uniqueness_fn(
204                schemas,
205                schema_unique_fn,
206                schema_unique_fn,
207            )?,
208            // Temporary items from different sessions may share a name in the
209            // temporary schema (whose durable schema id is a sentinel shared
210            // by every session), so name uniqueness is additionally scoped by
211            // the owning session.
212            items: TableTransaction::new_with_uniqueness_fn(
213                items,
214                |a: &ItemValue, b| {
215                    a.schema_id == b.schema_id
216                        && a.name == b.name
217                        && a.ephemeral_owner_session == b.ephemeral_owner_session
218                        && {
219                            // `item_type` is slow, only compute if needed.
220                            let a_type = a.item_type();
221                            let b_type = b.item_type();
222                            (a_type != CatalogItemType::Type && b_type != CatalogItemType::Type)
223                                || (a_type == CatalogItemType::Type && b_type.conflicts_with_type())
224                                || (b_type == CatalogItemType::Type && a_type.conflicts_with_type())
225                        }
226                },
227                |prev: &ItemValue, next| {
228                    prev.schema_id == next.schema_id
229                        && prev.name == next.name
230                        && prev.ephemeral_owner_session == next.ephemeral_owner_session
231                        // `item_type` is slow, only compute it once name and schema match.
232                        && prev.item_type() == next.item_type()
233                },
234            )?,
235            comments: TableTransaction::new(comments)?,
236            roles: TableTransaction::new_with_uniqueness_fn(roles, role_key, role_key)?,
237            role_auth: TableTransaction::new(role_auth)?,
238            clusters: TableTransaction::new_with_uniqueness_fn(
239                clusters,
240                cluster_unique_fn,
241                cluster_unique_fn,
242            )?,
243            network_policies: TableTransaction::new_with_uniqueness_fn(
244                network_policies,
245                network_policy_unique_fn,
246                network_policy_unique_fn,
247            )?,
248            cluster_replicas: TableTransaction::new_with_uniqueness_fn(
249                cluster_replicas,
250                cluster_replica_unique_fn,
251                cluster_replica_unique_fn,
252            )?,
253            introspection_sources: TableTransaction::new(introspection_sources)?,
254            id_allocator: TableTransaction::new(id_allocator)?,
255            configs: TableTransaction::new(configs)?,
256            settings: TableTransaction::new(settings)?,
257            source_references: TableTransaction::new(source_references)?,
258            system_gid_mapping: TableTransaction::new(system_object_mappings)?,
259            system_configurations: TableTransaction::new(system_configurations)?,
260            cluster_system_configurations: TableTransaction::new(cluster_system_configurations)?,
261            replica_system_configurations: TableTransaction::new(replica_system_configurations)?,
262            default_privileges: TableTransaction::new(default_privileges)?,
263            system_privileges: TableTransaction::new(system_privileges)?,
264            storage_collection_metadata: TableTransaction::new(storage_collection_metadata)?,
265            unfinalized_shards: TableTransaction::new(unfinalized_shards)?,
266            // Uniqueness violations for this value occur at the key rather than
267            // the value (the key is the unit struct `()` so this is a singleton
268            // value).
269            txn_wal_shard: TableTransaction::new(txn_wal_shard)?,
270            audit_log_updates: Vec::new(),
271            upper,
272            op_id: 0,
273            commit_capability: Some(CommitCapability),
274        })
275    }
276
277    pub fn get_item(&self, id: &CatalogItemId) -> Option<Item> {
278        let key = ItemKey { id: *id };
279        self.items
280            .get(&key)
281            .map(|v| DurableType::from_key_value(key, v.clone()))
282    }
283
284    pub fn get_items(&self) -> impl Iterator<Item = Item> + use<> {
285        self.items
286            .items()
287            .into_iter()
288            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
289            .sorted_by_key(|Item { id, .. }| *id)
290    }
291
292    pub fn insert_audit_log_event(&mut self, event: VersionedEvent) {
293        self.insert_audit_log_events([event]);
294    }
295
296    pub fn insert_audit_log_events(&mut self, events: impl IntoIterator<Item = VersionedEvent>) {
297        let events = events
298            .into_iter()
299            .map(|event| (AuditLogKey { event }, Diff::ONE, self.op_id));
300        self.audit_log_updates.extend(events);
301    }
302
303    pub fn insert_user_database(
304        &mut self,
305        database_name: &str,
306        owner_id: RoleId,
307        privileges: Vec<MzAclItem>,
308        temporary_oids: &HashSet<u32>,
309    ) -> Result<(DatabaseId, u32), CatalogError> {
310        let id = self.get_and_increment_id(DATABASE_ID_ALLOC_KEY.to_string())?;
311        let id = DatabaseId::User(id);
312        let oid = self.allocate_oid(temporary_oids)?;
313        self.insert_database(id, database_name, owner_id, privileges, oid)?;
314        Ok((id, oid))
315    }
316
317    pub(crate) fn insert_database(
318        &mut self,
319        id: DatabaseId,
320        database_name: &str,
321        owner_id: RoleId,
322        privileges: Vec<MzAclItem>,
323        oid: u32,
324    ) -> Result<u32, CatalogError> {
325        match self.databases.insert(
326            DatabaseKey { id },
327            DatabaseValue {
328                name: database_name.to_string(),
329                owner_id,
330                privileges,
331                oid,
332            },
333            self.op_id,
334        ) {
335            Ok(_) => Ok(oid),
336            Err(_) => Err(SqlCatalogError::DatabaseAlreadyExists(database_name.to_owned()).into()),
337        }
338    }
339
340    pub fn insert_user_schema(
341        &mut self,
342        database_id: DatabaseId,
343        schema_name: &str,
344        owner_id: RoleId,
345        privileges: Vec<MzAclItem>,
346        temporary_oids: &HashSet<u32>,
347    ) -> Result<(SchemaId, u32), CatalogError> {
348        let id = self.get_and_increment_id(SCHEMA_ID_ALLOC_KEY.to_string())?;
349        let id = SchemaId::User(id);
350        let oid = self.allocate_oid(temporary_oids)?;
351        self.insert_schema(
352            id,
353            Some(database_id),
354            schema_name.to_string(),
355            owner_id,
356            privileges,
357            oid,
358        )?;
359        Ok((id, oid))
360    }
361
362    pub fn insert_system_schema(
363        &mut self,
364        schema_id: u64,
365        schema_name: &str,
366        owner_id: RoleId,
367        privileges: Vec<MzAclItem>,
368        oid: u32,
369    ) -> Result<(), CatalogError> {
370        let id = SchemaId::System(schema_id);
371        self.insert_schema(id, None, schema_name.to_string(), owner_id, privileges, oid)
372    }
373
374    pub(crate) fn insert_schema(
375        &mut self,
376        schema_id: SchemaId,
377        database_id: Option<DatabaseId>,
378        schema_name: String,
379        owner_id: RoleId,
380        privileges: Vec<MzAclItem>,
381        oid: u32,
382    ) -> Result<(), CatalogError> {
383        match self.schemas.insert(
384            SchemaKey { id: schema_id },
385            SchemaValue {
386                database_id,
387                name: schema_name.clone(),
388                owner_id,
389                privileges,
390                oid,
391            },
392            self.op_id,
393        ) {
394            Ok(_) => Ok(()),
395            Err(_) => Err(SqlCatalogError::SchemaAlreadyExists(schema_name).into()),
396        }
397    }
398
399    pub fn insert_builtin_role(
400        &mut self,
401        id: RoleId,
402        name: String,
403        attributes: RoleAttributesRaw,
404        membership: RoleMembership,
405        vars: RoleVars,
406        oid: u32,
407    ) -> Result<RoleId, CatalogError> {
408        soft_assert_or_log!(id.is_builtin(), "ID {id:?} is not builtin");
409        self.insert_role(id, name, attributes, membership, vars, oid)?;
410        Ok(id)
411    }
412
413    pub fn insert_user_role(
414        &mut self,
415        name: String,
416        attributes: RoleAttributesRaw,
417        membership: RoleMembership,
418        vars: RoleVars,
419        temporary_oids: &HashSet<u32>,
420    ) -> Result<(RoleId, u32), CatalogError> {
421        let id = self.get_and_increment_id(USER_ROLE_ID_ALLOC_KEY.to_string())?;
422        let id = RoleId::User(id);
423        let oid = self.allocate_oid(temporary_oids)?;
424        self.insert_role(id, name, attributes, membership, vars, oid)?;
425        Ok((id, oid))
426    }
427
428    fn insert_role(
429        &mut self,
430        id: RoleId,
431        name: String,
432        attributes: RoleAttributesRaw,
433        membership: RoleMembership,
434        vars: RoleVars,
435        oid: u32,
436    ) -> Result<(), CatalogError> {
437        if let Some(ref password) = attributes.password {
438            let hash = mz_auth::hash::scram256_hash(
439                password,
440                &attributes
441                    .scram_iterations
442                    .or_else(|| {
443                        soft_panic_or_log!(
444                            "Hash iterations must be set if a password is provided."
445                        );
446                        None
447                    })
448                    // This should never happen, but rather than panicking we'll
449                    // set a known secure value as a fallback.
450                    .unwrap_or_else(|| NonZeroU32::new(600_000).expect("known valid")),
451            )
452            .expect("password hash should be valid");
453            match self.role_auth.insert(
454                RoleAuthKey { role_id: id },
455                RoleAuthValue {
456                    password_hash: Some(hash),
457                    updated_at: SYSTEM_TIME(),
458                },
459                self.op_id,
460            ) {
461                Ok(_) => {}
462                Err(_) => {
463                    return Err(SqlCatalogError::RoleAlreadyExists(name).into());
464                }
465            }
466        }
467
468        match self.roles.insert(
469            RoleKey { id },
470            RoleValue {
471                name: name.clone(),
472                attributes: attributes.into(),
473                membership,
474                vars,
475                oid,
476            },
477            self.op_id,
478        ) {
479            Ok(_) => Ok(()),
480            Err(_) => Err(SqlCatalogError::RoleAlreadyExists(name).into()),
481        }
482    }
483
484    /// Panics if any introspection source id is not a system id
485    pub fn insert_user_cluster(
486        &mut self,
487        cluster_id: ClusterId,
488        cluster_name: &str,
489        introspection_source_indexes: Vec<(&'static BuiltinLog, CatalogItemId, GlobalId)>,
490        owner_id: RoleId,
491        privileges: Vec<MzAclItem>,
492        config: ClusterConfig,
493        temporary_oids: &HashSet<u32>,
494    ) -> Result<(), CatalogError> {
495        self.insert_cluster(
496            cluster_id,
497            cluster_name,
498            introspection_source_indexes,
499            owner_id,
500            privileges,
501            config,
502            temporary_oids,
503        )
504    }
505
506    /// Panics if any introspection source id is not a system id
507    pub fn insert_system_cluster(
508        &mut self,
509        cluster_name: &str,
510        introspection_source_indexes: Vec<(&'static BuiltinLog, CatalogItemId, GlobalId)>,
511        privileges: Vec<MzAclItem>,
512        owner_id: RoleId,
513        config: ClusterConfig,
514        temporary_oids: &HashSet<u32>,
515    ) -> Result<ClusterId, CatalogError> {
516        let cluster_id = self.get_and_increment_id(SYSTEM_CLUSTER_ID_ALLOC_KEY.to_string())?;
517        let cluster_id = ClusterId::system(cluster_id).ok_or(SqlCatalogError::IdExhaustion)?;
518        self.insert_cluster(
519            cluster_id,
520            cluster_name,
521            introspection_source_indexes,
522            owner_id,
523            privileges,
524            config,
525            temporary_oids,
526        )?;
527        Ok(cluster_id)
528    }
529
530    fn insert_cluster(
531        &mut self,
532        cluster_id: ClusterId,
533        cluster_name: &str,
534        introspection_source_indexes: Vec<(&'static BuiltinLog, CatalogItemId, GlobalId)>,
535        owner_id: RoleId,
536        privileges: Vec<MzAclItem>,
537        config: ClusterConfig,
538        temporary_oids: &HashSet<u32>,
539    ) -> Result<(), CatalogError> {
540        if let Err(_) = self.clusters.insert(
541            ClusterKey { id: cluster_id },
542            ClusterValue {
543                name: cluster_name.to_string(),
544                owner_id,
545                privileges,
546                config,
547            },
548            self.op_id,
549        ) {
550            return Err(SqlCatalogError::ClusterAlreadyExists(cluster_name.to_owned()).into());
551        };
552
553        let amount = usize_to_u64(introspection_source_indexes.len());
554        let oids = self.allocate_oids(amount, temporary_oids)?;
555        let introspection_source_indexes: Vec<_> = introspection_source_indexes
556            .into_iter()
557            .zip_eq(oids)
558            .map(|((builtin, item_id, index_id), oid)| (builtin, item_id, index_id, oid))
559            .collect();
560        for (builtin, item_id, index_id, oid) in introspection_source_indexes {
561            let introspection_source_index = IntrospectionSourceIndex {
562                cluster_id,
563                name: builtin.name.to_string(),
564                item_id,
565                index_id,
566                oid,
567            };
568            let (key, value) = introspection_source_index.into_key_value();
569            self.introspection_sources
570                .insert(key, value, self.op_id)
571                .expect("no uniqueness violation");
572        }
573
574        Ok(())
575    }
576
577    pub fn rename_cluster(
578        &mut self,
579        cluster_id: ClusterId,
580        cluster_name: &str,
581        cluster_to_name: &str,
582    ) -> Result<(), CatalogError> {
583        let key = ClusterKey { id: cluster_id };
584
585        match self.clusters.update(
586            |k, v| {
587                if *k == key {
588                    let mut value = v.clone();
589                    value.name = cluster_to_name.to_string();
590                    Some(value)
591                } else {
592                    None
593                }
594            },
595            self.op_id,
596        )? {
597            Diff::ZERO => Err(SqlCatalogError::UnknownCluster(cluster_name.to_string()).into()),
598            Diff::ONE => Ok(()),
599            n => panic!(
600                "Expected to update single cluster {cluster_name} ({cluster_id}), updated {n}"
601            ),
602        }
603    }
604
605    pub fn rename_cluster_replica(
606        &mut self,
607        replica_id: ReplicaId,
608        replica_name: &QualifiedReplica,
609        replica_to_name: &str,
610    ) -> Result<(), CatalogError> {
611        let key = ClusterReplicaKey { id: replica_id };
612
613        match self.cluster_replicas.update(
614            |k, v| {
615                if *k == key {
616                    let mut value = v.clone();
617                    value.name = replica_to_name.to_string();
618                    Some(value)
619                } else {
620                    None
621                }
622            },
623            self.op_id,
624        )? {
625            Diff::ZERO => {
626                Err(SqlCatalogError::UnknownClusterReplica(replica_name.to_string()).into())
627            }
628            Diff::ONE => Ok(()),
629            n => panic!(
630                "Expected to update single cluster replica {replica_name} ({replica_id}), updated {n}"
631            ),
632        }
633    }
634
635    pub fn insert_cluster_replica_with_id(
636        &mut self,
637        cluster_id: ClusterId,
638        replica_id: ReplicaId,
639        replica_name: &str,
640        config: ReplicaConfig,
641        owner_id: RoleId,
642    ) -> Result<(), CatalogError> {
643        if let Err(_) = self.cluster_replicas.insert(
644            ClusterReplicaKey { id: replica_id },
645            ClusterReplicaValue {
646                cluster_id,
647                name: replica_name.into(),
648                config,
649                owner_id,
650            },
651            self.op_id,
652        ) {
653            let cluster = self
654                .clusters
655                .get(&ClusterKey { id: cluster_id })
656                .expect("cluster exists");
657            return Err(SqlCatalogError::DuplicateReplica(
658                replica_name.to_string(),
659                cluster.name.to_string(),
660            )
661            .into());
662        };
663        Ok(())
664    }
665
666    pub fn insert_user_network_policy(
667        &mut self,
668        name: String,
669        rules: Vec<NetworkPolicyRule>,
670        privileges: Vec<MzAclItem>,
671        owner_id: RoleId,
672        temporary_oids: &HashSet<u32>,
673    ) -> Result<NetworkPolicyId, CatalogError> {
674        let oid = self.allocate_oid(temporary_oids)?;
675        let id = self.get_and_increment_id(USER_NETWORK_POLICY_ID_ALLOC_KEY.to_string())?;
676        let id = NetworkPolicyId::User(id);
677        self.insert_network_policy(id, name, rules, privileges, owner_id, oid)
678    }
679
680    pub fn insert_network_policy(
681        &mut self,
682        id: NetworkPolicyId,
683        name: String,
684        rules: Vec<NetworkPolicyRule>,
685        privileges: Vec<MzAclItem>,
686        owner_id: RoleId,
687        oid: u32,
688    ) -> Result<NetworkPolicyId, CatalogError> {
689        match self.network_policies.insert(
690            NetworkPolicyKey { id },
691            NetworkPolicyValue {
692                name: name.clone(),
693                rules,
694                privileges,
695                owner_id,
696                oid,
697            },
698            self.op_id,
699        ) {
700            Ok(_) => Ok(id),
701            Err(_) => Err(SqlCatalogError::NetworkPolicyAlreadyExists(name).into()),
702        }
703    }
704
705    /// Updates persisted information about persisted introspection source
706    /// indexes.
707    ///
708    /// Panics if provided id is not a system id.
709    pub fn update_introspection_source_index_gids(
710        &mut self,
711        mappings: impl Iterator<
712            Item = (
713                ClusterId,
714                impl Iterator<Item = (String, CatalogItemId, GlobalId, u32)>,
715            ),
716        >,
717    ) -> Result<(), CatalogError> {
718        for (cluster_id, updates) in mappings {
719            for (name, item_id, index_id, oid) in updates {
720                let introspection_source_index = IntrospectionSourceIndex {
721                    cluster_id,
722                    name,
723                    item_id,
724                    index_id,
725                    oid,
726                };
727                let (key, value) = introspection_source_index.into_key_value();
728
729                let prev = self
730                    .introspection_sources
731                    .set(key, Some(value), self.op_id)?;
732                if prev.is_none() {
733                    return Err(SqlCatalogError::FailedBuiltinSchemaMigration(format!(
734                        "{index_id}"
735                    ))
736                    .into());
737                }
738            }
739        }
740        Ok(())
741    }
742
743    pub fn insert_user_item(
744        &mut self,
745        id: CatalogItemId,
746        global_id: GlobalId,
747        schema_id: SchemaId,
748        item_name: &str,
749        create_sql: String,
750        owner_id: RoleId,
751        privileges: Vec<MzAclItem>,
752        temporary_oids: &HashSet<u32>,
753        versions: BTreeMap<RelationVersion, GlobalId>,
754        ephemeral_owner_session: Option<Uuid>,
755    ) -> Result<u32, CatalogError> {
756        let oid = self.allocate_oid(temporary_oids)?;
757        self.insert_item(
758            id,
759            oid,
760            global_id,
761            schema_id,
762            item_name,
763            create_sql,
764            owner_id,
765            privileges,
766            versions,
767            ephemeral_owner_session,
768        )?;
769        Ok(oid)
770    }
771
772    pub fn insert_item(
773        &mut self,
774        id: CatalogItemId,
775        oid: u32,
776        global_id: GlobalId,
777        schema_id: SchemaId,
778        item_name: &str,
779        create_sql: String,
780        owner_id: RoleId,
781        privileges: Vec<MzAclItem>,
782        extra_versions: BTreeMap<RelationVersion, GlobalId>,
783        ephemeral_owner_session: Option<Uuid>,
784    ) -> Result<(), CatalogError> {
785        match self.items.insert(
786            ItemKey { id },
787            ItemValue {
788                schema_id,
789                name: item_name.to_string(),
790                create_sql,
791                owner_id,
792                privileges,
793                oid,
794                global_id,
795                extra_versions,
796                ephemeral_owner_session,
797            },
798            self.op_id,
799        ) {
800            Ok(_) => Ok(()),
801            Err(_) => Err(SqlCatalogError::ItemAlreadyExists(id, item_name.to_owned()).into()),
802        }
803    }
804
805    /// Removes every item owned by an ephemeral session from the transaction,
806    /// along with the durable state a graceful drop would have removed with
807    /// it: storage collection metadata (moving the backing shards to the
808    /// finalization WAL), comments, and source references.
809    ///
810    /// Used to reclaim temporary items when the catalog is opened with write
811    /// intent, at which point every session that could own one is dead.
812    ///
813    /// This must mirror everything the graceful `Op::DropObjects` path
814    /// persists for a temporary item, because nothing revisits the leftovers:
815    /// bootstrap only ever inserts collection metadata for items present in
816    /// the catalog, and shard finalization is driven solely by the
817    /// `unfinalized_shards` collection, so a metadata row that outlives its
818    /// item leaks the persist shard permanently.
819    pub fn remove_ephemeral_items(&mut self) {
820        let mut keys = Vec::new();
821        let mut item_ids = BTreeSet::new();
822        let mut global_ids = BTreeSet::new();
823        for (key, value) in self.items.items() {
824            if value.ephemeral_owner_session.is_none() {
825                continue;
826            }
827            item_ids.insert(key.id);
828            global_ids.insert(value.global_id);
829            global_ids.extend(value.extra_versions.values().copied());
830            keys.push(key.clone());
831        }
832        self.items.delete_by_keys(keys, self.op_id);
833
834        // Move the items' storage mappings to the finalization WAL, like
835        // `StorageCollections::prepare_state` does for a graceful drop. Every
836        // version of a table maps to the same shard, and a shard that a
837        // remaining mapping still references must not be finalized. No
838        // remaining mapping can reference one today (only replacement
839        // materialized views share shards, and those cannot be temporary),
840        // so this mirrors `prepare_state`'s guard defensively.
841        let dropped_mappings = self.delete_collection_metadata(global_ids);
842        let mut dropped_shards: BTreeSet<_> = dropped_mappings
843            .into_iter()
844            .map(|(_, shard)| shard)
845            .collect();
846        let live_shards: BTreeSet<_> = self.get_collection_metadata().into_values().collect();
847        dropped_shards.retain(|shard| {
848            let live = live_shards.contains(shard);
849            if live {
850                soft_panic_or_log!(
851                    "shard {shard} of a reclaimed ephemeral item is still referenced by a \
852                     live collection, not finalizing it"
853                );
854            }
855            !live
856        });
857        self.insert_unfinalized_shards(dropped_shards).expect(
858            "inserting unfinalized shards only fails on duplicate values, which it ignores",
859        );
860
861        // Comments on ephemeral items would otherwise dangle and, because
862        // item ids are reused, could later re-attach to an unrelated object.
863        self.comments.delete(
864            |key, _value| match key.object_id {
865                CommentObjectId::Table(item_id)
866                | CommentObjectId::View(item_id)
867                | CommentObjectId::MaterializedView(item_id)
868                | CommentObjectId::Source(item_id)
869                | CommentObjectId::Sink(item_id)
870                | CommentObjectId::MetricSink(item_id)
871                | CommentObjectId::Index(item_id)
872                | CommentObjectId::Func(item_id)
873                | CommentObjectId::Connection(item_id)
874                | CommentObjectId::Type(item_id)
875                | CommentObjectId::Secret(item_id) => item_ids.contains(&item_id),
876                CommentObjectId::Role(_)
877                | CommentObjectId::Database(_)
878                | CommentObjectId::Schema(_)
879                | CommentObjectId::Cluster(_)
880                | CommentObjectId::ClusterReplica(_)
881                | CommentObjectId::NetworkPolicy(_) => false,
882            },
883            self.op_id,
884        );
885
886        // Only sources hold source references and sources cannot be temporary
887        // today, so this is defensive.
888        self.source_references
889            .delete(|key, _value| item_ids.contains(&key.source_id), self.op_id);
890    }
891
892    pub fn get_and_increment_id(&mut self, key: String) -> Result<u64, CatalogError> {
893        Ok(self.get_and_increment_id_by(key, 1)?.into_element())
894    }
895
896    pub fn get_and_increment_id_by(
897        &mut self,
898        key: String,
899        amount: u64,
900    ) -> Result<Vec<u64>, CatalogError> {
901        assert!(
902            key != SYSTEM_ITEM_ALLOC_KEY || !self.durable_catalog.is_bootstrap_complete(),
903            "system item IDs cannot be allocated outside of bootstrap"
904        );
905
906        let current_id = self
907            .id_allocator
908            .items()
909            .get(&IdAllocKey { name: key.clone() })
910            .unwrap_or_else(|| panic!("{key} id allocator missing"))
911            .next_id;
912        let next_id = current_id
913            .checked_add(amount)
914            .ok_or(SqlCatalogError::IdExhaustion)?;
915        let prev = self.id_allocator.set(
916            IdAllocKey { name: key },
917            Some(IdAllocValue { next_id }),
918            self.op_id,
919        )?;
920        assert_eq!(
921            prev,
922            Some(IdAllocValue {
923                next_id: current_id
924            })
925        );
926        Ok((current_id..next_id).collect())
927    }
928
929    pub fn allocate_system_item_ids(
930        &mut self,
931        amount: u64,
932    ) -> Result<Vec<(CatalogItemId, GlobalId)>, CatalogError> {
933        assert!(
934            !self.durable_catalog.is_bootstrap_complete(),
935            "we can only allocate system item IDs during bootstrap"
936        );
937        Ok(self
938            .get_and_increment_id_by(SYSTEM_ITEM_ALLOC_KEY.to_string(), amount)?
939            .into_iter()
940            // TODO(alter_table): Use separate ID allocators.
941            .map(|x| (CatalogItemId::System(x), GlobalId::System(x)))
942            .collect())
943    }
944
945    /// Allocates an ID for an introspection source index. These IDs are deterministically derived
946    /// from the `cluster_id` and `log_variant`.
947    ///
948    /// Introspection source indexes are a special edge case of items. They are considered system
949    /// items, but they are the only system item that can be created by the user at any time. All
950    /// other system items can only be created by the system during the startup of an upgrade.
951    ///
952    /// Furthermore, all other system item IDs are allocated deterministically in the same order
953    /// during startup. Therefore, all read-only `environmentd` processes during an upgrade will
954    /// allocate the same system IDs to the same items, and due to the way catalog fencing works,
955    /// only one of them can successfully write the IDs down to the catalog. This removes the need
956    /// for `environmentd` processes to coordinate system IDs allocated during read-only mode.
957    ///
958    /// Since introspection IDs can be allocated at any time, read-only instances would either need
959    /// to coordinate across processes when allocating a new ID or allocate them deterministically.
960    /// We opted to allocate the IDs deterministically to avoid the overhead of coordination.
961    ///
962    /// Introspection source index IDs are 64 bit integers, with the following format (not to
963    /// scale):
964    ///
965    /// -------------------------------------------------------------
966    /// | Cluster ID Variant | Cluster ID Inner Value | Log Variant |
967    /// |--------------------|------------------------|-------------|
968    /// |       8-bits       |         48-bits        |   8-bits    |
969    /// -------------------------------------------------------------
970    ///
971    /// Cluster ID Variant:      A unique number indicating the variant of cluster the index belongs
972    ///                          to.
973    /// Cluster ID Inner Value:  A per variant unique number indicating the cluster the index
974    ///                          belongs to.
975    /// Log Variant:             A unique number indicating the log variant this index is on.
976    pub fn allocate_introspection_source_index_id(
977        cluster_id: &ClusterId,
978        log_variant: LogVariant,
979    ) -> (CatalogItemId, GlobalId) {
980        let cluster_variant: u8 = match cluster_id {
981            ClusterId::System(_) => 1,
982            ClusterId::User(_) => 2,
983        };
984        let cluster_id: u64 = cluster_id.inner_id();
985        const CLUSTER_ID_MASK: u64 = 0xFFFF << 48;
986        assert_eq!(
987            CLUSTER_ID_MASK & cluster_id,
988            0,
989            "invalid cluster ID: {cluster_id}"
990        );
991        let log_variant: u8 = match log_variant {
992            LogVariant::Timely(TimelyLog::Operates) => 1,
993            LogVariant::Timely(TimelyLog::Channels) => 2,
994            LogVariant::Timely(TimelyLog::Elapsed) => 3,
995            LogVariant::Timely(TimelyLog::Histogram) => 4,
996            LogVariant::Timely(TimelyLog::Addresses) => 5,
997            LogVariant::Timely(TimelyLog::Parks) => 6,
998            LogVariant::Timely(TimelyLog::MessagesSent) => 7,
999            LogVariant::Timely(TimelyLog::MessagesReceived) => 8,
1000            LogVariant::Timely(TimelyLog::Reachability) => 9,
1001            LogVariant::Timely(TimelyLog::BatchesSent) => 10,
1002            LogVariant::Timely(TimelyLog::BatchesReceived) => 11,
1003            LogVariant::Differential(DifferentialLog::ArrangementBatches) => 12,
1004            LogVariant::Differential(DifferentialLog::ArrangementRecords) => 13,
1005            LogVariant::Differential(DifferentialLog::Sharing) => 14,
1006            LogVariant::Differential(DifferentialLog::BatcherRecords) => 15,
1007            LogVariant::Differential(DifferentialLog::BatcherSize) => 16,
1008            LogVariant::Differential(DifferentialLog::BatcherCapacity) => 17,
1009            LogVariant::Differential(DifferentialLog::BatcherAllocations) => 18,
1010            LogVariant::Compute(ComputeLog::DataflowCurrent) => 19,
1011            LogVariant::Compute(ComputeLog::FrontierCurrent) => 20,
1012            LogVariant::Compute(ComputeLog::PeekCurrent) => 21,
1013            LogVariant::Compute(ComputeLog::PeekDuration) => 22,
1014            LogVariant::Compute(ComputeLog::ImportFrontierCurrent) => 23,
1015            LogVariant::Compute(ComputeLog::ArrangementHeapSize) => 24,
1016            LogVariant::Compute(ComputeLog::ArrangementHeapCapacity) => 25,
1017            LogVariant::Compute(ComputeLog::ArrangementHeapAllocations) => 26,
1018            LogVariant::Compute(ComputeLog::ErrorCount) => 28,
1019            LogVariant::Compute(ComputeLog::HydrationTime) => 29,
1020            LogVariant::Compute(ComputeLog::LirMapping) => 30,
1021            LogVariant::Compute(ComputeLog::DataflowGlobal) => 31,
1022            LogVariant::Compute(ComputeLog::OperatorHydrationStatus) => 32,
1023            LogVariant::Compute(ComputeLog::PrometheusMetrics) => 33,
1024            LogVariant::Compute(ComputeLog::ResourceUsage) => 34,
1025        };
1026
1027        let mut id: u64 = u64::from(cluster_variant) << 56;
1028        id |= cluster_id << 8;
1029        id |= u64::from(log_variant);
1030
1031        (
1032            CatalogItemId::IntrospectionSourceIndex(id),
1033            GlobalId::IntrospectionSourceIndex(id),
1034        )
1035    }
1036
1037    pub fn allocate_user_item_ids(
1038        &mut self,
1039        amount: u64,
1040    ) -> Result<Vec<(CatalogItemId, GlobalId)>, CatalogError> {
1041        Ok(self
1042            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), amount)?
1043            .into_iter()
1044            // TODO(alter_table): Use separate ID allocators.
1045            .map(|x| (CatalogItemId::User(x), GlobalId::User(x)))
1046            .collect())
1047    }
1048
1049    pub fn allocate_system_replica_id(&mut self) -> Result<ReplicaId, CatalogError> {
1050        let id = self.get_and_increment_id(SYSTEM_REPLICA_ID_ALLOC_KEY.to_string())?;
1051        Ok(ReplicaId::System(id))
1052    }
1053
1054    pub fn allocate_audit_log_id(&mut self) -> Result<u64, CatalogError> {
1055        self.get_and_increment_id(AUDIT_LOG_ID_ALLOC_KEY.to_string())
1056    }
1057
1058    /// Allocates `amount` OIDs. OIDs can be recycled if they aren't currently assigned to any
1059    /// object.
1060    #[mz_ore::instrument]
1061    fn allocate_oids(
1062        &mut self,
1063        amount: u64,
1064        temporary_oids: &HashSet<u32>,
1065    ) -> Result<Vec<u32>, CatalogError> {
1066        /// Struct representing an OID for a user object. Allocated OIDs can be recycled, so when we've
1067        /// allocated [`u32::MAX`] we'll wrap back around to [`FIRST_USER_OID`].
1068        struct UserOid(u32);
1069
1070        impl UserOid {
1071            fn new(oid: u32) -> Result<UserOid, anyhow::Error> {
1072                if oid < FIRST_USER_OID {
1073                    Err(anyhow!("invalid user OID {oid}"))
1074                } else {
1075                    Ok(UserOid(oid))
1076                }
1077            }
1078        }
1079
1080        impl std::ops::AddAssign<u32> for UserOid {
1081            fn add_assign(&mut self, rhs: u32) {
1082                let (res, overflow) = self.0.overflowing_add(rhs);
1083                self.0 = if overflow { FIRST_USER_OID + res } else { res };
1084            }
1085        }
1086
1087        if amount > u32::MAX.into() {
1088            return Err(CatalogError::Catalog(SqlCatalogError::OidExhaustion));
1089        }
1090
1091        // This is potentially slow to do everytime we allocate an OID. A faster approach might be
1092        // to have an ID allocator that is updated everytime an OID is allocated or de-allocated.
1093        // However, benchmarking shows that this doesn't make a noticeable difference and the other
1094        // approach requires making sure that allocator always stays in-sync which can be
1095        // error-prone. If DDL starts slowing down, this is a good place to try and optimize.
1096        let mut allocated_oids = HashSet::with_capacity(
1097            self.databases.len()
1098                + self.schemas.len()
1099                + self.roles.len()
1100                + self.items.len()
1101                + self.introspection_sources.len()
1102                + temporary_oids.len(),
1103        );
1104        self.databases.for_values(|_, value| {
1105            allocated_oids.insert(value.oid);
1106        });
1107        self.schemas.for_values(|_, value| {
1108            allocated_oids.insert(value.oid);
1109        });
1110        self.roles.for_values(|_, value| {
1111            allocated_oids.insert(value.oid);
1112        });
1113        self.items.for_values(|_, value| {
1114            allocated_oids.insert(value.oid);
1115        });
1116        self.introspection_sources.for_values(|_, value| {
1117            allocated_oids.insert(value.oid);
1118        });
1119
1120        let is_allocated = |oid| allocated_oids.contains(&oid) || temporary_oids.contains(&oid);
1121
1122        let start_oid: u32 = self
1123            .id_allocator
1124            .items()
1125            .get(&IdAllocKey {
1126                name: OID_ALLOC_KEY.to_string(),
1127            })
1128            .unwrap_or_else(|| panic!("{OID_ALLOC_KEY} id allocator missing"))
1129            .next_id
1130            .try_into()
1131            .expect("we should never persist an oid outside of the u32 range");
1132        let mut current_oid = UserOid::new(start_oid)
1133            .expect("we should never persist an oid outside of user OID range");
1134        let mut oids = Vec::new();
1135        while oids.len() < u64_to_usize(amount) {
1136            if !is_allocated(current_oid.0) {
1137                oids.push(current_oid.0);
1138            }
1139            current_oid += 1;
1140
1141            if current_oid.0 == start_oid && oids.len() < u64_to_usize(amount) {
1142                // We've exhausted all possible OIDs and still don't have `amount`.
1143                return Err(CatalogError::Catalog(SqlCatalogError::OidExhaustion));
1144            }
1145        }
1146
1147        let next_id = current_oid.0;
1148        let prev = self.id_allocator.set(
1149            IdAllocKey {
1150                name: OID_ALLOC_KEY.to_string(),
1151            },
1152            Some(IdAllocValue {
1153                next_id: next_id.into(),
1154            }),
1155            self.op_id,
1156        )?;
1157        assert_eq!(
1158            prev,
1159            Some(IdAllocValue {
1160                next_id: start_oid.into(),
1161            })
1162        );
1163
1164        Ok(oids)
1165    }
1166
1167    /// Allocates a single OID. OIDs can be recycled if they aren't currently assigned to any
1168    /// object.
1169    pub fn allocate_oid(&mut self, temporary_oids: &HashSet<u32>) -> Result<u32, CatalogError> {
1170        self.allocate_oids(1, temporary_oids)
1171            .map(|oids| oids.into_element())
1172    }
1173
1174    /// Exports the current state of this transaction as a [`Snapshot`].
1175    ///
1176    /// This merges each `TableTransaction`'s initial data with its pending
1177    /// changes to produce the current view, then converts back to proto types.
1178    /// Used to persist transaction state between incremental DDL dry runs so
1179    /// the next dry run's fresh `Transaction` starts in sync with the
1180    /// accumulated `CatalogState`.
1181    pub fn current_snapshot(&self) -> Snapshot {
1182        Snapshot {
1183            databases: self.databases.current_items_proto(),
1184            schemas: self.schemas.current_items_proto(),
1185            roles: self.roles.current_items_proto(),
1186            role_auth: self.role_auth.current_items_proto(),
1187            items: self.items.current_items_proto(),
1188            comments: self.comments.current_items_proto(),
1189            clusters: self.clusters.current_items_proto(),
1190            network_policies: self.network_policies.current_items_proto(),
1191            cluster_replicas: self.cluster_replicas.current_items_proto(),
1192            introspection_sources: self.introspection_sources.current_items_proto(),
1193            id_allocator: self.id_allocator.current_items_proto(),
1194            configs: self.configs.current_items_proto(),
1195            settings: self.settings.current_items_proto(),
1196            system_object_mappings: self.system_gid_mapping.current_items_proto(),
1197            system_configurations: self.system_configurations.current_items_proto(),
1198            cluster_system_configurations: self.cluster_system_configurations.current_items_proto(),
1199            replica_system_configurations: self.replica_system_configurations.current_items_proto(),
1200            default_privileges: self.default_privileges.current_items_proto(),
1201            source_references: self.source_references.current_items_proto(),
1202            system_privileges: self.system_privileges.current_items_proto(),
1203            storage_collection_metadata: self.storage_collection_metadata.current_items_proto(),
1204            unfinalized_shards: self.unfinalized_shards.current_items_proto(),
1205            txn_wal_shard: self.txn_wal_shard.current_items_proto(),
1206        }
1207    }
1208
1209    pub(crate) fn insert_id_allocator(
1210        &mut self,
1211        name: String,
1212        next_id: u64,
1213    ) -> Result<(), CatalogError> {
1214        match self.id_allocator.insert(
1215            IdAllocKey { name: name.clone() },
1216            IdAllocValue { next_id },
1217            self.op_id,
1218        ) {
1219            Ok(_) => Ok(()),
1220            Err(_) => Err(SqlCatalogError::IdAllocatorAlreadyExists(name).into()),
1221        }
1222    }
1223
1224    /// Removes the database `id` from the transaction.
1225    ///
1226    /// Returns an error if `id` is not found.
1227    ///
1228    /// Runtime is linear with respect to the total number of databases in the catalog.
1229    /// DO NOT call this function in a loop, use [`Self::remove_databases`] instead.
1230    pub fn remove_database(&mut self, id: &DatabaseId) -> Result<(), CatalogError> {
1231        let prev = self
1232            .databases
1233            .set(DatabaseKey { id: *id }, None, self.op_id)?;
1234        if prev.is_some() {
1235            Ok(())
1236        } else {
1237            Err(SqlCatalogError::UnknownDatabase(id.to_string()).into())
1238        }
1239    }
1240
1241    /// Removes all databases in `databases` from the transaction.
1242    ///
1243    /// Returns an error if any id in `databases` is not found.
1244    ///
1245    /// NOTE: On error, there still may be some databases removed from the transaction. It
1246    /// is up to the caller to either abort the transaction or commit.
1247    pub fn remove_databases(
1248        &mut self,
1249        databases: &BTreeSet<DatabaseId>,
1250    ) -> Result<(), CatalogError> {
1251        if databases.is_empty() {
1252            return Ok(());
1253        }
1254
1255        let to_remove = databases
1256            .iter()
1257            .map(|id| (DatabaseKey { id: *id }, None))
1258            .collect();
1259        let mut prev = self.databases.set_many(to_remove, self.op_id)?;
1260        prev.retain(|_k, val| val.is_none());
1261
1262        if !prev.is_empty() {
1263            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1264            return Err(SqlCatalogError::UnknownDatabase(err).into());
1265        }
1266
1267        Ok(())
1268    }
1269
1270    /// Removes the schema identified by `database_id` and `schema_id` from the transaction.
1271    ///
1272    /// Returns an error if `(database_id, schema_id)` is not found.
1273    ///
1274    /// Runtime is linear with respect to the total number of schemas in the catalog.
1275    /// DO NOT call this function in a loop, use [`Self::remove_schemas`] instead.
1276    pub fn remove_schema(
1277        &mut self,
1278        database_id: &Option<DatabaseId>,
1279        schema_id: &SchemaId,
1280    ) -> Result<(), CatalogError> {
1281        let prev = self
1282            .schemas
1283            .set(SchemaKey { id: *schema_id }, None, self.op_id)?;
1284        if prev.is_some() {
1285            Ok(())
1286        } else {
1287            let database_name = match database_id {
1288                Some(id) => format!("{id}."),
1289                None => "".to_string(),
1290            };
1291            Err(SqlCatalogError::UnknownSchema(format!("{}.{}", database_name, schema_id)).into())
1292        }
1293    }
1294
1295    /// Removes all schemas in `schemas` from the transaction.
1296    ///
1297    /// Returns an error if any id in `schemas` is not found.
1298    ///
1299    /// NOTE: On error, there still may be some schemas removed from the transaction. It
1300    /// is up to the caller to either abort the transaction or commit.
1301    pub fn remove_schemas(
1302        &mut self,
1303        schemas: &BTreeMap<SchemaId, ResolvedDatabaseSpecifier>,
1304    ) -> Result<(), CatalogError> {
1305        if schemas.is_empty() {
1306            return Ok(());
1307        }
1308
1309        let to_remove = schemas
1310            .iter()
1311            .map(|(schema_id, _)| (SchemaKey { id: *schema_id }, None))
1312            .collect();
1313        let mut prev = self.schemas.set_many(to_remove, self.op_id)?;
1314        prev.retain(|_k, v| v.is_none());
1315
1316        if !prev.is_empty() {
1317            let err = prev
1318                .keys()
1319                .map(|k| {
1320                    let db_spec = schemas.get(&k.id).expect("should_exist");
1321                    let db_name = match db_spec {
1322                        ResolvedDatabaseSpecifier::Id(id) => format!("{id}."),
1323                        ResolvedDatabaseSpecifier::Ambient => "".to_string(),
1324                    };
1325                    format!("{}.{}", db_name, k.id)
1326                })
1327                .join(", ");
1328
1329            return Err(SqlCatalogError::UnknownSchema(err).into());
1330        }
1331
1332        Ok(())
1333    }
1334
1335    pub fn remove_source_references(
1336        &mut self,
1337        source_id: CatalogItemId,
1338    ) -> Result<(), CatalogError> {
1339        let deleted = self
1340            .source_references
1341            .delete_by_key(SourceReferencesKey { source_id }, self.op_id)
1342            .is_some();
1343        if deleted {
1344            Ok(())
1345        } else {
1346            Err(SqlCatalogError::UnknownItem(source_id.to_string()).into())
1347        }
1348    }
1349
1350    /// Removes all user roles in `roles` from the transaction.
1351    ///
1352    /// Returns an error if any id in `roles` is not found.
1353    ///
1354    /// NOTE: On error, there still may be some roles removed from the transaction. It
1355    /// is up to the caller to either abort the transaction or commit.
1356    pub fn remove_user_roles(&mut self, roles: &BTreeSet<RoleId>) -> Result<(), CatalogError> {
1357        assert!(
1358            roles.iter().all(|id| id.is_user()),
1359            "cannot delete non-user roles"
1360        );
1361        self.remove_roles(roles)
1362    }
1363
1364    /// Removes all roles in `roles` from the transaction.
1365    ///
1366    /// Returns an error if any id in `roles` is not found.
1367    ///
1368    /// NOTE: On error, there still may be some roles removed from the transaction. It
1369    /// is up to the caller to either abort the transaction or commit.
1370    pub fn remove_roles(&mut self, roles: &BTreeSet<RoleId>) -> Result<(), CatalogError> {
1371        if roles.is_empty() {
1372            return Ok(());
1373        }
1374
1375        let to_remove_keys = roles
1376            .iter()
1377            .map(|role_id| RoleKey { id: *role_id })
1378            .collect::<Vec<_>>();
1379
1380        let to_remove_roles = to_remove_keys
1381            .iter()
1382            .map(|role_key| (role_key.clone(), None))
1383            .collect();
1384
1385        let mut prev = self.roles.set_many(to_remove_roles, self.op_id)?;
1386
1387        let to_remove_role_auth = to_remove_keys
1388            .iter()
1389            .map(|role_key| {
1390                (
1391                    RoleAuthKey {
1392                        role_id: role_key.id,
1393                    },
1394                    None,
1395                )
1396            })
1397            .collect();
1398
1399        let mut role_auth_prev = self.role_auth.set_many(to_remove_role_auth, self.op_id)?;
1400
1401        prev.retain(|_k, v| v.is_none());
1402        if !prev.is_empty() {
1403            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1404            return Err(SqlCatalogError::UnknownRole(err).into());
1405        }
1406
1407        role_auth_prev.retain(|_k, v| v.is_none());
1408        // The reason we don't to the same check as above is that the role auth table
1409        // is not required to have all roles in the role table.
1410
1411        Ok(())
1412    }
1413
1414    /// Removes all cluster in `clusters` from the transaction.
1415    ///
1416    /// Returns an error if any id in `clusters` is not found.
1417    ///
1418    /// NOTE: On error, there still may be some clusters removed from the transaction. It is up to
1419    /// the caller to either abort the transaction or commit.
1420    pub fn remove_clusters(&mut self, clusters: &BTreeSet<ClusterId>) -> Result<(), CatalogError> {
1421        if clusters.is_empty() {
1422            return Ok(());
1423        }
1424
1425        let to_remove = clusters
1426            .iter()
1427            .map(|cluster_id| (ClusterKey { id: *cluster_id }, None))
1428            .collect();
1429        let mut prev = self.clusters.set_many(to_remove, self.op_id)?;
1430
1431        prev.retain(|_k, v| v.is_none());
1432        if !prev.is_empty() {
1433            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1434            return Err(SqlCatalogError::UnknownCluster(err).into());
1435        }
1436
1437        // Cascade delete introspection sources and cluster replicas.
1438        //
1439        // TODO(benesch): this doesn't seem right. Cascade deletions should
1440        // be entirely the domain of the higher catalog layer, not the
1441        // storage layer.
1442        self.cluster_replicas
1443            .delete(|_k, v| clusters.contains(&v.cluster_id), self.op_id);
1444        self.introspection_sources
1445            .delete(|k, _v| clusters.contains(&k.cluster_id), self.op_id);
1446
1447        Ok(())
1448    }
1449
1450    /// Removes the cluster replica `id` from the transaction.
1451    ///
1452    /// Returns an error if `id` is not found.
1453    ///
1454    /// Runtime is linear with respect to the total number of cluster replicas in the catalog.
1455    /// DO NOT call this function in a loop, use [`Self::remove_cluster_replicas`] instead.
1456    pub fn remove_cluster_replica(&mut self, id: ReplicaId) -> Result<(), CatalogError> {
1457        let deleted = self
1458            .cluster_replicas
1459            .delete_by_key(ClusterReplicaKey { id }, self.op_id)
1460            .is_some();
1461        if deleted {
1462            Ok(())
1463        } else {
1464            Err(SqlCatalogError::UnknownClusterReplica(id.to_string()).into())
1465        }
1466    }
1467
1468    /// Removes all cluster replicas in `replicas` from the transaction.
1469    ///
1470    /// Returns an error if any id in `replicas` is not found.
1471    ///
1472    /// NOTE: On error, there still may be some cluster replicas removed from the transaction. It
1473    /// is up to the caller to either abort the transaction or commit.
1474    pub fn remove_cluster_replicas(
1475        &mut self,
1476        replicas: &BTreeSet<ReplicaId>,
1477    ) -> Result<(), CatalogError> {
1478        if replicas.is_empty() {
1479            return Ok(());
1480        }
1481
1482        let to_remove = replicas
1483            .iter()
1484            .map(|replica_id| (ClusterReplicaKey { id: *replica_id }, None))
1485            .collect();
1486        let mut prev = self.cluster_replicas.set_many(to_remove, self.op_id)?;
1487
1488        prev.retain(|_k, v| v.is_none());
1489        if !prev.is_empty() {
1490            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1491            return Err(SqlCatalogError::UnknownClusterReplica(err).into());
1492        }
1493
1494        Ok(())
1495    }
1496
1497    /// Removes item `id` from the transaction.
1498    ///
1499    /// Returns an error if `id` is not found.
1500    ///
1501    /// Runtime is linear with respect to the total number of items in the catalog.
1502    /// DO NOT call this function in a loop, use [`Self::remove_items`] instead.
1503    pub fn remove_item(&mut self, id: CatalogItemId) -> Result<(), CatalogError> {
1504        let prev = self.items.set(ItemKey { id }, None, self.op_id)?;
1505        if prev.is_some() {
1506            Ok(())
1507        } else {
1508            Err(SqlCatalogError::UnknownItem(id.to_string()).into())
1509        }
1510    }
1511
1512    /// Removes all items in `ids` from the transaction.
1513    ///
1514    /// Returns an error if any id in `ids` is not found.
1515    ///
1516    /// NOTE: On error, there still may be some items removed from the transaction. It is
1517    /// up to the caller to either abort the transaction or commit.
1518    pub fn remove_items(&mut self, ids: &BTreeSet<CatalogItemId>) -> Result<(), CatalogError> {
1519        if ids.is_empty() {
1520            return Ok(());
1521        }
1522
1523        let ks: Vec<_> = ids.clone().into_iter().map(|id| ItemKey { id }).collect();
1524        let n = self.items.delete_by_keys(ks, self.op_id).len();
1525        if n == ids.len() {
1526            Ok(())
1527        } else {
1528            let item_ids = self.items.items().keys().map(|k| k.id).collect();
1529            let mut unknown = ids.difference(&item_ids);
1530            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1531        }
1532    }
1533
1534    /// Removes all system object mappings in `descriptions` from the transaction.
1535    ///
1536    /// Returns an error if any description in `descriptions` is not found.
1537    ///
1538    /// NOTE: On error, there still may be some items removed from the transaction. It is
1539    /// up to the caller to either abort the transaction or commit.
1540    pub fn remove_system_object_mappings(
1541        &mut self,
1542        descriptions: BTreeSet<SystemObjectDescription>,
1543    ) -> Result<(), CatalogError> {
1544        if descriptions.is_empty() {
1545            return Ok(());
1546        }
1547
1548        let ks: Vec<_> = descriptions
1549            .clone()
1550            .into_iter()
1551            .map(|desc| GidMappingKey {
1552                schema_name: desc.schema_name,
1553                object_type: desc.object_type,
1554                object_name: desc.object_name,
1555            })
1556            .collect();
1557        let n = self.system_gid_mapping.delete_by_keys(ks, self.op_id).len();
1558
1559        if n == descriptions.len() {
1560            Ok(())
1561        } else {
1562            let item_descriptions = self
1563                .system_gid_mapping
1564                .items()
1565                .keys()
1566                .map(|k| SystemObjectDescription {
1567                    schema_name: k.schema_name.clone(),
1568                    object_type: k.object_type.clone(),
1569                    object_name: k.object_name.clone(),
1570                })
1571                .collect();
1572            let mut unknown = descriptions.difference(&item_descriptions).map(|desc| {
1573                format!(
1574                    "{} {}.{}",
1575                    desc.object_type, desc.schema_name, desc.object_name
1576                )
1577            });
1578            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1579        }
1580    }
1581
1582    /// Removes all introspection source indexes in `indexes` from the transaction.
1583    ///
1584    /// Returns an error if any index in `indexes` is not found.
1585    ///
1586    /// NOTE: On error, there still may be some indexes removed from the transaction. It is
1587    /// up to the caller to either abort the transaction or commit.
1588    pub fn remove_introspection_source_indexes(
1589        &mut self,
1590        introspection_source_indexes: BTreeSet<(ClusterId, String)>,
1591    ) -> Result<(), CatalogError> {
1592        if introspection_source_indexes.is_empty() {
1593            return Ok(());
1594        }
1595
1596        let ks: Vec<_> = introspection_source_indexes
1597            .clone()
1598            .into_iter()
1599            .map(|(cluster_id, name)| ClusterIntrospectionSourceIndexKey { cluster_id, name })
1600            .collect();
1601        let n = self
1602            .introspection_sources
1603            .delete_by_keys(ks, self.op_id)
1604            .len();
1605        if n == introspection_source_indexes.len() {
1606            Ok(())
1607        } else {
1608            let txn_indexes = self
1609                .introspection_sources
1610                .items()
1611                .keys()
1612                .map(|k| (k.cluster_id, k.name.clone()))
1613                .collect();
1614            let mut unknown = introspection_source_indexes
1615                .difference(&txn_indexes)
1616                .map(|(cluster_id, name)| format!("{cluster_id} {name}"));
1617            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1618        }
1619    }
1620
1621    /// Updates item `id` in the transaction to `item_name` and `item`.
1622    ///
1623    /// Returns an error if `id` is not found.
1624    ///
1625    /// Runtime is linear with respect to the total number of items in the catalog.
1626    /// DO NOT call this function in a loop, use [`Self::update_items`] instead.
1627    pub fn update_item(&mut self, id: CatalogItemId, item: Item) -> Result<(), CatalogError> {
1628        let updated =
1629            self.items
1630                .update_by_key(ItemKey { id }, item.into_key_value().1, self.op_id)?;
1631        if updated {
1632            Ok(())
1633        } else {
1634            Err(SqlCatalogError::UnknownItem(id.to_string()).into())
1635        }
1636    }
1637
1638    /// Updates all items with ids matching the keys of `items` in the transaction, to the
1639    /// corresponding value in `items`.
1640    ///
1641    /// Returns an error if any id in `items` is not found.
1642    ///
1643    /// NOTE: On error, there still may be some items updated in the transaction. It is
1644    /// up to the caller to either abort the transaction or commit.
1645    pub fn update_items(
1646        &mut self,
1647        items: BTreeMap<CatalogItemId, Item>,
1648    ) -> Result<(), CatalogError> {
1649        if items.is_empty() {
1650            return Ok(());
1651        }
1652
1653        let update_ids: BTreeSet<_> = items.keys().cloned().collect();
1654        let kvs: Vec<_> = items
1655            .clone()
1656            .into_iter()
1657            .map(|(id, item)| (ItemKey { id }, item.into_key_value().1))
1658            .collect();
1659        let n = self.items.update_by_keys(kvs, self.op_id)?;
1660        let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize");
1661        if n == update_ids.len() {
1662            Ok(())
1663        } else {
1664            let item_ids: BTreeSet<_> = self.items.items().keys().map(|k| k.id).collect();
1665            let mut unknown = update_ids.difference(&item_ids);
1666            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1667        }
1668    }
1669
1670    /// Updates role `id` in the transaction to `role`.
1671    ///
1672    /// Returns an error if `id` is not found.
1673    ///
1674    /// Runtime is linear with respect to the total number of items in the catalog.
1675    /// DO NOT call this function in a loop, implement and use some `Self::update_roles` instead.
1676    /// You should model it after [`Self::update_items`].
1677    pub fn update_role(
1678        &mut self,
1679        id: RoleId,
1680        role: Role,
1681        password: PasswordAction,
1682    ) -> Result<(), CatalogError> {
1683        let key = RoleKey { id };
1684        if self.roles.get(&key).is_some() {
1685            let auth_key = RoleAuthKey { role_id: id };
1686
1687            match password {
1688                PasswordAction::Set(new_password) => {
1689                    let hash = mz_auth::hash::scram256_hash(
1690                        &new_password.password,
1691                        &new_password.scram_iterations,
1692                    )
1693                    .expect("password hash should be valid");
1694                    let value = RoleAuthValue {
1695                        password_hash: Some(hash),
1696                        updated_at: SYSTEM_TIME(),
1697                    };
1698
1699                    if self.role_auth.get(&auth_key).is_some() {
1700                        self.role_auth
1701                            .update_by_key(auth_key.clone(), value, self.op_id)?;
1702                    } else {
1703                        self.role_auth.insert(auth_key.clone(), value, self.op_id)?;
1704                    }
1705                }
1706                PasswordAction::Clear => {
1707                    let value = RoleAuthValue {
1708                        password_hash: None,
1709                        updated_at: SYSTEM_TIME(),
1710                    };
1711                    if self.role_auth.get(&auth_key).is_some() {
1712                        self.role_auth
1713                            .update_by_key(auth_key.clone(), value, self.op_id)?;
1714                    }
1715                }
1716                PasswordAction::NoChange => {}
1717            }
1718
1719            self.roles
1720                .update_by_key(key, role.into_key_value().1, self.op_id)?;
1721
1722            Ok(())
1723        } else {
1724            Err(SqlCatalogError::UnknownRole(id.to_string()).into())
1725        }
1726    }
1727
1728    /// Updates all [`Role`]s with ids matching the keys of `roles` in the transaction, to the
1729    /// corresponding value in `roles`.
1730    ///
1731    /// This function does *not* write role_authentication information to the catalog.
1732    /// It is purely for updating the role itself.
1733    ///
1734    /// Returns an error if any id in `roles` is not found.
1735    ///
1736    /// NOTE: On error, there still may be some roles updated in the transaction. It is
1737    /// up to the caller to either abort the transaction or commit.
1738    pub fn update_roles_without_auth(
1739        &mut self,
1740        roles: BTreeMap<RoleId, Role>,
1741    ) -> Result<(), CatalogError> {
1742        if roles.is_empty() {
1743            return Ok(());
1744        }
1745
1746        let update_role_ids: BTreeSet<_> = roles.keys().cloned().collect();
1747        let kvs: Vec<_> = roles
1748            .into_iter()
1749            .map(|(id, role)| (RoleKey { id }, role.into_key_value().1))
1750            .collect();
1751        let n = self.roles.update_by_keys(kvs, self.op_id)?;
1752        let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize");
1753
1754        if n == update_role_ids.len() {
1755            Ok(())
1756        } else {
1757            let role_ids: BTreeSet<_> = self.roles.items().keys().map(|k| k.id).collect();
1758            let mut unknown = update_role_ids.difference(&role_ids);
1759            Err(SqlCatalogError::UnknownRole(unknown.join(", ")).into())
1760        }
1761    }
1762
1763    /// Updates persisted mapping from system objects to global IDs and fingerprints. Each element
1764    /// of `mappings` should be (old-global-id, new-system-object-mapping).
1765    ///
1766    /// Panics if provided id is not a system id.
1767    pub fn update_system_object_mappings(
1768        &mut self,
1769        mappings: BTreeMap<CatalogItemId, SystemObjectMapping>,
1770    ) -> Result<(), CatalogError> {
1771        if mappings.is_empty() {
1772            return Ok(());
1773        }
1774
1775        let n = self.system_gid_mapping.update(
1776            |_k, v| {
1777                if let Some(mapping) = mappings.get(&CatalogItemId::from(v.catalog_id)) {
1778                    let (_, new_value) = mapping.clone().into_key_value();
1779                    Some(new_value)
1780                } else {
1781                    None
1782                }
1783            },
1784            self.op_id,
1785        )?;
1786
1787        if usize::try_from(n.into_inner()).expect("update diff should fit into usize")
1788            != mappings.len()
1789        {
1790            let id_str = mappings.keys().map(|id| id.to_string()).join(",");
1791            return Err(SqlCatalogError::FailedBuiltinSchemaMigration(id_str).into());
1792        }
1793
1794        Ok(())
1795    }
1796
1797    /// Updates cluster `id` in the transaction to `cluster`.
1798    ///
1799    /// Returns an error if `id` is not found.
1800    ///
1801    /// Runtime is linear with respect to the total number of clusters in the catalog.
1802    /// DO NOT call this function in a loop.
1803    pub fn update_cluster(&mut self, id: ClusterId, cluster: Cluster) -> Result<(), CatalogError> {
1804        let updated = self.clusters.update_by_key(
1805            ClusterKey { id },
1806            cluster.into_key_value().1,
1807            self.op_id,
1808        )?;
1809        if updated {
1810            Ok(())
1811        } else {
1812            Err(SqlCatalogError::UnknownCluster(id.to_string()).into())
1813        }
1814    }
1815
1816    /// Updates cluster replica `replica_id` in the transaction to `replica`.
1817    ///
1818    /// Returns an error if `replica_id` is not found.
1819    ///
1820    /// Runtime is linear with respect to the total number of cluster replicas in the catalog.
1821    /// DO NOT call this function in a loop.
1822    pub fn update_cluster_replica(
1823        &mut self,
1824        replica_id: ReplicaId,
1825        replica: ClusterReplica,
1826    ) -> Result<(), CatalogError> {
1827        let updated = self.cluster_replicas.update_by_key(
1828            ClusterReplicaKey { id: replica_id },
1829            replica.into_key_value().1,
1830            self.op_id,
1831        )?;
1832        if updated {
1833            Ok(())
1834        } else {
1835            Err(SqlCatalogError::UnknownClusterReplica(replica_id.to_string()).into())
1836        }
1837    }
1838
1839    /// Updates database `id` in the transaction to `database`.
1840    ///
1841    /// Returns an error if `id` is not found.
1842    ///
1843    /// Runtime is linear with respect to the total number of databases in the catalog.
1844    /// DO NOT call this function in a loop.
1845    pub fn update_database(
1846        &mut self,
1847        id: DatabaseId,
1848        database: Database,
1849    ) -> Result<(), CatalogError> {
1850        let updated = self.databases.update_by_key(
1851            DatabaseKey { id },
1852            database.into_key_value().1,
1853            self.op_id,
1854        )?;
1855        if updated {
1856            Ok(())
1857        } else {
1858            Err(SqlCatalogError::UnknownDatabase(id.to_string()).into())
1859        }
1860    }
1861
1862    /// Updates schema `schema_id` in the transaction to `schema`.
1863    ///
1864    /// Returns an error if `schema_id` is not found.
1865    ///
1866    /// Runtime is linear with respect to the total number of schemas in the catalog.
1867    /// DO NOT call this function in a loop.
1868    pub fn update_schema(
1869        &mut self,
1870        schema_id: SchemaId,
1871        schema: Schema,
1872    ) -> Result<(), CatalogError> {
1873        let updated = self.schemas.update_by_key(
1874            SchemaKey { id: schema_id },
1875            schema.into_key_value().1,
1876            self.op_id,
1877        )?;
1878        if updated {
1879            Ok(())
1880        } else {
1881            Err(SqlCatalogError::UnknownSchema(schema_id.to_string()).into())
1882        }
1883    }
1884
1885    /// Updates `network_policy_id` in the transaction to `network policy`.
1886    ///
1887    /// Returns an error if `id` is not found.
1888    ///
1889    /// Runtime is linear with respect to the total number of databases in the catalog.
1890    /// DO NOT call this function in a loop.
1891    pub fn update_network_policy(
1892        &mut self,
1893        id: NetworkPolicyId,
1894        network_policy: NetworkPolicy,
1895    ) -> Result<(), CatalogError> {
1896        let updated = self.network_policies.update_by_key(
1897            NetworkPolicyKey { id },
1898            network_policy.into_key_value().1,
1899            self.op_id,
1900        )?;
1901        if updated {
1902            Ok(())
1903        } else {
1904            Err(SqlCatalogError::UnknownNetworkPolicy(id.to_string()).into())
1905        }
1906    }
1907    /// Removes all network policies in `network policies` from the transaction.
1908    ///
1909    /// Returns an error if any id in `network policy` is not found.
1910    ///
1911    /// NOTE: On error, there still may be some roles removed from the transaction. It
1912    /// is up to the caller to either abort the transaction or commit.
1913    pub fn remove_network_policies(
1914        &mut self,
1915        network_policies: &BTreeSet<NetworkPolicyId>,
1916    ) -> Result<(), CatalogError> {
1917        if network_policies.is_empty() {
1918            return Ok(());
1919        }
1920
1921        let to_remove = network_policies
1922            .iter()
1923            .map(|policy_id| (NetworkPolicyKey { id: *policy_id }, None))
1924            .collect();
1925        let mut prev = self.network_policies.set_many(to_remove, self.op_id)?;
1926        assert!(
1927            prev.iter().all(|(k, _)| k.id.is_user()),
1928            "cannot delete non-user network policy"
1929        );
1930
1931        prev.retain(|_k, v| v.is_none());
1932        if !prev.is_empty() {
1933            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1934            return Err(SqlCatalogError::UnknownNetworkPolicy(err).into());
1935        }
1936
1937        Ok(())
1938    }
1939    /// Set persisted default privilege.
1940    ///
1941    /// DO NOT call this function in a loop, use [`Self::set_default_privileges`] instead.
1942    pub fn set_default_privilege(
1943        &mut self,
1944        role_id: RoleId,
1945        database_id: Option<DatabaseId>,
1946        schema_id: Option<SchemaId>,
1947        object_type: ObjectType,
1948        grantee: RoleId,
1949        privileges: Option<AclMode>,
1950    ) -> Result<(), CatalogError> {
1951        self.default_privileges.set(
1952            DefaultPrivilegesKey {
1953                role_id,
1954                database_id,
1955                schema_id,
1956                object_type,
1957                grantee,
1958            },
1959            privileges.map(|privileges| DefaultPrivilegesValue { privileges }),
1960            self.op_id,
1961        )?;
1962        Ok(())
1963    }
1964
1965    /// Set persisted default privileges.
1966    pub fn set_default_privileges(
1967        &mut self,
1968        default_privileges: Vec<DefaultPrivilege>,
1969    ) -> Result<(), CatalogError> {
1970        if default_privileges.is_empty() {
1971            return Ok(());
1972        }
1973
1974        let default_privileges = default_privileges
1975            .into_iter()
1976            .map(DurableType::into_key_value)
1977            .map(|(k, v)| (k, Some(v)))
1978            .collect();
1979        self.default_privileges
1980            .set_many(default_privileges, self.op_id)?;
1981        Ok(())
1982    }
1983
1984    /// Set persisted system privilege.
1985    ///
1986    /// DO NOT call this function in a loop, use [`Self::set_system_privileges`] instead.
1987    pub fn set_system_privilege(
1988        &mut self,
1989        grantee: RoleId,
1990        grantor: RoleId,
1991        acl_mode: Option<AclMode>,
1992    ) -> Result<(), CatalogError> {
1993        self.system_privileges.set(
1994            SystemPrivilegesKey { grantee, grantor },
1995            acl_mode.map(|acl_mode| SystemPrivilegesValue { acl_mode }),
1996            self.op_id,
1997        )?;
1998        Ok(())
1999    }
2000
2001    /// Set persisted system privileges.
2002    pub fn set_system_privileges(
2003        &mut self,
2004        system_privileges: Vec<MzAclItem>,
2005    ) -> Result<(), CatalogError> {
2006        if system_privileges.is_empty() {
2007            return Ok(());
2008        }
2009
2010        let system_privileges = system_privileges
2011            .into_iter()
2012            .map(DurableType::into_key_value)
2013            .map(|(k, v)| (k, Some(v)))
2014            .collect();
2015        self.system_privileges
2016            .set_many(system_privileges, self.op_id)?;
2017        Ok(())
2018    }
2019
2020    /// Set persisted setting.
2021    pub fn set_setting(&mut self, name: String, value: Option<String>) -> Result<(), CatalogError> {
2022        self.settings.set(
2023            SettingKey { name },
2024            value.map(|value| SettingValue { value }),
2025            self.op_id,
2026        )?;
2027        Ok(())
2028    }
2029
2030    pub fn set_catalog_content_version(&mut self, version: String) -> Result<(), CatalogError> {
2031        self.set_setting(CATALOG_CONTENT_VERSION_KEY.to_string(), Some(version))
2032    }
2033
2034    /// Insert persisted introspection source index.
2035    pub fn insert_introspection_source_indexes(
2036        &mut self,
2037        introspection_source_indexes: Vec<(ClusterId, String, CatalogItemId, GlobalId)>,
2038        temporary_oids: &HashSet<u32>,
2039    ) -> Result<(), CatalogError> {
2040        if introspection_source_indexes.is_empty() {
2041            return Ok(());
2042        }
2043
2044        let amount = usize_to_u64(introspection_source_indexes.len());
2045        let oids = self.allocate_oids(amount, temporary_oids)?;
2046        let introspection_source_indexes: Vec<_> = introspection_source_indexes
2047            .into_iter()
2048            .zip_eq(oids)
2049            .map(
2050                |((cluster_id, name, item_id, index_id), oid)| IntrospectionSourceIndex {
2051                    cluster_id,
2052                    name,
2053                    item_id,
2054                    index_id,
2055                    oid,
2056                },
2057            )
2058            .collect();
2059
2060        for introspection_source_index in introspection_source_indexes {
2061            let (key, value) = introspection_source_index.into_key_value();
2062            self.introspection_sources.insert(key, value, self.op_id)?;
2063        }
2064
2065        Ok(())
2066    }
2067
2068    /// Set persisted system object mappings.
2069    pub fn set_system_object_mappings(
2070        &mut self,
2071        mappings: Vec<SystemObjectMapping>,
2072    ) -> Result<(), CatalogError> {
2073        if mappings.is_empty() {
2074            return Ok(());
2075        }
2076
2077        let mappings = mappings
2078            .into_iter()
2079            .map(DurableType::into_key_value)
2080            .map(|(k, v)| (k, Some(v)))
2081            .collect();
2082        self.system_gid_mapping.set_many(mappings, self.op_id)?;
2083        Ok(())
2084    }
2085
2086    /// Set persisted replica.
2087    pub fn set_replicas(&mut self, replicas: Vec<ClusterReplica>) -> Result<(), CatalogError> {
2088        if replicas.is_empty() {
2089            return Ok(());
2090        }
2091
2092        let replicas = replicas
2093            .into_iter()
2094            .map(DurableType::into_key_value)
2095            .map(|(k, v)| (k, Some(v)))
2096            .collect();
2097        self.cluster_replicas.set_many(replicas, self.op_id)?;
2098        Ok(())
2099    }
2100
2101    /// Set persisted configuration.
2102    pub fn set_config(&mut self, key: String, value: Option<u64>) -> Result<(), CatalogError> {
2103        match value {
2104            Some(value) => {
2105                let config = Config { key, value };
2106                let (key, value) = config.into_key_value();
2107                self.configs.set(key, Some(value), self.op_id)?;
2108            }
2109            None => {
2110                self.configs.set(ConfigKey { key }, None, self.op_id)?;
2111            }
2112        }
2113        Ok(())
2114    }
2115
2116    /// Get the value of a persisted config.
2117    pub fn get_config(&self, key: String) -> Option<u64> {
2118        self.configs
2119            .get(&ConfigKey { key })
2120            .map(|entry| entry.value)
2121    }
2122
2123    /// Get the value of a persisted setting.
2124    pub fn get_setting(&self, name: String) -> Option<&str> {
2125        self.settings
2126            .get(&SettingKey { name })
2127            .map(|entry| &*entry.value)
2128    }
2129
2130    pub fn get_builtin_migration_shard(&self) -> Option<ShardId> {
2131        self.get_setting(BUILTIN_MIGRATION_SHARD_KEY.to_string())
2132            .map(|shard_id| shard_id.parse().expect("valid ShardId"))
2133    }
2134
2135    pub fn set_builtin_migration_shard(&mut self, shard_id: ShardId) -> Result<(), CatalogError> {
2136        self.set_setting(
2137            BUILTIN_MIGRATION_SHARD_KEY.to_string(),
2138            Some(shard_id.to_string()),
2139        )
2140    }
2141
2142    pub fn get_expression_cache_shard(&self) -> Option<ShardId> {
2143        self.get_setting(EXPRESSION_CACHE_SHARD_KEY.to_string())
2144            .map(|shard_id| shard_id.parse().expect("valid ShardId"))
2145    }
2146
2147    pub fn set_expression_cache_shard(&mut self, shard_id: ShardId) -> Result<(), CatalogError> {
2148        self.set_setting(
2149            EXPRESSION_CACHE_SHARD_KEY.to_string(),
2150            Some(shard_id.to_string()),
2151        )
2152    }
2153
2154    /// Updates the catalog `with_0dt_deployment_max_wait` "config" value to
2155    /// match the `with_0dt_deployment_max_wait` "system var" value.
2156    ///
2157    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2158    /// but use it in boot before Launch Darkly is available.
2159    pub fn set_0dt_deployment_max_wait(&mut self, value: Duration) -> Result<(), CatalogError> {
2160        self.set_config(
2161            WITH_0DT_DEPLOYMENT_MAX_WAIT.into(),
2162            Some(
2163                value
2164                    .as_millis()
2165                    .try_into()
2166                    .expect("max wait fits into u64"),
2167            ),
2168        )
2169    }
2170
2171    /// Updates the catalog `with_0dt_deployment_ddl_check_interval` "config"
2172    /// value to match the `with_0dt_deployment_ddl_check_interval` "system var"
2173    /// value.
2174    ///
2175    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2176    /// but use it in boot before Launch Darkly is available.
2177    pub fn set_0dt_deployment_ddl_check_interval(
2178        &mut self,
2179        value: Duration,
2180    ) -> Result<(), CatalogError> {
2181        self.set_config(
2182            WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.into(),
2183            Some(
2184                value
2185                    .as_millis()
2186                    .try_into()
2187                    .expect("ddl check interval fits into u64"),
2188            ),
2189        )
2190    }
2191
2192    /// Updates the catalog `0dt_deployment_panic_after_timeout` "config" value to
2193    /// match the `0dt_deployment_panic_after_timeout` "system var" value.
2194    ///
2195    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2196    /// but use it in boot before Launch Darkly is available.
2197    pub fn set_enable_0dt_deployment_panic_after_timeout(
2198        &mut self,
2199        value: bool,
2200    ) -> Result<(), CatalogError> {
2201        self.set_config(
2202            ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.into(),
2203            Some(u64::from(value)),
2204        )
2205    }
2206
2207    /// Removes the catalog `with_0dt_deployment_max_wait` "config" value to
2208    /// match the `with_0dt_deployment_max_wait` "system var" value.
2209    ///
2210    /// These are mirrored so that we can toggle the flag with LaunchDarkly,
2211    /// but use it in boot before LaunchDarkly is available.
2212    pub fn reset_0dt_deployment_max_wait(&mut self) -> Result<(), CatalogError> {
2213        self.set_config(WITH_0DT_DEPLOYMENT_MAX_WAIT.into(), None)
2214    }
2215
2216    /// Removes the catalog `with_0dt_deployment_ddl_check_interval` "config"
2217    /// value to match the `with_0dt_deployment_ddl_check_interval` "system var"
2218    /// value.
2219    ///
2220    /// These are mirrored so that we can toggle the flag with LaunchDarkly, but
2221    /// use it in boot before LaunchDarkly is available.
2222    pub fn reset_0dt_deployment_ddl_check_interval(&mut self) -> Result<(), CatalogError> {
2223        self.set_config(WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.into(), None)
2224    }
2225
2226    /// Removes the catalog `enable_0dt_deployment_panic_after_timeout` "config"
2227    /// value to match the `enable_0dt_deployment_panic_after_timeout` "system
2228    /// var" value.
2229    ///
2230    /// These are mirrored so that we can toggle the flag with LaunchDarkly, but
2231    /// use it in boot before LaunchDarkly is available.
2232    pub fn reset_enable_0dt_deployment_panic_after_timeout(&mut self) -> Result<(), CatalogError> {
2233        self.set_config(ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.into(), None)
2234    }
2235
2236    /// Updates the catalog `system_config_synced` "config" value to true.
2237    pub fn set_system_config_synced_once(&mut self) -> Result<(), CatalogError> {
2238        self.set_config(SYSTEM_CONFIG_SYNCED_KEY.into(), Some(1))
2239    }
2240
2241    pub fn update_comment(
2242        &mut self,
2243        object_id: CommentObjectId,
2244        sub_component: Option<usize>,
2245        comment: Option<String>,
2246    ) -> Result<(), CatalogError> {
2247        let key = CommentKey {
2248            object_id,
2249            sub_component,
2250        };
2251        let value = comment.map(|c| CommentValue { comment: c });
2252        self.comments.set(key, value, self.op_id)?;
2253
2254        Ok(())
2255    }
2256
2257    pub fn drop_comments(
2258        &mut self,
2259        object_ids: &BTreeSet<CommentObjectId>,
2260    ) -> Result<(), CatalogError> {
2261        if object_ids.is_empty() {
2262            return Ok(());
2263        }
2264
2265        self.comments
2266            .delete(|k, _v| object_ids.contains(&k.object_id), self.op_id);
2267        Ok(())
2268    }
2269
2270    pub fn update_source_references(
2271        &mut self,
2272        source_id: CatalogItemId,
2273        references: Vec<SourceReference>,
2274        updated_at: u64,
2275    ) -> Result<(), CatalogError> {
2276        let key = SourceReferencesKey { source_id };
2277        let value = SourceReferencesValue {
2278            references,
2279            updated_at,
2280        };
2281        self.source_references.set(key, Some(value), self.op_id)?;
2282        Ok(())
2283    }
2284
2285    /// Upserts persisted system configuration `name` to `value`.
2286    pub fn upsert_system_config(&mut self, name: &str, value: String) -> Result<(), CatalogError> {
2287        let key = ServerConfigurationKey {
2288            name: name.to_string(),
2289        };
2290        let value = ServerConfigurationValue { value };
2291        self.system_configurations
2292            .set(key, Some(value), self.op_id)?;
2293        Ok(())
2294    }
2295
2296    /// Removes persisted system configuration `name`.
2297    pub fn remove_system_config(&mut self, name: &str) {
2298        let key = ServerConfigurationKey {
2299            name: name.to_string(),
2300        };
2301        self.system_configurations
2302            .set(key, None, self.op_id)
2303            .expect("cannot have uniqueness violation");
2304    }
2305
2306    /// Removes all persisted system configurations.
2307    pub fn clear_system_configs(&mut self) {
2308        self.system_configurations.delete(|_k, _v| true, self.op_id);
2309    }
2310
2311    /// Returns the persisted cluster-coherent scoped system configurations.
2312    pub fn get_cluster_system_configurations(
2313        &self,
2314    ) -> impl Iterator<Item = ClusterSystemConfiguration> + use<'_> {
2315        self.cluster_system_configurations
2316            .items()
2317            .into_iter()
2318            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2319    }
2320
2321    /// Upserts the persisted cluster-coherent scoped configuration `name` = `value`
2322    /// for `cluster_id`.
2323    pub fn upsert_cluster_system_config(
2324        &mut self,
2325        cluster_id: ClusterId,
2326        name: &str,
2327        value: String,
2328    ) -> Result<(), CatalogError> {
2329        let key = ClusterSystemConfigurationKey {
2330            cluster_id,
2331            name: name.to_string(),
2332        };
2333        let value = ClusterSystemConfigurationValue { value };
2334        self.cluster_system_configurations
2335            .set(key, Some(value), self.op_id)?;
2336        Ok(())
2337    }
2338
2339    /// Removes the persisted cluster-coherent scoped configuration `name` for
2340    /// `cluster_id`.
2341    pub fn remove_cluster_system_config(&mut self, cluster_id: ClusterId, name: &str) {
2342        let key = ClusterSystemConfigurationKey {
2343            cluster_id,
2344            name: name.to_string(),
2345        };
2346        self.cluster_system_configurations
2347            .set(key, None, self.op_id)
2348            .expect("cannot have uniqueness violation");
2349    }
2350
2351    /// Returns the persisted replica-local scoped system configurations.
2352    pub fn get_replica_system_configurations(
2353        &self,
2354    ) -> impl Iterator<Item = ReplicaSystemConfiguration> + use<'_> {
2355        self.replica_system_configurations
2356            .items()
2357            .into_iter()
2358            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2359    }
2360
2361    /// Upserts the persisted replica-local scoped configuration `name` = `value`
2362    /// for `replica_id`.
2363    pub fn upsert_replica_system_config(
2364        &mut self,
2365        replica_id: ReplicaId,
2366        name: &str,
2367        value: String,
2368    ) -> Result<(), CatalogError> {
2369        let key = ReplicaSystemConfigurationKey {
2370            replica_id,
2371            name: name.to_string(),
2372        };
2373        let value = ReplicaSystemConfigurationValue { value };
2374        self.replica_system_configurations
2375            .set(key, Some(value), self.op_id)?;
2376        Ok(())
2377    }
2378
2379    /// Removes the persisted replica-local scoped configuration `name` for
2380    /// `replica_id`.
2381    pub fn remove_replica_system_config(&mut self, replica_id: ReplicaId, name: &str) {
2382        let key = ReplicaSystemConfigurationKey {
2383            replica_id,
2384            name: name.to_string(),
2385        };
2386        self.replica_system_configurations
2387            .set(key, None, self.op_id)
2388            .expect("cannot have uniqueness violation");
2389    }
2390
2391    pub(crate) fn insert_config(&mut self, key: String, value: u64) -> Result<(), CatalogError> {
2392        match self.configs.insert(
2393            ConfigKey { key: key.clone() },
2394            ConfigValue { value },
2395            self.op_id,
2396        ) {
2397            Ok(_) => Ok(()),
2398            Err(_) => Err(SqlCatalogError::ConfigAlreadyExists(key).into()),
2399        }
2400    }
2401
2402    pub fn get_clusters(&self) -> impl Iterator<Item = Cluster> + use<'_> {
2403        self.clusters
2404            .items()
2405            .into_iter()
2406            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2407    }
2408
2409    pub fn get_cluster_replicas(&self) -> impl Iterator<Item = ClusterReplica> + use<'_> {
2410        self.cluster_replicas
2411            .items()
2412            .into_iter()
2413            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2414    }
2415
2416    pub fn get_databases(&self) -> impl Iterator<Item = Database> + use<'_> {
2417        self.databases
2418            .items()
2419            .into_iter()
2420            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2421    }
2422
2423    pub fn get_roles(&self) -> impl Iterator<Item = Role> + use<'_> {
2424        self.roles
2425            .items()
2426            .into_iter()
2427            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2428    }
2429
2430    pub fn get_network_policies(&self) -> impl Iterator<Item = NetworkPolicy> + use<'_> {
2431        self.network_policies
2432            .items()
2433            .into_iter()
2434            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2435    }
2436
2437    pub fn get_system_object_mappings(
2438        &self,
2439    ) -> impl Iterator<Item = SystemObjectMapping> + use<'_> {
2440        self.system_gid_mapping
2441            .items()
2442            .into_iter()
2443            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2444    }
2445
2446    pub fn get_schemas(&self) -> impl Iterator<Item = Schema> + use<'_> {
2447        self.schemas
2448            .items()
2449            .into_iter()
2450            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2451    }
2452
2453    pub fn get_system_configurations(&self) -> impl Iterator<Item = SystemConfiguration> + use<'_> {
2454        self.system_configurations
2455            .items()
2456            .into_iter()
2457            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2458    }
2459
2460    pub fn get_schema(&self, id: &SchemaId) -> Option<Schema> {
2461        let key = SchemaKey { id: *id };
2462        self.schemas
2463            .get(&key)
2464            .map(|v| DurableType::from_key_value(key, v.clone()))
2465    }
2466
2467    pub fn get_introspection_source_indexes(
2468        &self,
2469        cluster_id: ClusterId,
2470    ) -> BTreeMap<&str, (GlobalId, u32)> {
2471        self.introspection_sources
2472            .items()
2473            .into_iter()
2474            .filter(|(k, _v)| k.cluster_id == cluster_id)
2475            .map(|(k, v)| (k.name.as_str(), (v.global_id.into(), v.oid)))
2476            .collect()
2477    }
2478
2479    pub fn get_catalog_content_version(&self) -> Option<&str> {
2480        self.settings
2481            .get(&SettingKey {
2482                name: CATALOG_CONTENT_VERSION_KEY.to_string(),
2483            })
2484            .map(|value| &*value.value)
2485    }
2486
2487    pub fn get_authentication_mock_nonce(&self) -> Option<String> {
2488        self.settings
2489            .get(&SettingKey {
2490                name: MOCK_AUTHENTICATION_NONCE_KEY.to_string(),
2491            })
2492            .map(|value| value.value.clone())
2493    }
2494
2495    /// Commit the current operation within the transaction. This does not cause anything to be
2496    /// written durably, but signals to the current transaction that we are moving on to the next
2497    /// operation.
2498    ///
2499    /// Returns the updates of the committed operation.
2500    #[must_use]
2501    pub fn get_and_commit_op_updates(&mut self) -> Vec<StateUpdate> {
2502        let updates = self.get_op_updates();
2503        self.commit_op();
2504        updates
2505    }
2506
2507    fn get_op_updates(&self) -> Vec<StateUpdate> {
2508        fn get_collection_op_updates<'a, T>(
2509            table_txn: &'a TableTransaction<T::Key, T::Value>,
2510            kind_fn: impl Fn(T) -> StateUpdateKind + 'a,
2511            op: Timestamp,
2512        ) -> impl Iterator<Item = (StateUpdateKind, StateDiff)> + 'a
2513        where
2514            T::Key: Ord + Eq + Clone + Debug,
2515            T::Value: Ord + Clone + Debug,
2516            T: DurableType,
2517        {
2518            table_txn
2519                .pending
2520                .iter()
2521                .flat_map(|(k, vs)| vs.into_iter().map(move |v| (k, v)))
2522                .filter_map(move |(k, v)| {
2523                    if v.ts == op {
2524                        let key = k.clone();
2525                        let value = v.value.clone();
2526                        let diff = v.diff.clone().try_into().expect("invalid diff");
2527                        let update = DurableType::from_key_value(key, value);
2528                        let kind = kind_fn(update);
2529                        Some((kind, diff))
2530                    } else {
2531                        None
2532                    }
2533                })
2534        }
2535
2536        fn get_large_collection_op_updates<'a, T>(
2537            collection: &'a Vec<(T::Key, Diff, Timestamp)>,
2538            kind_fn: impl Fn(T) -> StateUpdateKind + 'a,
2539            op: Timestamp,
2540        ) -> impl Iterator<Item = (StateUpdateKind, StateDiff)> + 'a
2541        where
2542            T::Key: Ord + Eq + Clone + Debug,
2543            T: DurableType<Value = ()>,
2544        {
2545            collection.iter().filter_map(move |(k, diff, ts)| {
2546                if *ts == op {
2547                    let key = k.clone();
2548                    let diff = diff.clone().try_into().expect("invalid diff");
2549                    let update = DurableType::from_key_value(key, ());
2550                    let kind = kind_fn(update);
2551                    Some((kind, diff))
2552                } else {
2553                    None
2554                }
2555            })
2556        }
2557
2558        let Transaction {
2559            durable_catalog: _,
2560            databases,
2561            schemas,
2562            items,
2563            comments,
2564            roles,
2565            role_auth,
2566            clusters,
2567            network_policies,
2568            cluster_replicas,
2569            introspection_sources,
2570            system_gid_mapping,
2571            system_configurations,
2572            cluster_system_configurations,
2573            replica_system_configurations,
2574            default_privileges,
2575            source_references,
2576            system_privileges,
2577            audit_log_updates,
2578            storage_collection_metadata,
2579            unfinalized_shards,
2580            // Not representable as a `StateUpdate`.
2581            id_allocator: _,
2582            configs: _,
2583            settings: _,
2584            txn_wal_shard: _,
2585            upper,
2586            op_id: _,
2587            commit_capability: _,
2588        } = &self;
2589
2590        let updates = std::iter::empty()
2591            .chain(get_collection_op_updates(
2592                roles,
2593                StateUpdateKind::Role,
2594                self.op_id,
2595            ))
2596            .chain(get_collection_op_updates(
2597                role_auth,
2598                StateUpdateKind::RoleAuth,
2599                self.op_id,
2600            ))
2601            .chain(get_collection_op_updates(
2602                databases,
2603                StateUpdateKind::Database,
2604                self.op_id,
2605            ))
2606            .chain(get_collection_op_updates(
2607                schemas,
2608                StateUpdateKind::Schema,
2609                self.op_id,
2610            ))
2611            .chain(get_collection_op_updates(
2612                default_privileges,
2613                StateUpdateKind::DefaultPrivilege,
2614                self.op_id,
2615            ))
2616            .chain(get_collection_op_updates(
2617                system_privileges,
2618                StateUpdateKind::SystemPrivilege,
2619                self.op_id,
2620            ))
2621            .chain(get_collection_op_updates(
2622                system_configurations,
2623                StateUpdateKind::SystemConfiguration,
2624                self.op_id,
2625            ))
2626            .chain(get_collection_op_updates(
2627                cluster_system_configurations,
2628                StateUpdateKind::ClusterSystemConfiguration,
2629                self.op_id,
2630            ))
2631            .chain(get_collection_op_updates(
2632                replica_system_configurations,
2633                StateUpdateKind::ReplicaSystemConfiguration,
2634                self.op_id,
2635            ))
2636            .chain(get_collection_op_updates(
2637                clusters,
2638                StateUpdateKind::Cluster,
2639                self.op_id,
2640            ))
2641            .chain(get_collection_op_updates(
2642                network_policies,
2643                StateUpdateKind::NetworkPolicy,
2644                self.op_id,
2645            ))
2646            .chain(get_collection_op_updates(
2647                introspection_sources,
2648                StateUpdateKind::IntrospectionSourceIndex,
2649                self.op_id,
2650            ))
2651            .chain(get_collection_op_updates(
2652                cluster_replicas,
2653                StateUpdateKind::ClusterReplica,
2654                self.op_id,
2655            ))
2656            .chain(get_collection_op_updates(
2657                system_gid_mapping,
2658                StateUpdateKind::SystemObjectMapping,
2659                self.op_id,
2660            ))
2661            .chain(get_collection_op_updates(
2662                items,
2663                StateUpdateKind::Item,
2664                self.op_id,
2665            ))
2666            .chain(get_collection_op_updates(
2667                comments,
2668                StateUpdateKind::Comment,
2669                self.op_id,
2670            ))
2671            .chain(get_collection_op_updates(
2672                source_references,
2673                StateUpdateKind::SourceReferences,
2674                self.op_id,
2675            ))
2676            .chain(get_collection_op_updates(
2677                storage_collection_metadata,
2678                StateUpdateKind::StorageCollectionMetadata,
2679                self.op_id,
2680            ))
2681            .chain(get_collection_op_updates(
2682                unfinalized_shards,
2683                StateUpdateKind::UnfinalizedShard,
2684                self.op_id,
2685            ))
2686            .chain(get_large_collection_op_updates(
2687                audit_log_updates,
2688                StateUpdateKind::AuditLog,
2689                self.op_id,
2690            ))
2691            .map(|(kind, diff)| StateUpdate {
2692                kind,
2693                ts: upper.clone(),
2694                diff,
2695            })
2696            .collect();
2697
2698        updates
2699    }
2700
2701    pub fn is_savepoint(&self) -> bool {
2702        self.durable_catalog.is_savepoint()
2703    }
2704
2705    fn commit_op(&mut self) {
2706        self.op_id += 1;
2707    }
2708
2709    pub fn op_id(&self) -> Timestamp {
2710        self.op_id
2711    }
2712
2713    pub fn upper(&self) -> mz_repr::Timestamp {
2714        self.upper
2715    }
2716
2717    fn ensure_committable(&self) -> Result<(), CatalogError> {
2718        match self.commit_capability {
2719            Some(_) => Ok(()),
2720            None => Err(DurableCatalogError::DryRunTransaction.into()),
2721        }
2722    }
2723
2724    /// Verifies that this process has not missed catalog content updates.
2725    pub(super) async fn ensure_not_out_of_sync(&mut self) -> Result<(), CatalogError> {
2726        self.durable_catalog
2727            .ensure_not_out_of_sync(self.upper)
2728            .await
2729    }
2730
2731    pub(crate) fn into_parts(
2732        self,
2733    ) -> Result<(TransactionBatch, &'a mut dyn DurableCatalogState), CatalogError> {
2734        let commit_capability = self
2735            .commit_capability
2736            .ok_or(DurableCatalogError::DryRunTransaction)?;
2737        let audit_log_updates = self
2738            .audit_log_updates
2739            .into_iter()
2740            .map(|(k, diff, _op)| (k.into_proto(), (), diff))
2741            .collect();
2742
2743        let txn_batch = TransactionBatch {
2744            databases: self.databases.pending(),
2745            schemas: self.schemas.pending(),
2746            items: self.items.pending(),
2747            comments: self.comments.pending(),
2748            roles: self.roles.pending(),
2749            role_auth: self.role_auth.pending(),
2750            clusters: self.clusters.pending(),
2751            cluster_replicas: self.cluster_replicas.pending(),
2752            network_policies: self.network_policies.pending(),
2753            introspection_sources: self.introspection_sources.pending(),
2754            id_allocator: self.id_allocator.pending(),
2755            configs: self.configs.pending(),
2756            source_references: self.source_references.pending(),
2757            settings: self.settings.pending(),
2758            system_gid_mapping: self.system_gid_mapping.pending(),
2759            system_configurations: self.system_configurations.pending(),
2760            cluster_system_configurations: self.cluster_system_configurations.pending(),
2761            replica_system_configurations: self.replica_system_configurations.pending(),
2762            default_privileges: self.default_privileges.pending(),
2763            system_privileges: self.system_privileges.pending(),
2764            storage_collection_metadata: self.storage_collection_metadata.pending(),
2765            unfinalized_shards: self.unfinalized_shards.pending(),
2766            txn_wal_shard: self.txn_wal_shard.pending(),
2767            audit_log_updates,
2768            upper: self.upper,
2769            _commit_capability: commit_capability,
2770        };
2771        Ok((txn_batch, self.durable_catalog))
2772    }
2773
2774    /// Commits the storage transaction to durable storage.
2775    ///
2776    /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect
2777    /// programming error that leaves durable state unchanged. Any other error
2778    /// outside read-only mode indicates the catalog may be in an indeterminate
2779    /// state and needs to be fully re-read before proceeding. In general, such
2780    /// errors must be fatal to the calling process. We do not panic/halt here so
2781    /// initialization can report them.
2782    ///
2783    /// The transaction is committed at `commit_ts`.
2784    ///
2785    /// Returns what the upper was directly after the transaction committed.
2786    ///
2787    /// In read-only mode, this will return an error for non-empty transactions indicating that the
2788    /// catalog is not writeable.
2789    #[mz_ore::instrument(level = "debug")]
2790    pub(crate) async fn commit_internal(
2791        self,
2792        commit_ts: mz_repr::Timestamp,
2793    ) -> Result<(&'a mut dyn DurableCatalogState, mz_repr::Timestamp), CatalogError> {
2794        self.ensure_committable()?;
2795        let (mut txn_batch, durable_catalog) = self.into_parts()?;
2796        let TransactionBatch {
2797            databases,
2798            schemas,
2799            items,
2800            comments,
2801            roles,
2802            role_auth,
2803            clusters,
2804            cluster_replicas,
2805            network_policies,
2806            introspection_sources,
2807            id_allocator,
2808            configs,
2809            source_references,
2810            settings,
2811            system_gid_mapping,
2812            system_configurations,
2813            cluster_system_configurations,
2814            replica_system_configurations,
2815            default_privileges,
2816            system_privileges,
2817            storage_collection_metadata,
2818            unfinalized_shards,
2819            txn_wal_shard,
2820            audit_log_updates,
2821            upper: _,
2822            _commit_capability: _,
2823        } = &mut txn_batch;
2824        // Consolidate in memory because it will likely be faster than consolidating after the
2825        // transaction has been made durable.
2826        differential_dataflow::consolidation::consolidate_updates(databases);
2827        differential_dataflow::consolidation::consolidate_updates(schemas);
2828        differential_dataflow::consolidation::consolidate_updates(items);
2829        differential_dataflow::consolidation::consolidate_updates(comments);
2830        differential_dataflow::consolidation::consolidate_updates(roles);
2831        differential_dataflow::consolidation::consolidate_updates(role_auth);
2832        differential_dataflow::consolidation::consolidate_updates(clusters);
2833        differential_dataflow::consolidation::consolidate_updates(cluster_replicas);
2834        differential_dataflow::consolidation::consolidate_updates(network_policies);
2835        differential_dataflow::consolidation::consolidate_updates(introspection_sources);
2836        differential_dataflow::consolidation::consolidate_updates(id_allocator);
2837        differential_dataflow::consolidation::consolidate_updates(configs);
2838        differential_dataflow::consolidation::consolidate_updates(settings);
2839        differential_dataflow::consolidation::consolidate_updates(source_references);
2840        differential_dataflow::consolidation::consolidate_updates(system_gid_mapping);
2841        differential_dataflow::consolidation::consolidate_updates(system_configurations);
2842        differential_dataflow::consolidation::consolidate_updates(cluster_system_configurations);
2843        differential_dataflow::consolidation::consolidate_updates(replica_system_configurations);
2844        differential_dataflow::consolidation::consolidate_updates(default_privileges);
2845        differential_dataflow::consolidation::consolidate_updates(system_privileges);
2846        differential_dataflow::consolidation::consolidate_updates(storage_collection_metadata);
2847        differential_dataflow::consolidation::consolidate_updates(unfinalized_shards);
2848        differential_dataflow::consolidation::consolidate_updates(txn_wal_shard);
2849        differential_dataflow::consolidation::consolidate_updates(audit_log_updates);
2850
2851        let upper = durable_catalog
2852            .commit_transaction(txn_batch, commit_ts)
2853            .await?;
2854        Ok((durable_catalog, upper))
2855    }
2856
2857    /// Commits the storage transaction to durable storage.
2858    ///
2859    /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect
2860    /// programming error that leaves durable state unchanged. Any other error
2861    /// outside read-only mode indicates the catalog may be in an indeterminate
2862    /// state and needs to be fully re-read before proceeding. In general, such
2863    /// errors must be fatal to the calling process. We do not panic/halt here so
2864    /// initialization can report them.
2865    ///
2866    /// In read-only mode, this will return an error for non-empty transactions indicating that the
2867    /// catalog is not writeable.
2868    ///
2869    /// IMPORTANT: It is assumed that the committer of this transaction has already applied all
2870    /// updates from this transaction. Therefore, updates from this transaction will not be returned
2871    /// when calling [`crate::durable::ReadOnlyDurableCatalogState::sync_to_current_updates`] or
2872    /// [`crate::durable::ReadOnlyDurableCatalogState::sync_updates`].
2873    ///
2874    /// An alternative implementation would be for the caller to explicitly consume their updates
2875    /// after committing and only then apply the updates in-memory. While this removes assumptions
2876    /// about the caller in this method, in practice it results in duplicate work on every commit.
2877    #[mz_ore::instrument(level = "debug")]
2878    pub async fn commit(self, commit_ts: mz_repr::Timestamp) -> Result<(), CatalogError> {
2879        self.ensure_committable()?;
2880        let op_updates = self.get_op_updates();
2881        assert!(
2882            op_updates.is_empty(),
2883            "unconsumed transaction updates: {op_updates:?}"
2884        );
2885
2886        let (durable_storage, upper) = self.commit_internal(commit_ts).await?;
2887        // Drain all the updates from the commit since it is assumed that they were already applied.
2888        let updates = durable_storage.sync_updates(upper).await?;
2889        // Writable and savepoint catalogs should have consumed all updates before committing a
2890        // transaction, otherwise the commit was performed with an out of date state.
2891        // Read-only catalogs can only commit empty transactions, so they don't need to consume all
2892        // updates before committing.
2893        //
2894        // The off-loop group committer can advance the catalog upper without content while this
2895        // transaction is open. The commit then rebases above `commit_ts`. Its returned `upper` is
2896        // the exclusive upper of the successful write.
2897        soft_assert_no_log!(
2898            durable_storage.is_read_only()
2899                || updates
2900                    .iter()
2901                    .all(|update| update.ts >= commit_ts && update.ts < upper),
2902            "unconsumed updates existed before transaction commit: commit_ts={commit_ts:?}, upper={upper:?}, updates:{updates:?}"
2903        );
2904        Ok(())
2905    }
2906}
2907
2908use crate::durable::async_trait;
2909
2910use super::objects::{RoleAuthKey, RoleAuthValue};
2911
2912#[async_trait]
2913impl StorageTxn for Transaction<'_> {
2914    fn get_collection_metadata(&self) -> BTreeMap<GlobalId, ShardId> {
2915        self.storage_collection_metadata
2916            .items()
2917            .into_iter()
2918            .map(
2919                |(
2920                    StorageCollectionMetadataKey { id },
2921                    StorageCollectionMetadataValue { shard },
2922                )| { (*id, shard.clone()) },
2923            )
2924            .collect()
2925    }
2926
2927    fn insert_collection_metadata(
2928        &mut self,
2929        metadata: BTreeMap<GlobalId, ShardId>,
2930    ) -> Result<(), StorageError> {
2931        for (id, shard) in metadata {
2932            self.storage_collection_metadata
2933                .insert(
2934                    StorageCollectionMetadataKey { id },
2935                    StorageCollectionMetadataValue {
2936                        shard: shard.clone(),
2937                    },
2938                    self.op_id,
2939                )
2940                .map_err(|err| match err {
2941                    DurableCatalogError::DuplicateKey => {
2942                        StorageError::CollectionMetadataAlreadyExists(id)
2943                    }
2944                    DurableCatalogError::UniquenessViolation => {
2945                        StorageError::PersistShardAlreadyInUse(shard)
2946                    }
2947                    err => StorageError::Generic(anyhow::anyhow!(err)),
2948                })?;
2949        }
2950        Ok(())
2951    }
2952
2953    fn delete_collection_metadata(&mut self, ids: BTreeSet<GlobalId>) -> Vec<(GlobalId, ShardId)> {
2954        let ks: Vec<_> = ids
2955            .into_iter()
2956            .map(|id| StorageCollectionMetadataKey { id })
2957            .collect();
2958        self.storage_collection_metadata
2959            .delete_by_keys(ks, self.op_id)
2960            .into_iter()
2961            .map(
2962                |(
2963                    StorageCollectionMetadataKey { id },
2964                    StorageCollectionMetadataValue { shard },
2965                )| (id, shard),
2966            )
2967            .collect()
2968    }
2969
2970    fn get_unfinalized_shards(&self) -> BTreeSet<ShardId> {
2971        self.unfinalized_shards
2972            .items()
2973            .into_iter()
2974            .map(|(UnfinalizedShardKey { shard }, ())| *shard)
2975            .collect()
2976    }
2977
2978    fn insert_unfinalized_shards(&mut self, s: BTreeSet<ShardId>) -> Result<(), StorageError> {
2979        for shard in s {
2980            match self
2981                .unfinalized_shards
2982                .insert(UnfinalizedShardKey { shard }, (), self.op_id)
2983            {
2984                // Inserting duplicate keys has no effect.
2985                Ok(()) | Err(DurableCatalogError::DuplicateKey) => {}
2986                Err(e) => Err(StorageError::Generic(anyhow::anyhow!(e)))?,
2987            };
2988        }
2989        Ok(())
2990    }
2991
2992    fn remove_unfinalized_shards(&mut self, shards: BTreeSet<ShardId>) {
2993        let ks: Vec<_> = shards
2994            .into_iter()
2995            .map(|shard| UnfinalizedShardKey { shard })
2996            .collect();
2997        let _ = self.unfinalized_shards.delete_by_keys(ks, self.op_id);
2998    }
2999
3000    fn get_txn_wal_shard(&self) -> Option<ShardId> {
3001        self.txn_wal_shard
3002            .values()
3003            .iter()
3004            .next()
3005            .map(|TxnWalShardValue { shard }| *shard)
3006    }
3007
3008    fn write_txn_wal_shard(&mut self, shard: ShardId) -> Result<(), StorageError> {
3009        self.txn_wal_shard
3010            .insert((), TxnWalShardValue { shard }, self.op_id)
3011            .map_err(|err| match err {
3012                DurableCatalogError::DuplicateKey => StorageError::TxnWalShardAlreadyExists,
3013                err => StorageError::Generic(anyhow::anyhow!(err)),
3014            })
3015    }
3016}
3017
3018/// Describes a set of changes to apply as the result of a catalog transaction.
3019#[derive(Debug, PartialEq)]
3020pub struct TransactionBatch {
3021    pub(crate) databases: Vec<(proto::DatabaseKey, proto::DatabaseValue, Diff)>,
3022    pub(crate) schemas: Vec<(proto::SchemaKey, proto::SchemaValue, Diff)>,
3023    pub(crate) items: Vec<(proto::ItemKey, proto::ItemValue, Diff)>,
3024    pub(crate) comments: Vec<(proto::CommentKey, proto::CommentValue, Diff)>,
3025    pub(crate) roles: Vec<(proto::RoleKey, proto::RoleValue, Diff)>,
3026    pub(crate) role_auth: Vec<(proto::RoleAuthKey, proto::RoleAuthValue, Diff)>,
3027    pub(crate) clusters: Vec<(proto::ClusterKey, proto::ClusterValue, Diff)>,
3028    pub(crate) cluster_replicas: Vec<(proto::ClusterReplicaKey, proto::ClusterReplicaValue, Diff)>,
3029    pub(crate) network_policies: Vec<(proto::NetworkPolicyKey, proto::NetworkPolicyValue, Diff)>,
3030    pub(crate) introspection_sources: Vec<(
3031        proto::ClusterIntrospectionSourceIndexKey,
3032        proto::ClusterIntrospectionSourceIndexValue,
3033        Diff,
3034    )>,
3035    pub(crate) id_allocator: Vec<(proto::IdAllocKey, proto::IdAllocValue, Diff)>,
3036    pub(crate) configs: Vec<(proto::ConfigKey, proto::ConfigValue, Diff)>,
3037    pub(crate) settings: Vec<(proto::SettingKey, proto::SettingValue, Diff)>,
3038    pub(crate) system_gid_mapping: Vec<(proto::GidMappingKey, proto::GidMappingValue, Diff)>,
3039    pub(crate) system_configurations: Vec<(
3040        proto::ServerConfigurationKey,
3041        proto::ServerConfigurationValue,
3042        Diff,
3043    )>,
3044    pub(crate) cluster_system_configurations: Vec<(
3045        proto::ClusterSystemConfigurationKey,
3046        proto::ClusterSystemConfigurationValue,
3047        Diff,
3048    )>,
3049    pub(crate) replica_system_configurations: Vec<(
3050        proto::ReplicaSystemConfigurationKey,
3051        proto::ReplicaSystemConfigurationValue,
3052        Diff,
3053    )>,
3054    pub(crate) default_privileges: Vec<(
3055        proto::DefaultPrivilegesKey,
3056        proto::DefaultPrivilegesValue,
3057        Diff,
3058    )>,
3059    pub(crate) source_references: Vec<(
3060        proto::SourceReferencesKey,
3061        proto::SourceReferencesValue,
3062        Diff,
3063    )>,
3064    pub(crate) system_privileges: Vec<(
3065        proto::SystemPrivilegesKey,
3066        proto::SystemPrivilegesValue,
3067        Diff,
3068    )>,
3069    pub(crate) storage_collection_metadata: Vec<(
3070        proto::StorageCollectionMetadataKey,
3071        proto::StorageCollectionMetadataValue,
3072        Diff,
3073    )>,
3074    pub(crate) unfinalized_shards: Vec<(proto::UnfinalizedShardKey, (), Diff)>,
3075    pub(crate) txn_wal_shard: Vec<((), proto::TxnWalShardValue, Diff)>,
3076    pub(crate) audit_log_updates: Vec<(proto::AuditLogKey, (), Diff)>,
3077    /// The upper of the catalog when the transaction started.
3078    pub(crate) upper: mz_repr::Timestamp,
3079    // A private, non-cloneable capability keeps batches constructible only by
3080    // transaction decomposition.
3081    _commit_capability: CommitCapability,
3082}
3083
3084impl TransactionBatch {
3085    pub fn is_empty(&self) -> bool {
3086        let TransactionBatch {
3087            databases,
3088            schemas,
3089            items,
3090            comments,
3091            roles,
3092            role_auth,
3093            clusters,
3094            cluster_replicas,
3095            network_policies,
3096            introspection_sources,
3097            id_allocator,
3098            configs,
3099            settings,
3100            source_references,
3101            system_gid_mapping,
3102            system_configurations,
3103            cluster_system_configurations,
3104            replica_system_configurations,
3105            default_privileges,
3106            system_privileges,
3107            storage_collection_metadata,
3108            unfinalized_shards,
3109            txn_wal_shard,
3110            audit_log_updates,
3111            upper: _,
3112            _commit_capability: _,
3113        } = self;
3114        databases.is_empty()
3115            && schemas.is_empty()
3116            && items.is_empty()
3117            && comments.is_empty()
3118            && roles.is_empty()
3119            && role_auth.is_empty()
3120            && clusters.is_empty()
3121            && cluster_replicas.is_empty()
3122            && network_policies.is_empty()
3123            && introspection_sources.is_empty()
3124            && id_allocator.is_empty()
3125            && configs.is_empty()
3126            && settings.is_empty()
3127            && source_references.is_empty()
3128            && system_gid_mapping.is_empty()
3129            && system_configurations.is_empty()
3130            && cluster_system_configurations.is_empty()
3131            && replica_system_configurations.is_empty()
3132            && default_privileges.is_empty()
3133            && system_privileges.is_empty()
3134            && storage_collection_metadata.is_empty()
3135            && unfinalized_shards.is_empty()
3136            && txn_wal_shard.is_empty()
3137            && audit_log_updates.is_empty()
3138    }
3139}
3140
3141#[derive(Debug, Clone, PartialEq, Eq)]
3142struct TransactionUpdate<V> {
3143    value: V,
3144    ts: Timestamp,
3145    diff: Diff,
3146}
3147
3148/// Utility trait to check for plan validity.
3149trait UniqueName {
3150    /// Does the item have a unique name? If yes, we can check for name equality in validity
3151    /// checking.
3152    const HAS_UNIQUE_NAME: bool;
3153    /// The unique name, only returns a meaningful name if [`Self::HAS_UNIQUE_NAME`] is `true`.
3154    fn unique_name(&self) -> &str;
3155}
3156
3157mod unique_name {
3158    use crate::durable::objects::*;
3159
3160    macro_rules! impl_unique_name {
3161        ($($t:ty),* $(,)?) => {
3162            $(
3163                impl crate::durable::transaction::UniqueName for $t {
3164                    const HAS_UNIQUE_NAME: bool = true;
3165                    fn unique_name(&self) -> &str {
3166                        &self.name
3167                    }
3168                }
3169            )*
3170        };
3171    }
3172
3173    macro_rules! impl_no_unique_name {
3174        ($($t:ty),* $(,)?) => {
3175            $(
3176                impl crate::durable::transaction::UniqueName for $t {
3177                    const HAS_UNIQUE_NAME: bool = false;
3178                    fn unique_name(&self) -> &str {
3179                       ""
3180                    }
3181                }
3182            )*
3183        };
3184    }
3185
3186    impl_unique_name! {
3187        ClusterReplicaValue,
3188        ClusterValue,
3189        DatabaseValue,
3190        ItemValue,
3191        NetworkPolicyValue,
3192        RoleValue,
3193        SchemaValue,
3194    }
3195
3196    impl_no_unique_name!(
3197        (),
3198        ClusterIntrospectionSourceIndexValue,
3199        ClusterSystemConfigurationValue,
3200        CommentValue,
3201        ConfigValue,
3202        DefaultPrivilegesValue,
3203        GidMappingValue,
3204        IdAllocValue,
3205        ReplicaSystemConfigurationValue,
3206        ServerConfigurationValue,
3207        SettingValue,
3208        SourceReferencesValue,
3209        StorageCollectionMetadataValue,
3210        SystemPrivilegesValue,
3211        TxnWalShardValue,
3212        RoleAuthValue,
3213    );
3214
3215    #[cfg(test)]
3216    mod test {
3217        impl_no_unique_name!(String,);
3218    }
3219}
3220
3221/// A collection's uniqueness constraint.
3222///
3223/// `violation` reports whether two values conflict, i.e. cannot both exist.
3224///
3225/// `is_unique_key_unchanged_after_update` reports whether an update left
3226/// unchanged every field `violation` reads. It is used as an optimization
3227/// since `violation` is an expensive operation to run.
3228#[derive(Debug)]
3229struct UniquenessCheck<V> {
3230    violation: fn(a: &V, b: &V) -> bool,
3231    is_unique_key_unchanged_after_update: fn(prev: &V, next: &V) -> bool,
3232}
3233
3234/// TableTransaction emulates some features of a typical SQL transaction over
3235/// table for a Collection.
3236///
3237/// It supports:
3238/// - uniqueness constraints
3239/// - transactional reads and writes (including read-your-writes before commit)
3240///
3241/// `K` is the primary key type. Multiple entries with the same key are disallowed.
3242/// `V` is the an arbitrary value type.
3243#[derive(Debug)]
3244struct TableTransaction<K, V> {
3245    initial: BTreeMap<K, V>,
3246    // The desired updates to keys after commit.
3247    // Invariant: Value is sorted by `ts`.
3248    pending: BTreeMap<K, Vec<TransactionUpdate<V>>>,
3249    // `None` for collections with no uniqueness constraint.
3250    uniqueness_check: Option<UniquenessCheck<V>>,
3251}
3252
3253impl<K, V> TableTransaction<K, V>
3254where
3255    K: Ord + Eq + Clone + Debug,
3256    V: Ord + Clone + Debug + UniqueName,
3257{
3258    /// Create a new TableTransaction with initial data.
3259    ///
3260    /// Internally the catalog serializes data as protobuf. All fields in a proto message are
3261    /// optional, which makes using them in Rust cumbersome. Generic parameters `KP` and `VP` are
3262    /// protobuf types which deserialize to `K` and `V` that a [`TableTransaction`] is generic
3263    /// over.
3264    fn new<KP, VP>(initial: BTreeMap<KP, VP>) -> Result<Self, TryFromProtoError>
3265    where
3266        K: RustType<KP>,
3267        V: RustType<VP>,
3268    {
3269        let initial = initial
3270            .into_iter()
3271            .map(RustType::from_proto)
3272            .collect::<Result<_, _>>()?;
3273
3274        Ok(Self {
3275            initial,
3276            pending: BTreeMap::new(),
3277            uniqueness_check: None,
3278        })
3279    }
3280
3281    /// Like [`Self::new`], but with the collection's uniqueness constraint.
3282    /// See [`UniquenessCheck`] for more details on the uniqueness constraint.
3283    fn new_with_uniqueness_fn<KP, VP>(
3284        initial: BTreeMap<KP, VP>,
3285        uniqueness_violation: fn(a: &V, b: &V) -> bool,
3286        is_unique_key_unchanged_after_update: fn(prev: &V, next: &V) -> bool,
3287    ) -> Result<Self, TryFromProtoError>
3288    where
3289        K: RustType<KP>,
3290        V: RustType<VP>,
3291    {
3292        let initial = initial
3293            .into_iter()
3294            .map(RustType::from_proto)
3295            .collect::<Result<_, _>>()?;
3296
3297        Ok(Self {
3298            initial,
3299            pending: BTreeMap::new(),
3300            uniqueness_check: Some(UniquenessCheck {
3301                violation: uniqueness_violation,
3302                is_unique_key_unchanged_after_update,
3303            }),
3304        })
3305    }
3306
3307    /// Consumes and returns the pending changes and their diffs. `Diff` is
3308    /// guaranteed to be 1 or -1.
3309    fn pending<KP, VP>(self) -> Vec<(KP, VP, Diff)>
3310    where
3311        K: RustType<KP>,
3312        V: RustType<VP>,
3313    {
3314        soft_assert_no_log!(self.verify().is_ok());
3315        // Pending describes the desired final state for some keys. K,V pairs should be
3316        // retracted if they already exist and were deleted or are being updated.
3317        self.pending
3318            .into_iter()
3319            .flat_map(|(k, v)| {
3320                let mut v: Vec<_> = v
3321                    .into_iter()
3322                    .map(|TransactionUpdate { value, ts: _, diff }| (value, diff))
3323                    .collect();
3324                differential_dataflow::consolidation::consolidate(&mut v);
3325                v.into_iter().map(move |(v, diff)| (k.clone(), v, diff))
3326            })
3327            .map(|(key, val, diff)| (key.into_proto(), val.into_proto(), diff))
3328            .collect()
3329    }
3330
3331    /// Verifies that no items in `self` violate `self.uniqueness_check`.
3332    ///
3333    /// Runtime is O(n^2), where n is the number of items in `self`, if
3334    /// [`UniqueName::HAS_UNIQUE_NAME`] is false for `V`. Prefer using [`Self::verify_keys`].
3335    fn verify(&self) -> Result<(), DurableCatalogError> {
3336        if let Some(check) = &self.uniqueness_check {
3337            // Compare each value to each other value and ensure they are unique.
3338            let items = self.values();
3339            if V::HAS_UNIQUE_NAME {
3340                let by_name: BTreeMap<_, _> = items
3341                    .iter()
3342                    .enumerate()
3343                    .map(|(v, vi)| (vi.unique_name(), (v, vi)))
3344                    .collect();
3345                for (i, vi) in items.iter().enumerate() {
3346                    if let Some((j, vj)) = by_name.get(vi.unique_name()) {
3347                        if i != *j && (check.violation)(vi, *vj) {
3348                            return Err(DurableCatalogError::UniquenessViolation);
3349                        }
3350                    }
3351                }
3352            } else {
3353                for (i, vi) in items.iter().enumerate() {
3354                    for (j, vj) in items.iter().enumerate() {
3355                        if i != j && (check.violation)(vi, vj) {
3356                            return Err(DurableCatalogError::UniquenessViolation);
3357                        }
3358                    }
3359                }
3360            }
3361        }
3362        soft_assert_no_log!(
3363            self.pending
3364                .values()
3365                .all(|pending| { pending.is_sorted_by(|a, b| a.ts <= b.ts) }),
3366            "pending should be sorted by timestamp: {:?}",
3367            self.pending
3368        );
3369        Ok(())
3370    }
3371
3372    /// Verifies that no items in `self` violate `self.uniqueness_check` with `keys`.
3373    ///
3374    /// Runtime is O(n * k), where n is the number of items in `self` and k is the number of
3375    /// items in `keys`.
3376    fn verify_keys<'a>(
3377        &self,
3378        keys: impl IntoIterator<Item = &'a K>,
3379    ) -> Result<(), DurableCatalogError>
3380    where
3381        K: 'a,
3382    {
3383        if let Some(check) = &self.uniqueness_check {
3384            let entries: Vec<_> = keys
3385                .into_iter()
3386                .filter_map(|key| self.get(key).map(|value| (key, value)))
3387                .collect();
3388            // Compare each value in `entries` to each value in `self` and ensure they are unique.
3389            for (ki, vi) in self.items() {
3390                for (kj, vj) in &entries {
3391                    if ki != *kj && (check.violation)(vi, vj) {
3392                        return Err(DurableCatalogError::UniquenessViolation);
3393                    }
3394                }
3395            }
3396        }
3397        soft_assert_no_log!(self.verify().is_ok());
3398        Ok(())
3399    }
3400
3401    /// Iterates over the items viewable in the current transaction in arbitrary
3402    /// order and applies `f` on all key, value pairs.
3403    fn for_values<'a, F: FnMut(&'a K, &'a V)>(&'a self, mut f: F) {
3404        let mut seen = BTreeSet::new();
3405        for k in self.pending.keys() {
3406            seen.insert(k);
3407            let v = self.get(k);
3408            // Deleted items don't exist so shouldn't be visited, but still suppress
3409            // visiting the key later.
3410            if let Some(v) = v {
3411                f(k, v);
3412            }
3413        }
3414        for (k, v) in self.initial.iter() {
3415            // Add on initial items that don't have updates.
3416            if !seen.contains(k) {
3417                f(k, v);
3418            }
3419        }
3420    }
3421
3422    /// Returns the current value of `k`.
3423    fn get(&self, k: &K) -> Option<&V> {
3424        let pending = self.pending.get(k).map(Vec::as_slice).unwrap_or_default();
3425        let mut updates = Vec::with_capacity(pending.len() + 1);
3426        if let Some(initial) = self.initial.get(k) {
3427            updates.push((initial, Diff::ONE));
3428        }
3429        updates.extend(
3430            pending
3431                .into_iter()
3432                .map(|TransactionUpdate { value, ts: _, diff }| (value, *diff)),
3433        );
3434
3435        differential_dataflow::consolidation::consolidate(&mut updates);
3436        assert!(updates.len() <= 1);
3437        updates.into_iter().next().map(|(v, _)| v)
3438    }
3439
3440    /// Returns the items viewable in the current transaction. The items are
3441    /// cloned, so this is an expensive operation. Prefer using [`Self::items`], or
3442    /// [`Self::for_values`].
3443    // Used by tests.
3444    #[cfg(test)]
3445    fn items_cloned(&self) -> BTreeMap<K, V> {
3446        let mut items = BTreeMap::new();
3447        self.for_values(|k, v| {
3448            items.insert(k.clone(), v.clone());
3449        });
3450        items
3451    }
3452
3453    /// Returns the current items as proto-typed key-value pairs, suitable for
3454    /// constructing a [`Snapshot`]. This merges `initial` and `pending` to
3455    /// produce the current view and converts back to proto types.
3456    fn current_items_proto<KP, VP>(&self) -> BTreeMap<KP, VP>
3457    where
3458        K: RustType<KP>,
3459        V: RustType<VP>,
3460        KP: Ord,
3461    {
3462        let mut items = BTreeMap::new();
3463        self.for_values(|k, v| {
3464            items.insert(k.into_proto(), v.into_proto());
3465        });
3466        items
3467    }
3468
3469    /// Returns the items viewable in the current transaction as references. Returns a map
3470    /// of references.
3471    fn items(&self) -> BTreeMap<&K, &V> {
3472        let mut items = BTreeMap::new();
3473        self.for_values(|k, v| {
3474            items.insert(k, v);
3475        });
3476        items
3477    }
3478
3479    /// Returns the values viewable in the current transaction as references.
3480    fn values(&self) -> BTreeSet<&V> {
3481        let mut items = BTreeSet::new();
3482        self.for_values(|_, v| {
3483            items.insert(v);
3484        });
3485        items
3486    }
3487
3488    /// Returns the number of items viewable in the current transaction.
3489    fn len(&self) -> usize {
3490        let mut count = 0;
3491        self.for_values(|_, _| {
3492            count += 1;
3493        });
3494        count
3495    }
3496
3497    /// Iterates over the items viewable in the current transaction, and provides a
3498    /// map where additional pending items can be inserted, which will be appended
3499    /// to current pending items. Does not verify uniqueness.
3500    fn for_values_mut<F: FnMut(&mut BTreeMap<K, Vec<TransactionUpdate<V>>>, &K, &V)>(
3501        &mut self,
3502        mut f: F,
3503    ) {
3504        let mut pending = BTreeMap::new();
3505        self.for_values(|k, v| f(&mut pending, k, v));
3506        for (k, updates) in pending {
3507            self.pending.entry(k).or_default().extend(updates);
3508        }
3509    }
3510
3511    /// Inserts a new k,v pair.
3512    ///
3513    /// Returns an error if the uniqueness check failed or the key already exists.
3514    fn insert(&mut self, k: K, v: V, ts: Timestamp) -> Result<(), DurableCatalogError> {
3515        let mut violation = None;
3516        let uniqueness_violation = self.uniqueness_check.as_ref().map(|check| check.violation);
3517        self.for_values(|for_k, for_v| {
3518            if &k == for_k {
3519                violation = Some(DurableCatalogError::DuplicateKey);
3520            }
3521            if let Some(uniqueness_violation) = uniqueness_violation {
3522                if uniqueness_violation(for_v, &v) {
3523                    violation = Some(DurableCatalogError::UniquenessViolation);
3524                }
3525            }
3526        });
3527        if let Some(violation) = violation {
3528            return Err(violation);
3529        }
3530        self.pending.entry(k).or_default().push(TransactionUpdate {
3531            value: v,
3532            ts,
3533            diff: Diff::ONE,
3534        });
3535        soft_assert_no_log!(self.verify().is_ok());
3536        Ok(())
3537    }
3538
3539    /// Updates k, v pairs. `f` is a function that can return `Some(V)` if the
3540    /// value should be updated, otherwise `None`. Returns the number of changed
3541    /// entries.
3542    ///
3543    /// Returns an error if the uniqueness check failed.
3544    ///
3545    /// Prefer using [`Self::update_by_key`] or [`Self::update_by_keys`], which generally have
3546    /// better performance.
3547    fn update<F: Fn(&K, &V) -> Option<V>>(
3548        &mut self,
3549        f: F,
3550        ts: Timestamp,
3551    ) -> Result<Diff, DurableCatalogError> {
3552        let mut changed = Diff::ZERO;
3553        let mut keys = BTreeSet::new();
3554        // Keep a copy of pending in case of uniqueness violation.
3555        let pending = self.pending.clone();
3556        self.for_values_mut(|p, k, v| {
3557            if let Some(next) = f(k, v) {
3558                changed += Diff::ONE;
3559                keys.insert(k.clone());
3560                let updates = p.entry(k.clone()).or_default();
3561                updates.push(TransactionUpdate {
3562                    value: v.clone(),
3563                    ts,
3564                    diff: Diff::MINUS_ONE,
3565                });
3566                updates.push(TransactionUpdate {
3567                    value: next,
3568                    ts,
3569                    diff: Diff::ONE,
3570                });
3571            }
3572        });
3573        // Check for uniqueness violation.
3574        if let Err(err) = self.verify_keys(&keys) {
3575            self.pending = pending;
3576            Err(err)
3577        } else {
3578            Ok(changed)
3579        }
3580    }
3581
3582    /// Updates `k`, `v` pair if `k` already exists in `self`.
3583    ///
3584    /// Returns `true` if `k` was updated, `false` otherwise.
3585    /// Returns an error if the uniqueness check failed.
3586    fn update_by_key(&mut self, k: K, v: V, ts: Timestamp) -> Result<bool, DurableCatalogError> {
3587        if let Some(cur_v) = self.get(&k) {
3588            if v != *cur_v {
3589                self.set(k, Some(v), ts)?;
3590            }
3591            Ok(true)
3592        } else {
3593            Ok(false)
3594        }
3595    }
3596
3597    /// Updates k, v pairs. Keys that don't already exist in `self` are ignored.
3598    ///
3599    /// Returns the number of changed entries.
3600    /// Returns an error if the uniqueness check failed.
3601    fn update_by_keys(
3602        &mut self,
3603        kvs: impl IntoIterator<Item = (K, V)>,
3604        ts: Timestamp,
3605    ) -> Result<Diff, DurableCatalogError> {
3606        let kvs: Vec<_> = kvs
3607            .into_iter()
3608            .filter_map(|(k, v)| match self.get(&k) {
3609                // Record if updating this entry would be a no-op.
3610                Some(cur_v) => Some((*cur_v == v, k, v)),
3611                None => None,
3612            })
3613            .collect();
3614        let changed = kvs.len();
3615        let changed =
3616            Diff::try_from(changed).map_err(|e| DurableCatalogError::Internal(e.to_string()))?;
3617        let kvs = kvs
3618            .into_iter()
3619            // Filter out no-ops to save some work.
3620            .filter(|(no_op, _, _)| !no_op)
3621            .map(|(_, k, v)| (k, Some(v)))
3622            .collect();
3623        self.set_many(kvs, ts)?;
3624        Ok(changed)
3625    }
3626
3627    /// Whether changing a key's value from `prev` to `next` can introduce a uniqueness violation
3628    fn update_needs_uniqueness_check(&self, prev: Option<&V>, next: Option<&V>) -> bool {
3629        match (&self.uniqueness_check, prev, next) {
3630            // No uniqueness constraint, or a delete: nothing to check.
3631            (None, _, _) | (_, _, None) => false,
3632            // An update: a scan is only needed if it changed the unique key.
3633            (Some(check), Some(prev), Some(next)) => {
3634                !(check.is_unique_key_unchanged_after_update)(prev, next)
3635            }
3636            // An insert: must check.
3637            (Some(_), None, Some(_)) => true,
3638        }
3639    }
3640
3641    /// Set the value for a key. Returns the previous entry if the key existed,
3642    /// otherwise None.
3643    ///
3644    /// Returns an error if the uniqueness check failed.
3645    ///
3646    /// DO NOT call this function in a loop, use [`Self::set_many`] instead.
3647    fn set(&mut self, k: K, v: Option<V>, ts: Timestamp) -> Result<Option<V>, DurableCatalogError> {
3648        let prev = self.get(&k).cloned();
3649        let needs_uniqueness_check = self.update_needs_uniqueness_check(prev.as_ref(), v.as_ref());
3650        let entry = self.pending.entry(k.clone()).or_default();
3651        let restore_len = entry.len();
3652
3653        match (v, prev.clone()) {
3654            (Some(v), Some(prev)) => {
3655                entry.push(TransactionUpdate {
3656                    value: prev,
3657                    ts,
3658                    diff: Diff::MINUS_ONE,
3659                });
3660                entry.push(TransactionUpdate {
3661                    value: v,
3662                    ts,
3663                    diff: Diff::ONE,
3664                });
3665            }
3666            (Some(v), None) => {
3667                entry.push(TransactionUpdate {
3668                    value: v,
3669                    ts,
3670                    diff: Diff::ONE,
3671                });
3672            }
3673            (None, Some(prev)) => {
3674                entry.push(TransactionUpdate {
3675                    value: prev,
3676                    ts,
3677                    diff: Diff::MINUS_ONE,
3678                });
3679            }
3680            (None, None) => {}
3681        }
3682
3683        // Check for uniqueness violation
3684        if needs_uniqueness_check {
3685            if let Err(err) = self.verify_keys([&k]) {
3686                // Revert self.pending to the state it was in before calling this
3687                // function.
3688                let pending = self.pending.get_mut(&k).expect("inserted above");
3689                pending.truncate(restore_len);
3690                return Err(err);
3691            }
3692        }
3693        Ok(prev)
3694    }
3695
3696    /// Set the values for many keys. Returns the previous entry for each key if the key existed,
3697    /// otherwise None.
3698    ///
3699    /// Returns an error if any uniqueness check failed.
3700    fn set_many(
3701        &mut self,
3702        kvs: BTreeMap<K, Option<V>>,
3703        ts: Timestamp,
3704    ) -> Result<BTreeMap<K, Option<V>>, DurableCatalogError> {
3705        if kvs.is_empty() {
3706            return Ok(BTreeMap::new());
3707        }
3708
3709        let mut prevs = BTreeMap::new();
3710        let mut restores = BTreeMap::new();
3711        // Only the keys whose update can introduce a uniqueness violation need scanning.
3712        let mut keys_to_verify_uniqueness = Vec::new();
3713
3714        for (k, v) in kvs {
3715            let prev = self.get(&k).cloned();
3716            if self.update_needs_uniqueness_check(prev.as_ref(), v.as_ref()) {
3717                keys_to_verify_uniqueness.push(k.clone());
3718            }
3719            let entry = self.pending.entry(k.clone()).or_default();
3720            restores.insert(k.clone(), entry.len());
3721
3722            match (v, prev.clone()) {
3723                (Some(v), Some(prev)) => {
3724                    entry.push(TransactionUpdate {
3725                        value: prev,
3726                        ts,
3727                        diff: Diff::MINUS_ONE,
3728                    });
3729                    entry.push(TransactionUpdate {
3730                        value: v,
3731                        ts,
3732                        diff: Diff::ONE,
3733                    });
3734                }
3735                (Some(v), None) => {
3736                    entry.push(TransactionUpdate {
3737                        value: v,
3738                        ts,
3739                        diff: Diff::ONE,
3740                    });
3741                }
3742                (None, Some(prev)) => {
3743                    entry.push(TransactionUpdate {
3744                        value: prev,
3745                        ts,
3746                        diff: Diff::MINUS_ONE,
3747                    });
3748                }
3749                (None, None) => {}
3750            }
3751
3752            prevs.insert(k, prev);
3753        }
3754
3755        // Check for uniqueness violation.
3756        if let Err(err) = self.verify_keys(keys_to_verify_uniqueness.iter()) {
3757            for (k, restore_len) in restores {
3758                // Revert self.pending to the state it was in before calling this
3759                // function.
3760                let pending = self.pending.get_mut(&k).expect("inserted above");
3761                pending.truncate(restore_len);
3762            }
3763            Err(err)
3764        } else {
3765            Ok(prevs)
3766        }
3767    }
3768
3769    /// Deletes items for which `f` returns true. Returns the keys and values of
3770    /// the deleted entries.
3771    ///
3772    /// Prefer using [`Self::delete_by_key`] or [`Self::delete_by_keys`], which generally have
3773    /// better performance.
3774    fn delete<F: Fn(&K, &V) -> bool>(&mut self, f: F, ts: Timestamp) -> Vec<(K, V)> {
3775        let mut deleted = Vec::new();
3776        self.for_values_mut(|p, k, v| {
3777            if f(k, v) {
3778                deleted.push((k.clone(), v.clone()));
3779                p.entry(k.clone()).or_default().push(TransactionUpdate {
3780                    value: v.clone(),
3781                    ts,
3782                    diff: Diff::MINUS_ONE,
3783                });
3784            }
3785        });
3786        soft_assert_no_log!(self.verify().is_ok());
3787        deleted
3788    }
3789
3790    /// Deletes item with key `k`.
3791    ///
3792    /// Returns the value of the deleted entry, if it existed.
3793    fn delete_by_key(&mut self, k: K, ts: Timestamp) -> Option<V> {
3794        self.set(k, None, ts)
3795            .expect("deleting an entry cannot violate uniqueness")
3796    }
3797
3798    /// Deletes items with key in `ks`.
3799    ///
3800    /// Returns the keys and values of the deleted entries.
3801    fn delete_by_keys(&mut self, ks: impl IntoIterator<Item = K>, ts: Timestamp) -> Vec<(K, V)> {
3802        let kvs = ks.into_iter().map(|k| (k, None)).collect();
3803        let prevs = self
3804            .set_many(kvs, ts)
3805            .expect("deleting entries cannot violate uniqueness");
3806        prevs
3807            .into_iter()
3808            .filter_map(|(k, v)| v.map(|v| (k, v)))
3809            .collect()
3810    }
3811}
3812
3813#[cfg(test)]
3814#[allow(clippy::unwrap_used)]
3815mod tests {
3816    use super::*;
3817
3818    use mz_controller::clusters::ReplicaLogging;
3819    use mz_ore::now::SYSTEM_TIME;
3820    use mz_ore::{assert_none, assert_ok};
3821    use mz_persist_client::cache::PersistClientCache;
3822    use mz_persist_types::PersistLocation;
3823    use semver::Version;
3824
3825    use crate::durable::{
3826        ReplicaConfig, ReplicaLocation, TestCatalogStateBuilder, test_bootstrap_args,
3827    };
3828    use crate::memory;
3829
3830    #[mz_ore::test]
3831    fn test_table_transaction_simple() {
3832        fn uniqueness_violation(a: &String, b: &String) -> bool {
3833            a == b
3834        }
3835        let mut table = TableTransaction::new_with_uniqueness_fn(
3836            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "a".to_string())]),
3837            uniqueness_violation,
3838            uniqueness_violation,
3839        )
3840        .unwrap();
3841
3842        // Ideally, we compare for errors here, but it's hard/impossible to implement PartialEq
3843        // for DurableCatalogError.
3844        assert_ok!(table.insert(2i64.to_le_bytes().to_vec(), "b".to_string(), 0));
3845        assert_ok!(table.insert(3i64.to_le_bytes().to_vec(), "c".to_string(), 0));
3846        assert!(
3847            table
3848                .insert(1i64.to_le_bytes().to_vec(), "c".to_string(), 0)
3849                .is_err()
3850        );
3851        assert!(
3852            table
3853                .insert(4i64.to_le_bytes().to_vec(), "c".to_string(), 0)
3854                .is_err()
3855        );
3856    }
3857
3858    #[mz_ore::test]
3859    fn test_skip_scan_when_unique_key_unchanged() {
3860        fn first_char_same(prev: &String, next: &String) -> bool {
3861            prev.chars().next() == next.chars().next()
3862        }
3863
3864        // Panics if the uniqueness scan ever runs
3865        fn panic_uniqueness_violation(_: &String, _: &String) -> bool {
3866            panic!("uniqueness scan ran for an update that kept the same unique key");
3867        }
3868        let mut table = TableTransaction::new_with_uniqueness_fn(
3869            BTreeMap::from([
3870                (1i64.to_le_bytes().to_vec(), "a1".to_string()),
3871                (2i64.to_le_bytes().to_vec(), "b1".to_string()),
3872            ]),
3873            panic_uniqueness_violation,
3874            // We treat the first character as the unique key.
3875            first_char_same,
3876        )
3877        .unwrap();
3878        // Same first char, so the unique key didn't change and the update must not scan
3879        // for uniqueness.
3880        assert!(
3881            table
3882                .update_by_key(1i64.to_le_bytes().to_vec(), "a2".to_string(), 0)
3883                .unwrap()
3884        );
3885
3886        // An update that changes the unique key must still run the scan and detect a collision.
3887        fn real_uniqueness_violation(a: &String, b: &String) -> bool {
3888            a.chars().next() == b.chars().next()
3889        }
3890        let mut table = TableTransaction::new_with_uniqueness_fn(
3891            BTreeMap::from([
3892                (1i64.to_le_bytes().to_vec(), "a1".to_string()),
3893                (2i64.to_le_bytes().to_vec(), "b1".to_string()),
3894            ]),
3895            real_uniqueness_violation,
3896            first_char_same,
3897        )
3898        .unwrap();
3899        // Changing key 1's first char from 'a' to 'b' changes its unique key and collides
3900        // with key 2.
3901        assert!(
3902            table
3903                .update_by_key(1i64.to_le_bytes().to_vec(), "b2".to_string(), 0)
3904                .is_err()
3905        );
3906    }
3907
3908    #[mz_ore::test]
3909    fn test_table_transaction() {
3910        fn uniqueness_violation(a: &String, b: &String) -> bool {
3911            a == b
3912        }
3913        let mut table: BTreeMap<Vec<u8>, String> = BTreeMap::new();
3914
3915        fn commit(
3916            table: &mut BTreeMap<Vec<u8>, String>,
3917            mut pending: Vec<(Vec<u8>, String, Diff)>,
3918        ) {
3919            // Sort by diff so that we process retractions first.
3920            pending.sort_by(|a, b| a.2.cmp(&b.2));
3921            for (k, v, diff) in pending {
3922                if diff == Diff::MINUS_ONE {
3923                    let prev = table.remove(&k);
3924                    assert_eq!(prev, Some(v));
3925                } else if diff == Diff::ONE {
3926                    let prev = table.insert(k, v);
3927                    assert_eq!(prev, None);
3928                } else {
3929                    panic!("unexpected diff: {diff}");
3930                }
3931            }
3932        }
3933
3934        table.insert(1i64.to_le_bytes().to_vec(), "v1".to_string());
3935        table.insert(2i64.to_le_bytes().to_vec(), "v2".to_string());
3936        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
3937            table.clone(),
3938            uniqueness_violation,
3939            uniqueness_violation,
3940        )
3941        .unwrap();
3942        assert_eq!(table_txn.items_cloned(), table);
3943        assert_eq!(table_txn.delete(|_k, _v| false, 0).len(), 0);
3944        assert_eq!(table_txn.delete(|_k, v| v == "v2", 1).len(), 1);
3945        assert_eq!(
3946            table_txn.items_cloned(),
3947            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v1".to_string())])
3948        );
3949        assert_eq!(
3950            table_txn
3951                .update(|_k, _v| Some("v3".to_string()), 2)
3952                .unwrap(),
3953            Diff::ONE
3954        );
3955
3956        // Uniqueness violation.
3957        table_txn
3958            .insert(3i64.to_le_bytes().to_vec(), "v3".to_string(), 3)
3959            .unwrap_err();
3960
3961        table_txn
3962            .insert(3i64.to_le_bytes().to_vec(), "v4".to_string(), 4)
3963            .unwrap();
3964        assert_eq!(
3965            table_txn.items_cloned(),
3966            BTreeMap::from([
3967                (1i64.to_le_bytes().to_vec(), "v3".to_string()),
3968                (3i64.to_le_bytes().to_vec(), "v4".to_string()),
3969            ])
3970        );
3971        let err = table_txn
3972            .update(|_k, _v| Some("v1".to_string()), 5)
3973            .unwrap_err();
3974        assert!(
3975            matches!(err, DurableCatalogError::UniquenessViolation),
3976            "unexpected err: {err:?}"
3977        );
3978        let pending = table_txn.pending();
3979        assert_eq!(
3980            pending,
3981            vec![
3982                (
3983                    1i64.to_le_bytes().to_vec(),
3984                    "v1".to_string(),
3985                    Diff::MINUS_ONE
3986                ),
3987                (1i64.to_le_bytes().to_vec(), "v3".to_string(), Diff::ONE),
3988                (
3989                    2i64.to_le_bytes().to_vec(),
3990                    "v2".to_string(),
3991                    Diff::MINUS_ONE
3992                ),
3993                (3i64.to_le_bytes().to_vec(), "v4".to_string(), Diff::ONE),
3994            ]
3995        );
3996        commit(&mut table, pending);
3997        assert_eq!(
3998            table,
3999            BTreeMap::from([
4000                (1i64.to_le_bytes().to_vec(), "v3".to_string()),
4001                (3i64.to_le_bytes().to_vec(), "v4".to_string())
4002            ])
4003        );
4004
4005        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4006            table.clone(),
4007            uniqueness_violation,
4008            uniqueness_violation,
4009        )
4010        .unwrap();
4011        // Deleting then creating an item that has a uniqueness violation should work.
4012        assert_eq!(
4013            table_txn.delete(|k, _v| k == &1i64.to_le_bytes(), 0).len(),
4014            1
4015        );
4016        table_txn
4017            .insert(1i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4018            .unwrap();
4019        // Uniqueness violation in value.
4020        table_txn
4021            .insert(5i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4022            .unwrap_err();
4023        // Key already exists, expect error.
4024        table_txn
4025            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 0)
4026            .unwrap_err();
4027        assert_eq!(
4028            table_txn.delete(|k, _v| k == &1i64.to_le_bytes(), 0).len(),
4029            1
4030        );
4031        // Both the inserts work now because the key and uniqueness violation are gone.
4032        table_txn
4033            .insert(5i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4034            .unwrap();
4035        table_txn
4036            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 0)
4037            .unwrap();
4038        let pending = table_txn.pending();
4039        assert_eq!(
4040            pending,
4041            vec![
4042                (
4043                    1i64.to_le_bytes().to_vec(),
4044                    "v3".to_string(),
4045                    Diff::MINUS_ONE
4046                ),
4047                (1i64.to_le_bytes().to_vec(), "v5".to_string(), Diff::ONE),
4048                (5i64.to_le_bytes().to_vec(), "v3".to_string(), Diff::ONE),
4049            ]
4050        );
4051        commit(&mut table, pending);
4052        assert_eq!(
4053            table,
4054            BTreeMap::from([
4055                (1i64.to_le_bytes().to_vec(), "v5".to_string()),
4056                (3i64.to_le_bytes().to_vec(), "v4".to_string()),
4057                (5i64.to_le_bytes().to_vec(), "v3".to_string()),
4058            ])
4059        );
4060
4061        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4062            table.clone(),
4063            uniqueness_violation,
4064            uniqueness_violation,
4065        )
4066        .unwrap();
4067        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 3);
4068        table_txn
4069            .insert(1i64.to_le_bytes().to_vec(), "v1".to_string(), 0)
4070            .unwrap();
4071
4072        commit(&mut table, table_txn.pending());
4073        assert_eq!(
4074            table,
4075            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v1".to_string()),])
4076        );
4077
4078        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4079            table.clone(),
4080            uniqueness_violation,
4081            uniqueness_violation,
4082        )
4083        .unwrap();
4084        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 1);
4085        table_txn
4086            .insert(1i64.to_le_bytes().to_vec(), "v2".to_string(), 0)
4087            .unwrap();
4088        commit(&mut table, table_txn.pending());
4089        assert_eq!(
4090            table,
4091            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v2".to_string()),])
4092        );
4093
4094        // Verify we don't try to delete v3 or v4 during commit.
4095        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4096            table.clone(),
4097            uniqueness_violation,
4098            uniqueness_violation,
4099        )
4100        .unwrap();
4101        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 1);
4102        table_txn
4103            .insert(1i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4104            .unwrap();
4105        table_txn
4106            .insert(1i64.to_le_bytes().to_vec(), "v4".to_string(), 1)
4107            .unwrap_err();
4108        assert_eq!(table_txn.delete(|_k, _v| true, 1).len(), 1);
4109        table_txn
4110            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 1)
4111            .unwrap();
4112        commit(&mut table, table_txn.pending());
4113        assert_eq!(
4114            table.clone().into_iter().collect::<Vec<_>>(),
4115            vec![(1i64.to_le_bytes().to_vec(), "v5".to_string())]
4116        );
4117
4118        // Test `set`.
4119        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4120            table.clone(),
4121            uniqueness_violation,
4122            uniqueness_violation,
4123        )
4124        .unwrap();
4125        // Uniqueness violation.
4126        table_txn
4127            .set(2i64.to_le_bytes().to_vec(), Some("v5".to_string()), 0)
4128            .unwrap_err();
4129        table_txn
4130            .set(3i64.to_le_bytes().to_vec(), Some("v6".to_string()), 1)
4131            .unwrap();
4132        table_txn.set(2i64.to_le_bytes().to_vec(), None, 2).unwrap();
4133        table_txn.set(1i64.to_le_bytes().to_vec(), None, 2).unwrap();
4134        let pending = table_txn.pending();
4135        assert_eq!(
4136            pending,
4137            vec![
4138                (
4139                    1i64.to_le_bytes().to_vec(),
4140                    "v5".to_string(),
4141                    Diff::MINUS_ONE
4142                ),
4143                (3i64.to_le_bytes().to_vec(), "v6".to_string(), Diff::ONE),
4144            ]
4145        );
4146        commit(&mut table, pending);
4147        assert_eq!(
4148            table,
4149            BTreeMap::from([(3i64.to_le_bytes().to_vec(), "v6".to_string())])
4150        );
4151
4152        // Duplicate `set`.
4153        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4154            table.clone(),
4155            uniqueness_violation,
4156            uniqueness_violation,
4157        )
4158        .unwrap();
4159        table_txn
4160            .set(3i64.to_le_bytes().to_vec(), Some("v6".to_string()), 0)
4161            .unwrap();
4162        let pending = table_txn.pending::<Vec<u8>, String>();
4163        assert!(pending.is_empty());
4164
4165        // Test `set_many`.
4166        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4167            table.clone(),
4168            uniqueness_violation,
4169            uniqueness_violation,
4170        )
4171        .unwrap();
4172        // Uniqueness violation.
4173        table_txn
4174            .set_many(
4175                BTreeMap::from([
4176                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4177                    (42i64.to_le_bytes().to_vec(), Some("v1".to_string())),
4178                ]),
4179                0,
4180            )
4181            .unwrap_err();
4182        table_txn
4183            .set_many(
4184                BTreeMap::from([
4185                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4186                    (3i64.to_le_bytes().to_vec(), Some("v1".to_string())),
4187                ]),
4188                1,
4189            )
4190            .unwrap();
4191        table_txn
4192            .set_many(
4193                BTreeMap::from([
4194                    (42i64.to_le_bytes().to_vec(), Some("v7".to_string())),
4195                    (3i64.to_le_bytes().to_vec(), None),
4196                ]),
4197                2,
4198            )
4199            .unwrap();
4200        let pending = table_txn.pending();
4201        assert_eq!(
4202            pending,
4203            vec![
4204                (1i64.to_le_bytes().to_vec(), "v6".to_string(), Diff::ONE),
4205                (
4206                    3i64.to_le_bytes().to_vec(),
4207                    "v6".to_string(),
4208                    Diff::MINUS_ONE
4209                ),
4210                (42i64.to_le_bytes().to_vec(), "v7".to_string(), Diff::ONE),
4211            ]
4212        );
4213        commit(&mut table, pending);
4214        assert_eq!(
4215            table,
4216            BTreeMap::from([
4217                (1i64.to_le_bytes().to_vec(), "v6".to_string()),
4218                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4219            ])
4220        );
4221
4222        // Duplicate `set_many`.
4223        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4224            table.clone(),
4225            uniqueness_violation,
4226            uniqueness_violation,
4227        )
4228        .unwrap();
4229        table_txn
4230            .set_many(
4231                BTreeMap::from([
4232                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4233                    (42i64.to_le_bytes().to_vec(), Some("v7".to_string())),
4234                ]),
4235                0,
4236            )
4237            .unwrap();
4238        let pending = table_txn.pending::<Vec<u8>, String>();
4239        assert!(pending.is_empty());
4240        commit(&mut table, pending);
4241        assert_eq!(
4242            table,
4243            BTreeMap::from([
4244                (1i64.to_le_bytes().to_vec(), "v6".to_string()),
4245                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4246            ])
4247        );
4248
4249        // Test `update_by_key`
4250        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4251            table.clone(),
4252            uniqueness_violation,
4253            uniqueness_violation,
4254        )
4255        .unwrap();
4256        // Uniqueness violation.
4257        table_txn
4258            .update_by_key(1i64.to_le_bytes().to_vec(), "v7".to_string(), 0)
4259            .unwrap_err();
4260        assert!(
4261            table_txn
4262                .update_by_key(1i64.to_le_bytes().to_vec(), "v8".to_string(), 1)
4263                .unwrap()
4264        );
4265        assert!(
4266            !table_txn
4267                .update_by_key(5i64.to_le_bytes().to_vec(), "v8".to_string(), 2)
4268                .unwrap()
4269        );
4270        let pending = table_txn.pending();
4271        assert_eq!(
4272            pending,
4273            vec![
4274                (
4275                    1i64.to_le_bytes().to_vec(),
4276                    "v6".to_string(),
4277                    Diff::MINUS_ONE
4278                ),
4279                (1i64.to_le_bytes().to_vec(), "v8".to_string(), Diff::ONE),
4280            ]
4281        );
4282        commit(&mut table, pending);
4283        assert_eq!(
4284            table,
4285            BTreeMap::from([
4286                (1i64.to_le_bytes().to_vec(), "v8".to_string()),
4287                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4288            ])
4289        );
4290
4291        // Duplicate `update_by_key`.
4292        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4293            table.clone(),
4294            uniqueness_violation,
4295            uniqueness_violation,
4296        )
4297        .unwrap();
4298        assert!(
4299            table_txn
4300                .update_by_key(1i64.to_le_bytes().to_vec(), "v8".to_string(), 0)
4301                .unwrap()
4302        );
4303        let pending = table_txn.pending::<Vec<u8>, String>();
4304        assert!(pending.is_empty());
4305        commit(&mut table, pending);
4306        assert_eq!(
4307            table,
4308            BTreeMap::from([
4309                (1i64.to_le_bytes().to_vec(), "v8".to_string()),
4310                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4311            ])
4312        );
4313
4314        // Test `update_by_keys`
4315        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4316            table.clone(),
4317            uniqueness_violation,
4318            uniqueness_violation,
4319        )
4320        .unwrap();
4321        // Uniqueness violation.
4322        table_txn
4323            .update_by_keys(
4324                [
4325                    (1i64.to_le_bytes().to_vec(), "v7".to_string()),
4326                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4327                ],
4328                0,
4329            )
4330            .unwrap_err();
4331        let n = table_txn
4332            .update_by_keys(
4333                [
4334                    (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4335                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4336                ],
4337                1,
4338            )
4339            .unwrap();
4340        assert_eq!(n, Diff::ONE);
4341        let n = table_txn
4342            .update_by_keys(
4343                [
4344                    (15i64.to_le_bytes().to_vec(), "v9".to_string()),
4345                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4346                ],
4347                2,
4348            )
4349            .unwrap();
4350        assert_eq!(n, Diff::ZERO);
4351        let pending = table_txn.pending();
4352        assert_eq!(
4353            pending,
4354            vec![
4355                (
4356                    1i64.to_le_bytes().to_vec(),
4357                    "v8".to_string(),
4358                    Diff::MINUS_ONE
4359                ),
4360                (1i64.to_le_bytes().to_vec(), "v9".to_string(), Diff::ONE),
4361            ]
4362        );
4363        commit(&mut table, pending);
4364        assert_eq!(
4365            table,
4366            BTreeMap::from([
4367                (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4368                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4369            ])
4370        );
4371
4372        // Duplicate `update_by_keys`.
4373        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4374            table.clone(),
4375            uniqueness_violation,
4376            uniqueness_violation,
4377        )
4378        .unwrap();
4379        let n = table_txn
4380            .update_by_keys(
4381                [
4382                    (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4383                    (42i64.to_le_bytes().to_vec(), "v7".to_string()),
4384                ],
4385                0,
4386            )
4387            .unwrap();
4388        assert_eq!(n, Diff::from(2));
4389        let pending = table_txn.pending::<Vec<u8>, String>();
4390        assert!(pending.is_empty());
4391        commit(&mut table, pending);
4392        assert_eq!(
4393            table,
4394            BTreeMap::from([
4395                (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4396                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4397            ])
4398        );
4399
4400        // Test `delete_by_key`
4401        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4402            table.clone(),
4403            uniqueness_violation,
4404            uniqueness_violation,
4405        )
4406        .unwrap();
4407        let prev = table_txn.delete_by_key(1i64.to_le_bytes().to_vec(), 0);
4408        assert_eq!(prev, Some("v9".to_string()));
4409        let prev = table_txn.delete_by_key(5i64.to_le_bytes().to_vec(), 1);
4410        assert_none!(prev);
4411        let prev = table_txn.delete_by_key(1i64.to_le_bytes().to_vec(), 2);
4412        assert_none!(prev);
4413        let pending = table_txn.pending();
4414        assert_eq!(
4415            pending,
4416            vec![(
4417                1i64.to_le_bytes().to_vec(),
4418                "v9".to_string(),
4419                Diff::MINUS_ONE
4420            ),]
4421        );
4422        commit(&mut table, pending);
4423        assert_eq!(
4424            table,
4425            BTreeMap::from([(42i64.to_le_bytes().to_vec(), "v7".to_string())])
4426        );
4427
4428        // Test `delete_by_keys`
4429        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4430            table.clone(),
4431            uniqueness_violation,
4432            uniqueness_violation,
4433        )
4434        .unwrap();
4435        let prevs = table_txn.delete_by_keys(
4436            [42i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4437            0,
4438        );
4439        assert_eq!(
4440            prevs,
4441            vec![(42i64.to_le_bytes().to_vec(), "v7".to_string())]
4442        );
4443        let prevs = table_txn.delete_by_keys(
4444            [42i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4445            1,
4446        );
4447        assert_eq!(prevs, vec![]);
4448        let prevs = table_txn.delete_by_keys(
4449            [10i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4450            2,
4451        );
4452        assert_eq!(prevs, vec![]);
4453        let pending = table_txn.pending();
4454        assert_eq!(
4455            pending,
4456            vec![(
4457                42i64.to_le_bytes().to_vec(),
4458                "v7".to_string(),
4459                Diff::MINUS_ONE
4460            ),]
4461        );
4462        commit(&mut table, pending);
4463        assert_eq!(table, BTreeMap::new());
4464    }
4465
4466    #[mz_ore::test(tokio::test)]
4467    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4468    async fn test_savepoint() {
4469        const VERSION: Version = Version::new(26, 0, 0);
4470        let mut persist_cache = PersistClientCache::new_no_metrics();
4471        persist_cache.cfg.build_version = VERSION;
4472        let persist_client = persist_cache
4473            .open(PersistLocation::new_in_mem())
4474            .await
4475            .unwrap();
4476        let state_builder = TestCatalogStateBuilder::new(persist_client)
4477            .with_default_deploy_generation()
4478            .with_version(VERSION);
4479
4480        // Initialize catalog.
4481        let _ = state_builder
4482            .clone()
4483            .unwrap_build()
4484            .await
4485            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4486            .await
4487            .unwrap();
4488        let mut savepoint_state = state_builder
4489            .unwrap_build()
4490            .await
4491            .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args())
4492            .await
4493            .unwrap();
4494
4495        let initial_snapshot = savepoint_state.sync_to_current_updates().await.unwrap();
4496        assert!(!initial_snapshot.is_empty());
4497
4498        let db_name = "db";
4499        let db_owner = RoleId::User(42);
4500        let db_privileges = Vec::new();
4501        let mut txn = savepoint_state.transaction().await.unwrap();
4502        let (db_id, db_oid) = txn
4503            .insert_user_database(db_name, db_owner, db_privileges.clone(), &HashSet::new())
4504            .unwrap();
4505        let commit_ts = txn.upper();
4506        txn.commit_internal(commit_ts).await.unwrap();
4507        let updates = savepoint_state.sync_to_current_updates().await.unwrap();
4508        let update = updates.into_element();
4509
4510        assert_eq!(update.diff, StateDiff::Addition);
4511
4512        let db = match update.kind {
4513            memory::objects::StateUpdateKind::Database(db) => db,
4514            update => panic!("unexpected update: {update:?}"),
4515        };
4516
4517        assert_eq!(db_id, db.id);
4518        assert_eq!(db_oid, db.oid);
4519        assert_eq!(db_name, db.name);
4520        assert_eq!(db_owner, db.owner_id);
4521        assert_eq!(db_privileges, db.privileges);
4522    }
4523
4524    #[mz_ore::test(tokio::test)]
4525    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4526    async fn test_dry_run_transaction_rejects_internal_commit() {
4527        const VERSION: Version = Version::new(26, 0, 0);
4528        let mut persist_cache = PersistClientCache::new_no_metrics();
4529        persist_cache.cfg.build_version = VERSION;
4530        let persist_client = persist_cache
4531            .open(PersistLocation::new_in_mem())
4532            .await
4533            .unwrap();
4534        let mut state = TestCatalogStateBuilder::new(persist_client)
4535            .with_default_deploy_generation()
4536            .with_version(VERSION)
4537            .unwrap_build()
4538            .await
4539            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4540            .await
4541            .unwrap();
4542        let _ = state.sync_to_current_updates().await.unwrap();
4543
4544        let initial_id = state.get_next_id(USER_ITEM_ALLOC_KEY).await.unwrap();
4545        let initial_upper = state.current_upper().await;
4546        let snapshot = state.snapshot().await.unwrap();
4547        let mut dry_run = state.transaction_from_snapshot(snapshot).unwrap();
4548        let ids = dry_run
4549            .transaction_mut()
4550            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), 1)
4551            .unwrap();
4552        assert_eq!(ids, vec![initial_id]);
4553
4554        let transaction = dry_run.transaction;
4555        let err = transaction
4556            .commit_internal(initial_upper)
4557            .await
4558            .unwrap_err();
4559        assert!(matches!(
4560            err,
4561            CatalogError::Durable(DurableCatalogError::DryRunTransaction)
4562        ));
4563        assert_eq!(state.current_upper().await, initial_upper);
4564        assert_eq!(
4565            state.get_next_id(USER_ITEM_ALLOC_KEY).await.unwrap(),
4566            initial_id
4567        );
4568    }
4569
4570    #[mz_ore::test(tokio::test)]
4571    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4572    async fn test_dry_run_transaction_rejects_into_parts_escape() {
4573        const VERSION: Version = Version::new(26, 0, 0);
4574        let mut persist_cache = PersistClientCache::new_no_metrics();
4575        persist_cache.cfg.build_version = VERSION;
4576        let persist_client = persist_cache
4577            .open(PersistLocation::new_in_mem())
4578            .await
4579            .unwrap();
4580        let mut dry_run_state = TestCatalogStateBuilder::new(persist_client.clone())
4581            .with_default_deploy_generation()
4582            .with_version(VERSION)
4583            .unwrap_build()
4584            .await
4585            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4586            .await
4587            .unwrap();
4588        let mut replacement_state = TestCatalogStateBuilder::new(persist_client)
4589            .with_default_deploy_generation()
4590            .with_version(VERSION)
4591            .unwrap_build()
4592            .await
4593            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4594            .await
4595            .unwrap();
4596        let _ = dry_run_state.sync_to_current_updates().await.unwrap();
4597        let _ = replacement_state.sync_to_current_updates().await.unwrap();
4598
4599        let initial_id = dry_run_state
4600            .get_next_id(USER_ITEM_ALLOC_KEY)
4601            .await
4602            .unwrap();
4603        let initial_upper = dry_run_state.current_upper().await;
4604        let snapshot = dry_run_state.snapshot().await.unwrap();
4605        let mut dry_run = dry_run_state.transaction_from_snapshot(snapshot).unwrap();
4606        let ids = dry_run
4607            .transaction_mut()
4608            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), 1)
4609            .unwrap();
4610        assert_eq!(ids, vec![initial_id]);
4611
4612        let replacement = replacement_state.transaction().await.unwrap();
4613        let escaped = std::mem::replace(dry_run.transaction_mut(), replacement);
4614        drop(dry_run);
4615
4616        let err = match escaped.into_parts() {
4617            Ok(_) => panic!("dry-run transaction decomposed into committable parts"),
4618            Err(err) => err,
4619        };
4620        assert!(matches!(
4621            err,
4622            CatalogError::Durable(DurableCatalogError::DryRunTransaction)
4623        ));
4624        assert_eq!(dry_run_state.current_upper().await, initial_upper);
4625        assert_eq!(
4626            dry_run_state
4627                .get_next_id(USER_ITEM_ALLOC_KEY)
4628                .await
4629                .unwrap(),
4630            initial_id
4631        );
4632    }
4633
4634    /// Regression test for DB-147: inserting a replica with an explicit id must not consume the
4635    /// `IdAlloc` counter, and the durable allocator must advance independently so a later
4636    /// allocation never collides with an explicitly inserted id.
4637    #[mz_ore::test(tokio::test)]
4638    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4639    async fn test_insert_replica_with_id_does_not_consume_allocator() {
4640        const VERSION: Version = Version::new(26, 0, 0);
4641        let mut persist_cache = PersistClientCache::new_no_metrics();
4642        persist_cache.cfg.build_version = VERSION;
4643        let persist_client = persist_cache
4644            .open(PersistLocation::new_in_mem())
4645            .await
4646            .unwrap();
4647        let state_builder = TestCatalogStateBuilder::new(persist_client)
4648            .with_default_deploy_generation()
4649            .with_version(VERSION);
4650        let mut state = state_builder
4651            .unwrap_build()
4652            .await
4653            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4654            .await
4655            .unwrap();
4656
4657        // The cluster does not need to exist: `insert_cluster_replica_with_id` only writes a
4658        // `cluster_replicas` row and does not validate the referenced cluster.
4659        let cluster_id = ClusterId::User(1);
4660        let owner_id = RoleId::User(1);
4661        let config = ReplicaConfig {
4662            location: ReplicaLocation::Managed {
4663                size: "1".to_string(),
4664                availability_zones: Vec::new(),
4665                internal: false,
4666                billed_as: None,
4667                pending: false,
4668            },
4669            logging: ReplicaLogging {
4670                log_logging: false,
4671                interval: Some(Duration::from_secs(1)),
4672            },
4673            arrangement_compression: false,
4674        };
4675
4676        // Step 1: allocate one user replica id out-of-band via the durable allocator.
4677        let commit_ts = state.current_upper().await;
4678        let a = state
4679            .allocate_user_replica_ids(1, commit_ts)
4680            .await
4681            .unwrap()
4682            .into_element();
4683        assert!(a.is_user());
4684
4685        let initial_updates = state.sync_to_current_updates().await.unwrap();
4686        assert!(!initial_updates.is_empty());
4687
4688        // Step 2: insert a replica with that explicit id and commit.
4689        let mut txn = state.transaction().await.unwrap();
4690        txn.insert_cluster_replica_with_id(cluster_id, a, "explicit", config, owner_id)
4691            .unwrap();
4692        let commit_ts = txn.upper();
4693        txn.commit_internal(commit_ts).await.unwrap();
4694        let _ = state.sync_to_current_updates().await.unwrap();
4695
4696        // Step 3: allocate one more user replica id.
4697        let commit_ts = state.current_upper().await;
4698        let b = state
4699            .allocate_user_replica_ids(1, commit_ts)
4700            .await
4701            .unwrap()
4702            .into_element();
4703
4704        // Step 4: the explicit insert consumed zero ids, so the allocator's next value is the
4705        // direct successor of the first allocation.
4706        assert_eq!(b.inner_id(), a.inner_id() + 1);
4707
4708        // Step 5: the explicitly inserted replica is present in the committed state.
4709        let txn = state.transaction().await.unwrap();
4710        let found = txn
4711            .get_cluster_replicas()
4712            .any(|replica| replica.replica_id == a);
4713        assert!(found, "explicitly inserted replica {a} not found");
4714    }
4715
4716    #[mz_ore::test]
4717    fn test_allocate_introspection_source_index_id() {
4718        let cluster_variant: u8 = 0b0000_0001;
4719        let cluster_id_inner: u64 =
4720            0b0000_0000_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010;
4721        let timely_messages_received_log_variant: u8 = 0b0000_1000;
4722
4723        let cluster_id = ClusterId::System(cluster_id_inner);
4724        let log_variant = LogVariant::Timely(TimelyLog::MessagesReceived);
4725
4726        let introspection_source_index_id: u64 =
4727            0b0000_0001_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010_0000_1000;
4728
4729        // Sanity check that `introspection_source_index_id` contains `cluster_variant`.
4730        {
4731            let mut cluster_variant_mask = 0xFF << 56;
4732            cluster_variant_mask &= introspection_source_index_id;
4733            cluster_variant_mask >>= 56;
4734            assert_eq!(cluster_variant_mask, u64::from(cluster_variant));
4735        }
4736
4737        // Sanity check that `introspection_source_index_id` contains `cluster_id_inner`.
4738        {
4739            let mut cluster_id_inner_mask = 0xFFFF_FFFF_FFFF << 8;
4740            cluster_id_inner_mask &= introspection_source_index_id;
4741            cluster_id_inner_mask >>= 8;
4742            assert_eq!(cluster_id_inner_mask, cluster_id_inner);
4743        }
4744
4745        // Sanity check that `introspection_source_index_id` contains `timely_messages_received_log_variant`.
4746        {
4747            let mut log_variant_mask = 0xFF;
4748            log_variant_mask &= introspection_source_index_id;
4749            assert_eq!(
4750                log_variant_mask,
4751                u64::from(timely_messages_received_log_variant)
4752            );
4753        }
4754
4755        let (catalog_item_id, global_id) =
4756            Transaction::allocate_introspection_source_index_id(&cluster_id, log_variant);
4757
4758        assert_eq!(
4759            catalog_item_id,
4760            CatalogItemId::IntrospectionSourceIndex(introspection_source_index_id)
4761        );
4762        assert_eq!(
4763            global_id,
4764            GlobalId::IntrospectionSourceIndex(introspection_source_index_id)
4765        );
4766    }
4767}