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        };
1025
1026        let mut id: u64 = u64::from(cluster_variant) << 56;
1027        id |= cluster_id << 8;
1028        id |= u64::from(log_variant);
1029
1030        (
1031            CatalogItemId::IntrospectionSourceIndex(id),
1032            GlobalId::IntrospectionSourceIndex(id),
1033        )
1034    }
1035
1036    pub fn allocate_user_item_ids(
1037        &mut self,
1038        amount: u64,
1039    ) -> Result<Vec<(CatalogItemId, GlobalId)>, CatalogError> {
1040        Ok(self
1041            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), amount)?
1042            .into_iter()
1043            // TODO(alter_table): Use separate ID allocators.
1044            .map(|x| (CatalogItemId::User(x), GlobalId::User(x)))
1045            .collect())
1046    }
1047
1048    pub fn allocate_system_replica_id(&mut self) -> Result<ReplicaId, CatalogError> {
1049        let id = self.get_and_increment_id(SYSTEM_REPLICA_ID_ALLOC_KEY.to_string())?;
1050        Ok(ReplicaId::System(id))
1051    }
1052
1053    pub fn allocate_audit_log_id(&mut self) -> Result<u64, CatalogError> {
1054        self.get_and_increment_id(AUDIT_LOG_ID_ALLOC_KEY.to_string())
1055    }
1056
1057    /// Allocates `amount` OIDs. OIDs can be recycled if they aren't currently assigned to any
1058    /// object.
1059    #[mz_ore::instrument]
1060    fn allocate_oids(
1061        &mut self,
1062        amount: u64,
1063        temporary_oids: &HashSet<u32>,
1064    ) -> Result<Vec<u32>, CatalogError> {
1065        /// Struct representing an OID for a user object. Allocated OIDs can be recycled, so when we've
1066        /// allocated [`u32::MAX`] we'll wrap back around to [`FIRST_USER_OID`].
1067        struct UserOid(u32);
1068
1069        impl UserOid {
1070            fn new(oid: u32) -> Result<UserOid, anyhow::Error> {
1071                if oid < FIRST_USER_OID {
1072                    Err(anyhow!("invalid user OID {oid}"))
1073                } else {
1074                    Ok(UserOid(oid))
1075                }
1076            }
1077        }
1078
1079        impl std::ops::AddAssign<u32> for UserOid {
1080            fn add_assign(&mut self, rhs: u32) {
1081                let (res, overflow) = self.0.overflowing_add(rhs);
1082                self.0 = if overflow { FIRST_USER_OID + res } else { res };
1083            }
1084        }
1085
1086        if amount > u32::MAX.into() {
1087            return Err(CatalogError::Catalog(SqlCatalogError::OidExhaustion));
1088        }
1089
1090        // This is potentially slow to do everytime we allocate an OID. A faster approach might be
1091        // to have an ID allocator that is updated everytime an OID is allocated or de-allocated.
1092        // However, benchmarking shows that this doesn't make a noticeable difference and the other
1093        // approach requires making sure that allocator always stays in-sync which can be
1094        // error-prone. If DDL starts slowing down, this is a good place to try and optimize.
1095        let mut allocated_oids = HashSet::with_capacity(
1096            self.databases.len()
1097                + self.schemas.len()
1098                + self.roles.len()
1099                + self.items.len()
1100                + self.introspection_sources.len()
1101                + temporary_oids.len(),
1102        );
1103        self.databases.for_values(|_, value| {
1104            allocated_oids.insert(value.oid);
1105        });
1106        self.schemas.for_values(|_, value| {
1107            allocated_oids.insert(value.oid);
1108        });
1109        self.roles.for_values(|_, value| {
1110            allocated_oids.insert(value.oid);
1111        });
1112        self.items.for_values(|_, value| {
1113            allocated_oids.insert(value.oid);
1114        });
1115        self.introspection_sources.for_values(|_, value| {
1116            allocated_oids.insert(value.oid);
1117        });
1118
1119        let is_allocated = |oid| allocated_oids.contains(&oid) || temporary_oids.contains(&oid);
1120
1121        let start_oid: u32 = self
1122            .id_allocator
1123            .items()
1124            .get(&IdAllocKey {
1125                name: OID_ALLOC_KEY.to_string(),
1126            })
1127            .unwrap_or_else(|| panic!("{OID_ALLOC_KEY} id allocator missing"))
1128            .next_id
1129            .try_into()
1130            .expect("we should never persist an oid outside of the u32 range");
1131        let mut current_oid = UserOid::new(start_oid)
1132            .expect("we should never persist an oid outside of user OID range");
1133        let mut oids = Vec::new();
1134        while oids.len() < u64_to_usize(amount) {
1135            if !is_allocated(current_oid.0) {
1136                oids.push(current_oid.0);
1137            }
1138            current_oid += 1;
1139
1140            if current_oid.0 == start_oid && oids.len() < u64_to_usize(amount) {
1141                // We've exhausted all possible OIDs and still don't have `amount`.
1142                return Err(CatalogError::Catalog(SqlCatalogError::OidExhaustion));
1143            }
1144        }
1145
1146        let next_id = current_oid.0;
1147        let prev = self.id_allocator.set(
1148            IdAllocKey {
1149                name: OID_ALLOC_KEY.to_string(),
1150            },
1151            Some(IdAllocValue {
1152                next_id: next_id.into(),
1153            }),
1154            self.op_id,
1155        )?;
1156        assert_eq!(
1157            prev,
1158            Some(IdAllocValue {
1159                next_id: start_oid.into(),
1160            })
1161        );
1162
1163        Ok(oids)
1164    }
1165
1166    /// Allocates a single OID. OIDs can be recycled if they aren't currently assigned to any
1167    /// object.
1168    pub fn allocate_oid(&mut self, temporary_oids: &HashSet<u32>) -> Result<u32, CatalogError> {
1169        self.allocate_oids(1, temporary_oids)
1170            .map(|oids| oids.into_element())
1171    }
1172
1173    /// Exports the current state of this transaction as a [`Snapshot`].
1174    ///
1175    /// This merges each `TableTransaction`'s initial data with its pending
1176    /// changes to produce the current view, then converts back to proto types.
1177    /// Used to persist transaction state between incremental DDL dry runs so
1178    /// the next dry run's fresh `Transaction` starts in sync with the
1179    /// accumulated `CatalogState`.
1180    pub fn current_snapshot(&self) -> Snapshot {
1181        Snapshot {
1182            databases: self.databases.current_items_proto(),
1183            schemas: self.schemas.current_items_proto(),
1184            roles: self.roles.current_items_proto(),
1185            role_auth: self.role_auth.current_items_proto(),
1186            items: self.items.current_items_proto(),
1187            comments: self.comments.current_items_proto(),
1188            clusters: self.clusters.current_items_proto(),
1189            network_policies: self.network_policies.current_items_proto(),
1190            cluster_replicas: self.cluster_replicas.current_items_proto(),
1191            introspection_sources: self.introspection_sources.current_items_proto(),
1192            id_allocator: self.id_allocator.current_items_proto(),
1193            configs: self.configs.current_items_proto(),
1194            settings: self.settings.current_items_proto(),
1195            system_object_mappings: self.system_gid_mapping.current_items_proto(),
1196            system_configurations: self.system_configurations.current_items_proto(),
1197            cluster_system_configurations: self.cluster_system_configurations.current_items_proto(),
1198            replica_system_configurations: self.replica_system_configurations.current_items_proto(),
1199            default_privileges: self.default_privileges.current_items_proto(),
1200            source_references: self.source_references.current_items_proto(),
1201            system_privileges: self.system_privileges.current_items_proto(),
1202            storage_collection_metadata: self.storage_collection_metadata.current_items_proto(),
1203            unfinalized_shards: self.unfinalized_shards.current_items_proto(),
1204            txn_wal_shard: self.txn_wal_shard.current_items_proto(),
1205        }
1206    }
1207
1208    pub(crate) fn insert_id_allocator(
1209        &mut self,
1210        name: String,
1211        next_id: u64,
1212    ) -> Result<(), CatalogError> {
1213        match self.id_allocator.insert(
1214            IdAllocKey { name: name.clone() },
1215            IdAllocValue { next_id },
1216            self.op_id,
1217        ) {
1218            Ok(_) => Ok(()),
1219            Err(_) => Err(SqlCatalogError::IdAllocatorAlreadyExists(name).into()),
1220        }
1221    }
1222
1223    /// Removes the database `id` from the transaction.
1224    ///
1225    /// Returns an error if `id` is not found.
1226    ///
1227    /// Runtime is linear with respect to the total number of databases in the catalog.
1228    /// DO NOT call this function in a loop, use [`Self::remove_databases`] instead.
1229    pub fn remove_database(&mut self, id: &DatabaseId) -> Result<(), CatalogError> {
1230        let prev = self
1231            .databases
1232            .set(DatabaseKey { id: *id }, None, self.op_id)?;
1233        if prev.is_some() {
1234            Ok(())
1235        } else {
1236            Err(SqlCatalogError::UnknownDatabase(id.to_string()).into())
1237        }
1238    }
1239
1240    /// Removes all databases in `databases` from the transaction.
1241    ///
1242    /// Returns an error if any id in `databases` is not found.
1243    ///
1244    /// NOTE: On error, there still may be some databases removed from the transaction. It
1245    /// is up to the caller to either abort the transaction or commit.
1246    pub fn remove_databases(
1247        &mut self,
1248        databases: &BTreeSet<DatabaseId>,
1249    ) -> Result<(), CatalogError> {
1250        if databases.is_empty() {
1251            return Ok(());
1252        }
1253
1254        let to_remove = databases
1255            .iter()
1256            .map(|id| (DatabaseKey { id: *id }, None))
1257            .collect();
1258        let mut prev = self.databases.set_many(to_remove, self.op_id)?;
1259        prev.retain(|_k, val| val.is_none());
1260
1261        if !prev.is_empty() {
1262            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1263            return Err(SqlCatalogError::UnknownDatabase(err).into());
1264        }
1265
1266        Ok(())
1267    }
1268
1269    /// Removes the schema identified by `database_id` and `schema_id` from the transaction.
1270    ///
1271    /// Returns an error if `(database_id, schema_id)` is not found.
1272    ///
1273    /// Runtime is linear with respect to the total number of schemas in the catalog.
1274    /// DO NOT call this function in a loop, use [`Self::remove_schemas`] instead.
1275    pub fn remove_schema(
1276        &mut self,
1277        database_id: &Option<DatabaseId>,
1278        schema_id: &SchemaId,
1279    ) -> Result<(), CatalogError> {
1280        let prev = self
1281            .schemas
1282            .set(SchemaKey { id: *schema_id }, None, self.op_id)?;
1283        if prev.is_some() {
1284            Ok(())
1285        } else {
1286            let database_name = match database_id {
1287                Some(id) => format!("{id}."),
1288                None => "".to_string(),
1289            };
1290            Err(SqlCatalogError::UnknownSchema(format!("{}.{}", database_name, schema_id)).into())
1291        }
1292    }
1293
1294    /// Removes all schemas in `schemas` from the transaction.
1295    ///
1296    /// Returns an error if any id in `schemas` is not found.
1297    ///
1298    /// NOTE: On error, there still may be some schemas removed from the transaction. It
1299    /// is up to the caller to either abort the transaction or commit.
1300    pub fn remove_schemas(
1301        &mut self,
1302        schemas: &BTreeMap<SchemaId, ResolvedDatabaseSpecifier>,
1303    ) -> Result<(), CatalogError> {
1304        if schemas.is_empty() {
1305            return Ok(());
1306        }
1307
1308        let to_remove = schemas
1309            .iter()
1310            .map(|(schema_id, _)| (SchemaKey { id: *schema_id }, None))
1311            .collect();
1312        let mut prev = self.schemas.set_many(to_remove, self.op_id)?;
1313        prev.retain(|_k, v| v.is_none());
1314
1315        if !prev.is_empty() {
1316            let err = prev
1317                .keys()
1318                .map(|k| {
1319                    let db_spec = schemas.get(&k.id).expect("should_exist");
1320                    let db_name = match db_spec {
1321                        ResolvedDatabaseSpecifier::Id(id) => format!("{id}."),
1322                        ResolvedDatabaseSpecifier::Ambient => "".to_string(),
1323                    };
1324                    format!("{}.{}", db_name, k.id)
1325                })
1326                .join(", ");
1327
1328            return Err(SqlCatalogError::UnknownSchema(err).into());
1329        }
1330
1331        Ok(())
1332    }
1333
1334    pub fn remove_source_references(
1335        &mut self,
1336        source_id: CatalogItemId,
1337    ) -> Result<(), CatalogError> {
1338        let deleted = self
1339            .source_references
1340            .delete_by_key(SourceReferencesKey { source_id }, self.op_id)
1341            .is_some();
1342        if deleted {
1343            Ok(())
1344        } else {
1345            Err(SqlCatalogError::UnknownItem(source_id.to_string()).into())
1346        }
1347    }
1348
1349    /// Removes all user roles in `roles` from the transaction.
1350    ///
1351    /// Returns an error if any id in `roles` is not found.
1352    ///
1353    /// NOTE: On error, there still may be some roles removed from the transaction. It
1354    /// is up to the caller to either abort the transaction or commit.
1355    pub fn remove_user_roles(&mut self, roles: &BTreeSet<RoleId>) -> Result<(), CatalogError> {
1356        assert!(
1357            roles.iter().all(|id| id.is_user()),
1358            "cannot delete non-user roles"
1359        );
1360        self.remove_roles(roles)
1361    }
1362
1363    /// Removes all roles in `roles` from the transaction.
1364    ///
1365    /// Returns an error if any id in `roles` is not found.
1366    ///
1367    /// NOTE: On error, there still may be some roles removed from the transaction. It
1368    /// is up to the caller to either abort the transaction or commit.
1369    pub fn remove_roles(&mut self, roles: &BTreeSet<RoleId>) -> Result<(), CatalogError> {
1370        if roles.is_empty() {
1371            return Ok(());
1372        }
1373
1374        let to_remove_keys = roles
1375            .iter()
1376            .map(|role_id| RoleKey { id: *role_id })
1377            .collect::<Vec<_>>();
1378
1379        let to_remove_roles = to_remove_keys
1380            .iter()
1381            .map(|role_key| (role_key.clone(), None))
1382            .collect();
1383
1384        let mut prev = self.roles.set_many(to_remove_roles, self.op_id)?;
1385
1386        let to_remove_role_auth = to_remove_keys
1387            .iter()
1388            .map(|role_key| {
1389                (
1390                    RoleAuthKey {
1391                        role_id: role_key.id,
1392                    },
1393                    None,
1394                )
1395            })
1396            .collect();
1397
1398        let mut role_auth_prev = self.role_auth.set_many(to_remove_role_auth, self.op_id)?;
1399
1400        prev.retain(|_k, v| v.is_none());
1401        if !prev.is_empty() {
1402            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1403            return Err(SqlCatalogError::UnknownRole(err).into());
1404        }
1405
1406        role_auth_prev.retain(|_k, v| v.is_none());
1407        // The reason we don't to the same check as above is that the role auth table
1408        // is not required to have all roles in the role table.
1409
1410        Ok(())
1411    }
1412
1413    /// Removes all cluster in `clusters` from the transaction.
1414    ///
1415    /// Returns an error if any id in `clusters` is not found.
1416    ///
1417    /// NOTE: On error, there still may be some clusters removed from the transaction. It is up to
1418    /// the caller to either abort the transaction or commit.
1419    pub fn remove_clusters(&mut self, clusters: &BTreeSet<ClusterId>) -> Result<(), CatalogError> {
1420        if clusters.is_empty() {
1421            return Ok(());
1422        }
1423
1424        let to_remove = clusters
1425            .iter()
1426            .map(|cluster_id| (ClusterKey { id: *cluster_id }, None))
1427            .collect();
1428        let mut prev = self.clusters.set_many(to_remove, self.op_id)?;
1429
1430        prev.retain(|_k, v| v.is_none());
1431        if !prev.is_empty() {
1432            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1433            return Err(SqlCatalogError::UnknownCluster(err).into());
1434        }
1435
1436        // Cascade delete introspection sources and cluster replicas.
1437        //
1438        // TODO(benesch): this doesn't seem right. Cascade deletions should
1439        // be entirely the domain of the higher catalog layer, not the
1440        // storage layer.
1441        self.cluster_replicas
1442            .delete(|_k, v| clusters.contains(&v.cluster_id), self.op_id);
1443        self.introspection_sources
1444            .delete(|k, _v| clusters.contains(&k.cluster_id), self.op_id);
1445
1446        Ok(())
1447    }
1448
1449    /// Removes the cluster replica `id` from the transaction.
1450    ///
1451    /// Returns an error if `id` is not found.
1452    ///
1453    /// Runtime is linear with respect to the total number of cluster replicas in the catalog.
1454    /// DO NOT call this function in a loop, use [`Self::remove_cluster_replicas`] instead.
1455    pub fn remove_cluster_replica(&mut self, id: ReplicaId) -> Result<(), CatalogError> {
1456        let deleted = self
1457            .cluster_replicas
1458            .delete_by_key(ClusterReplicaKey { id }, self.op_id)
1459            .is_some();
1460        if deleted {
1461            Ok(())
1462        } else {
1463            Err(SqlCatalogError::UnknownClusterReplica(id.to_string()).into())
1464        }
1465    }
1466
1467    /// Removes all cluster replicas in `replicas` from the transaction.
1468    ///
1469    /// Returns an error if any id in `replicas` is not found.
1470    ///
1471    /// NOTE: On error, there still may be some cluster replicas removed from the transaction. It
1472    /// is up to the caller to either abort the transaction or commit.
1473    pub fn remove_cluster_replicas(
1474        &mut self,
1475        replicas: &BTreeSet<ReplicaId>,
1476    ) -> Result<(), CatalogError> {
1477        if replicas.is_empty() {
1478            return Ok(());
1479        }
1480
1481        let to_remove = replicas
1482            .iter()
1483            .map(|replica_id| (ClusterReplicaKey { id: *replica_id }, None))
1484            .collect();
1485        let mut prev = self.cluster_replicas.set_many(to_remove, self.op_id)?;
1486
1487        prev.retain(|_k, v| v.is_none());
1488        if !prev.is_empty() {
1489            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1490            return Err(SqlCatalogError::UnknownClusterReplica(err).into());
1491        }
1492
1493        Ok(())
1494    }
1495
1496    /// Removes item `id` from the transaction.
1497    ///
1498    /// Returns an error if `id` is not found.
1499    ///
1500    /// Runtime is linear with respect to the total number of items in the catalog.
1501    /// DO NOT call this function in a loop, use [`Self::remove_items`] instead.
1502    pub fn remove_item(&mut self, id: CatalogItemId) -> Result<(), CatalogError> {
1503        let prev = self.items.set(ItemKey { id }, None, self.op_id)?;
1504        if prev.is_some() {
1505            Ok(())
1506        } else {
1507            Err(SqlCatalogError::UnknownItem(id.to_string()).into())
1508        }
1509    }
1510
1511    /// Removes all items in `ids` from the transaction.
1512    ///
1513    /// Returns an error if any id in `ids` is not found.
1514    ///
1515    /// NOTE: On error, there still may be some items removed from the transaction. It is
1516    /// up to the caller to either abort the transaction or commit.
1517    pub fn remove_items(&mut self, ids: &BTreeSet<CatalogItemId>) -> Result<(), CatalogError> {
1518        if ids.is_empty() {
1519            return Ok(());
1520        }
1521
1522        let ks: Vec<_> = ids.clone().into_iter().map(|id| ItemKey { id }).collect();
1523        let n = self.items.delete_by_keys(ks, self.op_id).len();
1524        if n == ids.len() {
1525            Ok(())
1526        } else {
1527            let item_ids = self.items.items().keys().map(|k| k.id).collect();
1528            let mut unknown = ids.difference(&item_ids);
1529            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1530        }
1531    }
1532
1533    /// Removes all system object mappings in `descriptions` from the transaction.
1534    ///
1535    /// Returns an error if any description in `descriptions` is not found.
1536    ///
1537    /// NOTE: On error, there still may be some items removed from the transaction. It is
1538    /// up to the caller to either abort the transaction or commit.
1539    pub fn remove_system_object_mappings(
1540        &mut self,
1541        descriptions: BTreeSet<SystemObjectDescription>,
1542    ) -> Result<(), CatalogError> {
1543        if descriptions.is_empty() {
1544            return Ok(());
1545        }
1546
1547        let ks: Vec<_> = descriptions
1548            .clone()
1549            .into_iter()
1550            .map(|desc| GidMappingKey {
1551                schema_name: desc.schema_name,
1552                object_type: desc.object_type,
1553                object_name: desc.object_name,
1554            })
1555            .collect();
1556        let n = self.system_gid_mapping.delete_by_keys(ks, self.op_id).len();
1557
1558        if n == descriptions.len() {
1559            Ok(())
1560        } else {
1561            let item_descriptions = self
1562                .system_gid_mapping
1563                .items()
1564                .keys()
1565                .map(|k| SystemObjectDescription {
1566                    schema_name: k.schema_name.clone(),
1567                    object_type: k.object_type.clone(),
1568                    object_name: k.object_name.clone(),
1569                })
1570                .collect();
1571            let mut unknown = descriptions.difference(&item_descriptions).map(|desc| {
1572                format!(
1573                    "{} {}.{}",
1574                    desc.object_type, desc.schema_name, desc.object_name
1575                )
1576            });
1577            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1578        }
1579    }
1580
1581    /// Removes all introspection source indexes in `indexes` from the transaction.
1582    ///
1583    /// Returns an error if any index in `indexes` is not found.
1584    ///
1585    /// NOTE: On error, there still may be some indexes removed from the transaction. It is
1586    /// up to the caller to either abort the transaction or commit.
1587    pub fn remove_introspection_source_indexes(
1588        &mut self,
1589        introspection_source_indexes: BTreeSet<(ClusterId, String)>,
1590    ) -> Result<(), CatalogError> {
1591        if introspection_source_indexes.is_empty() {
1592            return Ok(());
1593        }
1594
1595        let ks: Vec<_> = introspection_source_indexes
1596            .clone()
1597            .into_iter()
1598            .map(|(cluster_id, name)| ClusterIntrospectionSourceIndexKey { cluster_id, name })
1599            .collect();
1600        let n = self
1601            .introspection_sources
1602            .delete_by_keys(ks, self.op_id)
1603            .len();
1604        if n == introspection_source_indexes.len() {
1605            Ok(())
1606        } else {
1607            let txn_indexes = self
1608                .introspection_sources
1609                .items()
1610                .keys()
1611                .map(|k| (k.cluster_id, k.name.clone()))
1612                .collect();
1613            let mut unknown = introspection_source_indexes
1614                .difference(&txn_indexes)
1615                .map(|(cluster_id, name)| format!("{cluster_id} {name}"));
1616            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1617        }
1618    }
1619
1620    /// Updates item `id` in the transaction to `item_name` and `item`.
1621    ///
1622    /// Returns an error if `id` is not found.
1623    ///
1624    /// Runtime is linear with respect to the total number of items in the catalog.
1625    /// DO NOT call this function in a loop, use [`Self::update_items`] instead.
1626    pub fn update_item(&mut self, id: CatalogItemId, item: Item) -> Result<(), CatalogError> {
1627        let updated =
1628            self.items
1629                .update_by_key(ItemKey { id }, item.into_key_value().1, self.op_id)?;
1630        if updated {
1631            Ok(())
1632        } else {
1633            Err(SqlCatalogError::UnknownItem(id.to_string()).into())
1634        }
1635    }
1636
1637    /// Updates all items with ids matching the keys of `items` in the transaction, to the
1638    /// corresponding value in `items`.
1639    ///
1640    /// Returns an error if any id in `items` is not found.
1641    ///
1642    /// NOTE: On error, there still may be some items updated in the transaction. It is
1643    /// up to the caller to either abort the transaction or commit.
1644    pub fn update_items(
1645        &mut self,
1646        items: BTreeMap<CatalogItemId, Item>,
1647    ) -> Result<(), CatalogError> {
1648        if items.is_empty() {
1649            return Ok(());
1650        }
1651
1652        let update_ids: BTreeSet<_> = items.keys().cloned().collect();
1653        let kvs: Vec<_> = items
1654            .clone()
1655            .into_iter()
1656            .map(|(id, item)| (ItemKey { id }, item.into_key_value().1))
1657            .collect();
1658        let n = self.items.update_by_keys(kvs, self.op_id)?;
1659        let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize");
1660        if n == update_ids.len() {
1661            Ok(())
1662        } else {
1663            let item_ids: BTreeSet<_> = self.items.items().keys().map(|k| k.id).collect();
1664            let mut unknown = update_ids.difference(&item_ids);
1665            Err(SqlCatalogError::UnknownItem(unknown.join(", ")).into())
1666        }
1667    }
1668
1669    /// Updates role `id` in the transaction to `role`.
1670    ///
1671    /// Returns an error if `id` is not found.
1672    ///
1673    /// Runtime is linear with respect to the total number of items in the catalog.
1674    /// DO NOT call this function in a loop, implement and use some `Self::update_roles` instead.
1675    /// You should model it after [`Self::update_items`].
1676    pub fn update_role(
1677        &mut self,
1678        id: RoleId,
1679        role: Role,
1680        password: PasswordAction,
1681    ) -> Result<(), CatalogError> {
1682        let key = RoleKey { id };
1683        if self.roles.get(&key).is_some() {
1684            let auth_key = RoleAuthKey { role_id: id };
1685
1686            match password {
1687                PasswordAction::Set(new_password) => {
1688                    let hash = mz_auth::hash::scram256_hash(
1689                        &new_password.password,
1690                        &new_password.scram_iterations,
1691                    )
1692                    .expect("password hash should be valid");
1693                    let value = RoleAuthValue {
1694                        password_hash: Some(hash),
1695                        updated_at: SYSTEM_TIME(),
1696                    };
1697
1698                    if self.role_auth.get(&auth_key).is_some() {
1699                        self.role_auth
1700                            .update_by_key(auth_key.clone(), value, self.op_id)?;
1701                    } else {
1702                        self.role_auth.insert(auth_key.clone(), value, self.op_id)?;
1703                    }
1704                }
1705                PasswordAction::Clear => {
1706                    let value = RoleAuthValue {
1707                        password_hash: None,
1708                        updated_at: SYSTEM_TIME(),
1709                    };
1710                    if self.role_auth.get(&auth_key).is_some() {
1711                        self.role_auth
1712                            .update_by_key(auth_key.clone(), value, self.op_id)?;
1713                    }
1714                }
1715                PasswordAction::NoChange => {}
1716            }
1717
1718            self.roles
1719                .update_by_key(key, role.into_key_value().1, self.op_id)?;
1720
1721            Ok(())
1722        } else {
1723            Err(SqlCatalogError::UnknownRole(id.to_string()).into())
1724        }
1725    }
1726
1727    /// Updates all [`Role`]s with ids matching the keys of `roles` in the transaction, to the
1728    /// corresponding value in `roles`.
1729    ///
1730    /// This function does *not* write role_authentication information to the catalog.
1731    /// It is purely for updating the role itself.
1732    ///
1733    /// Returns an error if any id in `roles` is not found.
1734    ///
1735    /// NOTE: On error, there still may be some roles updated in the transaction. It is
1736    /// up to the caller to either abort the transaction or commit.
1737    pub fn update_roles_without_auth(
1738        &mut self,
1739        roles: BTreeMap<RoleId, Role>,
1740    ) -> Result<(), CatalogError> {
1741        if roles.is_empty() {
1742            return Ok(());
1743        }
1744
1745        let update_role_ids: BTreeSet<_> = roles.keys().cloned().collect();
1746        let kvs: Vec<_> = roles
1747            .into_iter()
1748            .map(|(id, role)| (RoleKey { id }, role.into_key_value().1))
1749            .collect();
1750        let n = self.roles.update_by_keys(kvs, self.op_id)?;
1751        let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize");
1752
1753        if n == update_role_ids.len() {
1754            Ok(())
1755        } else {
1756            let role_ids: BTreeSet<_> = self.roles.items().keys().map(|k| k.id).collect();
1757            let mut unknown = update_role_ids.difference(&role_ids);
1758            Err(SqlCatalogError::UnknownRole(unknown.join(", ")).into())
1759        }
1760    }
1761
1762    /// Updates persisted mapping from system objects to global IDs and fingerprints. Each element
1763    /// of `mappings` should be (old-global-id, new-system-object-mapping).
1764    ///
1765    /// Panics if provided id is not a system id.
1766    pub fn update_system_object_mappings(
1767        &mut self,
1768        mappings: BTreeMap<CatalogItemId, SystemObjectMapping>,
1769    ) -> Result<(), CatalogError> {
1770        if mappings.is_empty() {
1771            return Ok(());
1772        }
1773
1774        let n = self.system_gid_mapping.update(
1775            |_k, v| {
1776                if let Some(mapping) = mappings.get(&CatalogItemId::from(v.catalog_id)) {
1777                    let (_, new_value) = mapping.clone().into_key_value();
1778                    Some(new_value)
1779                } else {
1780                    None
1781                }
1782            },
1783            self.op_id,
1784        )?;
1785
1786        if usize::try_from(n.into_inner()).expect("update diff should fit into usize")
1787            != mappings.len()
1788        {
1789            let id_str = mappings.keys().map(|id| id.to_string()).join(",");
1790            return Err(SqlCatalogError::FailedBuiltinSchemaMigration(id_str).into());
1791        }
1792
1793        Ok(())
1794    }
1795
1796    /// Updates cluster `id` in the transaction to `cluster`.
1797    ///
1798    /// Returns an error if `id` is not found.
1799    ///
1800    /// Runtime is linear with respect to the total number of clusters in the catalog.
1801    /// DO NOT call this function in a loop.
1802    pub fn update_cluster(&mut self, id: ClusterId, cluster: Cluster) -> Result<(), CatalogError> {
1803        let updated = self.clusters.update_by_key(
1804            ClusterKey { id },
1805            cluster.into_key_value().1,
1806            self.op_id,
1807        )?;
1808        if updated {
1809            Ok(())
1810        } else {
1811            Err(SqlCatalogError::UnknownCluster(id.to_string()).into())
1812        }
1813    }
1814
1815    /// Updates cluster replica `replica_id` in the transaction to `replica`.
1816    ///
1817    /// Returns an error if `replica_id` is not found.
1818    ///
1819    /// Runtime is linear with respect to the total number of cluster replicas in the catalog.
1820    /// DO NOT call this function in a loop.
1821    pub fn update_cluster_replica(
1822        &mut self,
1823        replica_id: ReplicaId,
1824        replica: ClusterReplica,
1825    ) -> Result<(), CatalogError> {
1826        let updated = self.cluster_replicas.update_by_key(
1827            ClusterReplicaKey { id: replica_id },
1828            replica.into_key_value().1,
1829            self.op_id,
1830        )?;
1831        if updated {
1832            Ok(())
1833        } else {
1834            Err(SqlCatalogError::UnknownClusterReplica(replica_id.to_string()).into())
1835        }
1836    }
1837
1838    /// Updates database `id` in the transaction to `database`.
1839    ///
1840    /// Returns an error if `id` is not found.
1841    ///
1842    /// Runtime is linear with respect to the total number of databases in the catalog.
1843    /// DO NOT call this function in a loop.
1844    pub fn update_database(
1845        &mut self,
1846        id: DatabaseId,
1847        database: Database,
1848    ) -> Result<(), CatalogError> {
1849        let updated = self.databases.update_by_key(
1850            DatabaseKey { id },
1851            database.into_key_value().1,
1852            self.op_id,
1853        )?;
1854        if updated {
1855            Ok(())
1856        } else {
1857            Err(SqlCatalogError::UnknownDatabase(id.to_string()).into())
1858        }
1859    }
1860
1861    /// Updates schema `schema_id` in the transaction to `schema`.
1862    ///
1863    /// Returns an error if `schema_id` is not found.
1864    ///
1865    /// Runtime is linear with respect to the total number of schemas in the catalog.
1866    /// DO NOT call this function in a loop.
1867    pub fn update_schema(
1868        &mut self,
1869        schema_id: SchemaId,
1870        schema: Schema,
1871    ) -> Result<(), CatalogError> {
1872        let updated = self.schemas.update_by_key(
1873            SchemaKey { id: schema_id },
1874            schema.into_key_value().1,
1875            self.op_id,
1876        )?;
1877        if updated {
1878            Ok(())
1879        } else {
1880            Err(SqlCatalogError::UnknownSchema(schema_id.to_string()).into())
1881        }
1882    }
1883
1884    /// Updates `network_policy_id` in the transaction to `network policy`.
1885    ///
1886    /// Returns an error if `id` is not found.
1887    ///
1888    /// Runtime is linear with respect to the total number of databases in the catalog.
1889    /// DO NOT call this function in a loop.
1890    pub fn update_network_policy(
1891        &mut self,
1892        id: NetworkPolicyId,
1893        network_policy: NetworkPolicy,
1894    ) -> Result<(), CatalogError> {
1895        let updated = self.network_policies.update_by_key(
1896            NetworkPolicyKey { id },
1897            network_policy.into_key_value().1,
1898            self.op_id,
1899        )?;
1900        if updated {
1901            Ok(())
1902        } else {
1903            Err(SqlCatalogError::UnknownNetworkPolicy(id.to_string()).into())
1904        }
1905    }
1906    /// Removes all network policies in `network policies` from the transaction.
1907    ///
1908    /// Returns an error if any id in `network policy` is not found.
1909    ///
1910    /// NOTE: On error, there still may be some roles removed from the transaction. It
1911    /// is up to the caller to either abort the transaction or commit.
1912    pub fn remove_network_policies(
1913        &mut self,
1914        network_policies: &BTreeSet<NetworkPolicyId>,
1915    ) -> Result<(), CatalogError> {
1916        if network_policies.is_empty() {
1917            return Ok(());
1918        }
1919
1920        let to_remove = network_policies
1921            .iter()
1922            .map(|policy_id| (NetworkPolicyKey { id: *policy_id }, None))
1923            .collect();
1924        let mut prev = self.network_policies.set_many(to_remove, self.op_id)?;
1925        assert!(
1926            prev.iter().all(|(k, _)| k.id.is_user()),
1927            "cannot delete non-user network policy"
1928        );
1929
1930        prev.retain(|_k, v| v.is_none());
1931        if !prev.is_empty() {
1932            let err = prev.keys().map(|k| k.id.to_string()).join(", ");
1933            return Err(SqlCatalogError::UnknownNetworkPolicy(err).into());
1934        }
1935
1936        Ok(())
1937    }
1938    /// Set persisted default privilege.
1939    ///
1940    /// DO NOT call this function in a loop, use [`Self::set_default_privileges`] instead.
1941    pub fn set_default_privilege(
1942        &mut self,
1943        role_id: RoleId,
1944        database_id: Option<DatabaseId>,
1945        schema_id: Option<SchemaId>,
1946        object_type: ObjectType,
1947        grantee: RoleId,
1948        privileges: Option<AclMode>,
1949    ) -> Result<(), CatalogError> {
1950        self.default_privileges.set(
1951            DefaultPrivilegesKey {
1952                role_id,
1953                database_id,
1954                schema_id,
1955                object_type,
1956                grantee,
1957            },
1958            privileges.map(|privileges| DefaultPrivilegesValue { privileges }),
1959            self.op_id,
1960        )?;
1961        Ok(())
1962    }
1963
1964    /// Set persisted default privileges.
1965    pub fn set_default_privileges(
1966        &mut self,
1967        default_privileges: Vec<DefaultPrivilege>,
1968    ) -> Result<(), CatalogError> {
1969        if default_privileges.is_empty() {
1970            return Ok(());
1971        }
1972
1973        let default_privileges = default_privileges
1974            .into_iter()
1975            .map(DurableType::into_key_value)
1976            .map(|(k, v)| (k, Some(v)))
1977            .collect();
1978        self.default_privileges
1979            .set_many(default_privileges, self.op_id)?;
1980        Ok(())
1981    }
1982
1983    /// Set persisted system privilege.
1984    ///
1985    /// DO NOT call this function in a loop, use [`Self::set_system_privileges`] instead.
1986    pub fn set_system_privilege(
1987        &mut self,
1988        grantee: RoleId,
1989        grantor: RoleId,
1990        acl_mode: Option<AclMode>,
1991    ) -> Result<(), CatalogError> {
1992        self.system_privileges.set(
1993            SystemPrivilegesKey { grantee, grantor },
1994            acl_mode.map(|acl_mode| SystemPrivilegesValue { acl_mode }),
1995            self.op_id,
1996        )?;
1997        Ok(())
1998    }
1999
2000    /// Set persisted system privileges.
2001    pub fn set_system_privileges(
2002        &mut self,
2003        system_privileges: Vec<MzAclItem>,
2004    ) -> Result<(), CatalogError> {
2005        if system_privileges.is_empty() {
2006            return Ok(());
2007        }
2008
2009        let system_privileges = system_privileges
2010            .into_iter()
2011            .map(DurableType::into_key_value)
2012            .map(|(k, v)| (k, Some(v)))
2013            .collect();
2014        self.system_privileges
2015            .set_many(system_privileges, self.op_id)?;
2016        Ok(())
2017    }
2018
2019    /// Set persisted setting.
2020    pub fn set_setting(&mut self, name: String, value: Option<String>) -> Result<(), CatalogError> {
2021        self.settings.set(
2022            SettingKey { name },
2023            value.map(|value| SettingValue { value }),
2024            self.op_id,
2025        )?;
2026        Ok(())
2027    }
2028
2029    pub fn set_catalog_content_version(&mut self, version: String) -> Result<(), CatalogError> {
2030        self.set_setting(CATALOG_CONTENT_VERSION_KEY.to_string(), Some(version))
2031    }
2032
2033    /// Insert persisted introspection source index.
2034    pub fn insert_introspection_source_indexes(
2035        &mut self,
2036        introspection_source_indexes: Vec<(ClusterId, String, CatalogItemId, GlobalId)>,
2037        temporary_oids: &HashSet<u32>,
2038    ) -> Result<(), CatalogError> {
2039        if introspection_source_indexes.is_empty() {
2040            return Ok(());
2041        }
2042
2043        let amount = usize_to_u64(introspection_source_indexes.len());
2044        let oids = self.allocate_oids(amount, temporary_oids)?;
2045        let introspection_source_indexes: Vec<_> = introspection_source_indexes
2046            .into_iter()
2047            .zip_eq(oids)
2048            .map(
2049                |((cluster_id, name, item_id, index_id), oid)| IntrospectionSourceIndex {
2050                    cluster_id,
2051                    name,
2052                    item_id,
2053                    index_id,
2054                    oid,
2055                },
2056            )
2057            .collect();
2058
2059        for introspection_source_index in introspection_source_indexes {
2060            let (key, value) = introspection_source_index.into_key_value();
2061            self.introspection_sources.insert(key, value, self.op_id)?;
2062        }
2063
2064        Ok(())
2065    }
2066
2067    /// Set persisted system object mappings.
2068    pub fn set_system_object_mappings(
2069        &mut self,
2070        mappings: Vec<SystemObjectMapping>,
2071    ) -> Result<(), CatalogError> {
2072        if mappings.is_empty() {
2073            return Ok(());
2074        }
2075
2076        let mappings = mappings
2077            .into_iter()
2078            .map(DurableType::into_key_value)
2079            .map(|(k, v)| (k, Some(v)))
2080            .collect();
2081        self.system_gid_mapping.set_many(mappings, self.op_id)?;
2082        Ok(())
2083    }
2084
2085    /// Set persisted replica.
2086    pub fn set_replicas(&mut self, replicas: Vec<ClusterReplica>) -> Result<(), CatalogError> {
2087        if replicas.is_empty() {
2088            return Ok(());
2089        }
2090
2091        let replicas = replicas
2092            .into_iter()
2093            .map(DurableType::into_key_value)
2094            .map(|(k, v)| (k, Some(v)))
2095            .collect();
2096        self.cluster_replicas.set_many(replicas, self.op_id)?;
2097        Ok(())
2098    }
2099
2100    /// Set persisted configuration.
2101    pub fn set_config(&mut self, key: String, value: Option<u64>) -> Result<(), CatalogError> {
2102        match value {
2103            Some(value) => {
2104                let config = Config { key, value };
2105                let (key, value) = config.into_key_value();
2106                self.configs.set(key, Some(value), self.op_id)?;
2107            }
2108            None => {
2109                self.configs.set(ConfigKey { key }, None, self.op_id)?;
2110            }
2111        }
2112        Ok(())
2113    }
2114
2115    /// Get the value of a persisted config.
2116    pub fn get_config(&self, key: String) -> Option<u64> {
2117        self.configs
2118            .get(&ConfigKey { key })
2119            .map(|entry| entry.value)
2120    }
2121
2122    /// Get the value of a persisted setting.
2123    pub fn get_setting(&self, name: String) -> Option<&str> {
2124        self.settings
2125            .get(&SettingKey { name })
2126            .map(|entry| &*entry.value)
2127    }
2128
2129    pub fn get_builtin_migration_shard(&self) -> Option<ShardId> {
2130        self.get_setting(BUILTIN_MIGRATION_SHARD_KEY.to_string())
2131            .map(|shard_id| shard_id.parse().expect("valid ShardId"))
2132    }
2133
2134    pub fn set_builtin_migration_shard(&mut self, shard_id: ShardId) -> Result<(), CatalogError> {
2135        self.set_setting(
2136            BUILTIN_MIGRATION_SHARD_KEY.to_string(),
2137            Some(shard_id.to_string()),
2138        )
2139    }
2140
2141    pub fn get_expression_cache_shard(&self) -> Option<ShardId> {
2142        self.get_setting(EXPRESSION_CACHE_SHARD_KEY.to_string())
2143            .map(|shard_id| shard_id.parse().expect("valid ShardId"))
2144    }
2145
2146    pub fn set_expression_cache_shard(&mut self, shard_id: ShardId) -> Result<(), CatalogError> {
2147        self.set_setting(
2148            EXPRESSION_CACHE_SHARD_KEY.to_string(),
2149            Some(shard_id.to_string()),
2150        )
2151    }
2152
2153    /// Updates the catalog `with_0dt_deployment_max_wait` "config" value to
2154    /// match the `with_0dt_deployment_max_wait` "system var" value.
2155    ///
2156    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2157    /// but use it in boot before Launch Darkly is available.
2158    pub fn set_0dt_deployment_max_wait(&mut self, value: Duration) -> Result<(), CatalogError> {
2159        self.set_config(
2160            WITH_0DT_DEPLOYMENT_MAX_WAIT.into(),
2161            Some(
2162                value
2163                    .as_millis()
2164                    .try_into()
2165                    .expect("max wait fits into u64"),
2166            ),
2167        )
2168    }
2169
2170    /// Updates the catalog `with_0dt_deployment_ddl_check_interval` "config"
2171    /// value to match the `with_0dt_deployment_ddl_check_interval` "system var"
2172    /// value.
2173    ///
2174    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2175    /// but use it in boot before Launch Darkly is available.
2176    pub fn set_0dt_deployment_ddl_check_interval(
2177        &mut self,
2178        value: Duration,
2179    ) -> Result<(), CatalogError> {
2180        self.set_config(
2181            WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.into(),
2182            Some(
2183                value
2184                    .as_millis()
2185                    .try_into()
2186                    .expect("ddl check interval fits into u64"),
2187            ),
2188        )
2189    }
2190
2191    /// Updates the catalog `0dt_deployment_panic_after_timeout` "config" value to
2192    /// match the `0dt_deployment_panic_after_timeout` "system var" value.
2193    ///
2194    /// These are mirrored so that we can toggle the flag with Launch Darkly,
2195    /// but use it in boot before Launch Darkly is available.
2196    pub fn set_enable_0dt_deployment_panic_after_timeout(
2197        &mut self,
2198        value: bool,
2199    ) -> Result<(), CatalogError> {
2200        self.set_config(
2201            ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.into(),
2202            Some(u64::from(value)),
2203        )
2204    }
2205
2206    /// Removes the catalog `with_0dt_deployment_max_wait` "config" value to
2207    /// match the `with_0dt_deployment_max_wait` "system var" value.
2208    ///
2209    /// These are mirrored so that we can toggle the flag with LaunchDarkly,
2210    /// but use it in boot before LaunchDarkly is available.
2211    pub fn reset_0dt_deployment_max_wait(&mut self) -> Result<(), CatalogError> {
2212        self.set_config(WITH_0DT_DEPLOYMENT_MAX_WAIT.into(), None)
2213    }
2214
2215    /// Removes the catalog `with_0dt_deployment_ddl_check_interval` "config"
2216    /// value to match the `with_0dt_deployment_ddl_check_interval` "system var"
2217    /// value.
2218    ///
2219    /// These are mirrored so that we can toggle the flag with LaunchDarkly, but
2220    /// use it in boot before LaunchDarkly is available.
2221    pub fn reset_0dt_deployment_ddl_check_interval(&mut self) -> Result<(), CatalogError> {
2222        self.set_config(WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL.into(), None)
2223    }
2224
2225    /// Removes the catalog `enable_0dt_deployment_panic_after_timeout` "config"
2226    /// value to match the `enable_0dt_deployment_panic_after_timeout` "system
2227    /// var" value.
2228    ///
2229    /// These are mirrored so that we can toggle the flag with LaunchDarkly, but
2230    /// use it in boot before LaunchDarkly is available.
2231    pub fn reset_enable_0dt_deployment_panic_after_timeout(&mut self) -> Result<(), CatalogError> {
2232        self.set_config(ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT.into(), None)
2233    }
2234
2235    /// Updates the catalog `system_config_synced` "config" value to true.
2236    pub fn set_system_config_synced_once(&mut self) -> Result<(), CatalogError> {
2237        self.set_config(SYSTEM_CONFIG_SYNCED_KEY.into(), Some(1))
2238    }
2239
2240    pub fn update_comment(
2241        &mut self,
2242        object_id: CommentObjectId,
2243        sub_component: Option<usize>,
2244        comment: Option<String>,
2245    ) -> Result<(), CatalogError> {
2246        let key = CommentKey {
2247            object_id,
2248            sub_component,
2249        };
2250        let value = comment.map(|c| CommentValue { comment: c });
2251        self.comments.set(key, value, self.op_id)?;
2252
2253        Ok(())
2254    }
2255
2256    pub fn drop_comments(
2257        &mut self,
2258        object_ids: &BTreeSet<CommentObjectId>,
2259    ) -> Result<(), CatalogError> {
2260        if object_ids.is_empty() {
2261            return Ok(());
2262        }
2263
2264        self.comments
2265            .delete(|k, _v| object_ids.contains(&k.object_id), self.op_id);
2266        Ok(())
2267    }
2268
2269    pub fn update_source_references(
2270        &mut self,
2271        source_id: CatalogItemId,
2272        references: Vec<SourceReference>,
2273        updated_at: u64,
2274    ) -> Result<(), CatalogError> {
2275        let key = SourceReferencesKey { source_id };
2276        let value = SourceReferencesValue {
2277            references,
2278            updated_at,
2279        };
2280        self.source_references.set(key, Some(value), self.op_id)?;
2281        Ok(())
2282    }
2283
2284    /// Upserts persisted system configuration `name` to `value`.
2285    pub fn upsert_system_config(&mut self, name: &str, value: String) -> Result<(), CatalogError> {
2286        let key = ServerConfigurationKey {
2287            name: name.to_string(),
2288        };
2289        let value = ServerConfigurationValue { value };
2290        self.system_configurations
2291            .set(key, Some(value), self.op_id)?;
2292        Ok(())
2293    }
2294
2295    /// Removes persisted system configuration `name`.
2296    pub fn remove_system_config(&mut self, name: &str) {
2297        let key = ServerConfigurationKey {
2298            name: name.to_string(),
2299        };
2300        self.system_configurations
2301            .set(key, None, self.op_id)
2302            .expect("cannot have uniqueness violation");
2303    }
2304
2305    /// Removes all persisted system configurations.
2306    pub fn clear_system_configs(&mut self) {
2307        self.system_configurations.delete(|_k, _v| true, self.op_id);
2308    }
2309
2310    /// Returns the persisted cluster-coherent scoped system configurations.
2311    pub fn get_cluster_system_configurations(
2312        &self,
2313    ) -> impl Iterator<Item = ClusterSystemConfiguration> + use<'_> {
2314        self.cluster_system_configurations
2315            .items()
2316            .into_iter()
2317            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2318    }
2319
2320    /// Upserts the persisted cluster-coherent scoped configuration `name` = `value`
2321    /// for `cluster_id`.
2322    pub fn upsert_cluster_system_config(
2323        &mut self,
2324        cluster_id: ClusterId,
2325        name: &str,
2326        value: String,
2327    ) -> Result<(), CatalogError> {
2328        let key = ClusterSystemConfigurationKey {
2329            cluster_id,
2330            name: name.to_string(),
2331        };
2332        let value = ClusterSystemConfigurationValue { value };
2333        self.cluster_system_configurations
2334            .set(key, Some(value), self.op_id)?;
2335        Ok(())
2336    }
2337
2338    /// Removes the persisted cluster-coherent scoped configuration `name` for
2339    /// `cluster_id`.
2340    pub fn remove_cluster_system_config(&mut self, cluster_id: ClusterId, name: &str) {
2341        let key = ClusterSystemConfigurationKey {
2342            cluster_id,
2343            name: name.to_string(),
2344        };
2345        self.cluster_system_configurations
2346            .set(key, None, self.op_id)
2347            .expect("cannot have uniqueness violation");
2348    }
2349
2350    /// Returns the persisted replica-local scoped system configurations.
2351    pub fn get_replica_system_configurations(
2352        &self,
2353    ) -> impl Iterator<Item = ReplicaSystemConfiguration> + use<'_> {
2354        self.replica_system_configurations
2355            .items()
2356            .into_iter()
2357            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2358    }
2359
2360    /// Upserts the persisted replica-local scoped configuration `name` = `value`
2361    /// for `replica_id`.
2362    pub fn upsert_replica_system_config(
2363        &mut self,
2364        replica_id: ReplicaId,
2365        name: &str,
2366        value: String,
2367    ) -> Result<(), CatalogError> {
2368        let key = ReplicaSystemConfigurationKey {
2369            replica_id,
2370            name: name.to_string(),
2371        };
2372        let value = ReplicaSystemConfigurationValue { value };
2373        self.replica_system_configurations
2374            .set(key, Some(value), self.op_id)?;
2375        Ok(())
2376    }
2377
2378    /// Removes the persisted replica-local scoped configuration `name` for
2379    /// `replica_id`.
2380    pub fn remove_replica_system_config(&mut self, replica_id: ReplicaId, name: &str) {
2381        let key = ReplicaSystemConfigurationKey {
2382            replica_id,
2383            name: name.to_string(),
2384        };
2385        self.replica_system_configurations
2386            .set(key, None, self.op_id)
2387            .expect("cannot have uniqueness violation");
2388    }
2389
2390    pub(crate) fn insert_config(&mut self, key: String, value: u64) -> Result<(), CatalogError> {
2391        match self.configs.insert(
2392            ConfigKey { key: key.clone() },
2393            ConfigValue { value },
2394            self.op_id,
2395        ) {
2396            Ok(_) => Ok(()),
2397            Err(_) => Err(SqlCatalogError::ConfigAlreadyExists(key).into()),
2398        }
2399    }
2400
2401    pub fn get_clusters(&self) -> impl Iterator<Item = Cluster> + use<'_> {
2402        self.clusters
2403            .items()
2404            .into_iter()
2405            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2406    }
2407
2408    pub fn get_cluster_replicas(&self) -> impl Iterator<Item = ClusterReplica> + use<'_> {
2409        self.cluster_replicas
2410            .items()
2411            .into_iter()
2412            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2413    }
2414
2415    pub fn get_databases(&self) -> impl Iterator<Item = Database> + use<'_> {
2416        self.databases
2417            .items()
2418            .into_iter()
2419            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2420    }
2421
2422    pub fn get_roles(&self) -> impl Iterator<Item = Role> + use<'_> {
2423        self.roles
2424            .items()
2425            .into_iter()
2426            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2427    }
2428
2429    pub fn get_network_policies(&self) -> impl Iterator<Item = NetworkPolicy> + use<'_> {
2430        self.network_policies
2431            .items()
2432            .into_iter()
2433            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2434    }
2435
2436    pub fn get_system_object_mappings(
2437        &self,
2438    ) -> impl Iterator<Item = SystemObjectMapping> + use<'_> {
2439        self.system_gid_mapping
2440            .items()
2441            .into_iter()
2442            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2443    }
2444
2445    pub fn get_schemas(&self) -> impl Iterator<Item = Schema> + use<'_> {
2446        self.schemas
2447            .items()
2448            .into_iter()
2449            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2450    }
2451
2452    pub fn get_system_configurations(&self) -> impl Iterator<Item = SystemConfiguration> + use<'_> {
2453        self.system_configurations
2454            .items()
2455            .into_iter()
2456            .map(|(k, v)| DurableType::from_key_value(k.clone(), v.clone()))
2457    }
2458
2459    pub fn get_schema(&self, id: &SchemaId) -> Option<Schema> {
2460        let key = SchemaKey { id: *id };
2461        self.schemas
2462            .get(&key)
2463            .map(|v| DurableType::from_key_value(key, v.clone()))
2464    }
2465
2466    pub fn get_introspection_source_indexes(
2467        &self,
2468        cluster_id: ClusterId,
2469    ) -> BTreeMap<&str, (GlobalId, u32)> {
2470        self.introspection_sources
2471            .items()
2472            .into_iter()
2473            .filter(|(k, _v)| k.cluster_id == cluster_id)
2474            .map(|(k, v)| (k.name.as_str(), (v.global_id.into(), v.oid)))
2475            .collect()
2476    }
2477
2478    pub fn get_catalog_content_version(&self) -> Option<&str> {
2479        self.settings
2480            .get(&SettingKey {
2481                name: CATALOG_CONTENT_VERSION_KEY.to_string(),
2482            })
2483            .map(|value| &*value.value)
2484    }
2485
2486    pub fn get_authentication_mock_nonce(&self) -> Option<String> {
2487        self.settings
2488            .get(&SettingKey {
2489                name: MOCK_AUTHENTICATION_NONCE_KEY.to_string(),
2490            })
2491            .map(|value| value.value.clone())
2492    }
2493
2494    /// Commit the current operation within the transaction. This does not cause anything to be
2495    /// written durably, but signals to the current transaction that we are moving on to the next
2496    /// operation.
2497    ///
2498    /// Returns the updates of the committed operation.
2499    #[must_use]
2500    pub fn get_and_commit_op_updates(&mut self) -> Vec<StateUpdate> {
2501        let updates = self.get_op_updates();
2502        self.commit_op();
2503        updates
2504    }
2505
2506    fn get_op_updates(&self) -> Vec<StateUpdate> {
2507        fn get_collection_op_updates<'a, T>(
2508            table_txn: &'a TableTransaction<T::Key, T::Value>,
2509            kind_fn: impl Fn(T) -> StateUpdateKind + 'a,
2510            op: Timestamp,
2511        ) -> impl Iterator<Item = (StateUpdateKind, StateDiff)> + 'a
2512        where
2513            T::Key: Ord + Eq + Clone + Debug,
2514            T::Value: Ord + Clone + Debug,
2515            T: DurableType,
2516        {
2517            table_txn
2518                .pending
2519                .iter()
2520                .flat_map(|(k, vs)| vs.into_iter().map(move |v| (k, v)))
2521                .filter_map(move |(k, v)| {
2522                    if v.ts == op {
2523                        let key = k.clone();
2524                        let value = v.value.clone();
2525                        let diff = v.diff.clone().try_into().expect("invalid diff");
2526                        let update = DurableType::from_key_value(key, value);
2527                        let kind = kind_fn(update);
2528                        Some((kind, diff))
2529                    } else {
2530                        None
2531                    }
2532                })
2533        }
2534
2535        fn get_large_collection_op_updates<'a, T>(
2536            collection: &'a Vec<(T::Key, Diff, Timestamp)>,
2537            kind_fn: impl Fn(T) -> StateUpdateKind + 'a,
2538            op: Timestamp,
2539        ) -> impl Iterator<Item = (StateUpdateKind, StateDiff)> + 'a
2540        where
2541            T::Key: Ord + Eq + Clone + Debug,
2542            T: DurableType<Value = ()>,
2543        {
2544            collection.iter().filter_map(move |(k, diff, ts)| {
2545                if *ts == op {
2546                    let key = k.clone();
2547                    let diff = diff.clone().try_into().expect("invalid diff");
2548                    let update = DurableType::from_key_value(key, ());
2549                    let kind = kind_fn(update);
2550                    Some((kind, diff))
2551                } else {
2552                    None
2553                }
2554            })
2555        }
2556
2557        let Transaction {
2558            durable_catalog: _,
2559            databases,
2560            schemas,
2561            items,
2562            comments,
2563            roles,
2564            role_auth,
2565            clusters,
2566            network_policies,
2567            cluster_replicas,
2568            introspection_sources,
2569            system_gid_mapping,
2570            system_configurations,
2571            cluster_system_configurations,
2572            replica_system_configurations,
2573            default_privileges,
2574            source_references,
2575            system_privileges,
2576            audit_log_updates,
2577            storage_collection_metadata,
2578            unfinalized_shards,
2579            // Not representable as a `StateUpdate`.
2580            id_allocator: _,
2581            configs: _,
2582            settings: _,
2583            txn_wal_shard: _,
2584            upper,
2585            op_id: _,
2586            commit_capability: _,
2587        } = &self;
2588
2589        let updates = std::iter::empty()
2590            .chain(get_collection_op_updates(
2591                roles,
2592                StateUpdateKind::Role,
2593                self.op_id,
2594            ))
2595            .chain(get_collection_op_updates(
2596                role_auth,
2597                StateUpdateKind::RoleAuth,
2598                self.op_id,
2599            ))
2600            .chain(get_collection_op_updates(
2601                databases,
2602                StateUpdateKind::Database,
2603                self.op_id,
2604            ))
2605            .chain(get_collection_op_updates(
2606                schemas,
2607                StateUpdateKind::Schema,
2608                self.op_id,
2609            ))
2610            .chain(get_collection_op_updates(
2611                default_privileges,
2612                StateUpdateKind::DefaultPrivilege,
2613                self.op_id,
2614            ))
2615            .chain(get_collection_op_updates(
2616                system_privileges,
2617                StateUpdateKind::SystemPrivilege,
2618                self.op_id,
2619            ))
2620            .chain(get_collection_op_updates(
2621                system_configurations,
2622                StateUpdateKind::SystemConfiguration,
2623                self.op_id,
2624            ))
2625            .chain(get_collection_op_updates(
2626                cluster_system_configurations,
2627                StateUpdateKind::ClusterSystemConfiguration,
2628                self.op_id,
2629            ))
2630            .chain(get_collection_op_updates(
2631                replica_system_configurations,
2632                StateUpdateKind::ReplicaSystemConfiguration,
2633                self.op_id,
2634            ))
2635            .chain(get_collection_op_updates(
2636                clusters,
2637                StateUpdateKind::Cluster,
2638                self.op_id,
2639            ))
2640            .chain(get_collection_op_updates(
2641                network_policies,
2642                StateUpdateKind::NetworkPolicy,
2643                self.op_id,
2644            ))
2645            .chain(get_collection_op_updates(
2646                introspection_sources,
2647                StateUpdateKind::IntrospectionSourceIndex,
2648                self.op_id,
2649            ))
2650            .chain(get_collection_op_updates(
2651                cluster_replicas,
2652                StateUpdateKind::ClusterReplica,
2653                self.op_id,
2654            ))
2655            .chain(get_collection_op_updates(
2656                system_gid_mapping,
2657                StateUpdateKind::SystemObjectMapping,
2658                self.op_id,
2659            ))
2660            .chain(get_collection_op_updates(
2661                items,
2662                StateUpdateKind::Item,
2663                self.op_id,
2664            ))
2665            .chain(get_collection_op_updates(
2666                comments,
2667                StateUpdateKind::Comment,
2668                self.op_id,
2669            ))
2670            .chain(get_collection_op_updates(
2671                source_references,
2672                StateUpdateKind::SourceReferences,
2673                self.op_id,
2674            ))
2675            .chain(get_collection_op_updates(
2676                storage_collection_metadata,
2677                StateUpdateKind::StorageCollectionMetadata,
2678                self.op_id,
2679            ))
2680            .chain(get_collection_op_updates(
2681                unfinalized_shards,
2682                StateUpdateKind::UnfinalizedShard,
2683                self.op_id,
2684            ))
2685            .chain(get_large_collection_op_updates(
2686                audit_log_updates,
2687                StateUpdateKind::AuditLog,
2688                self.op_id,
2689            ))
2690            .map(|(kind, diff)| StateUpdate {
2691                kind,
2692                ts: upper.clone(),
2693                diff,
2694            })
2695            .collect();
2696
2697        updates
2698    }
2699
2700    pub fn is_savepoint(&self) -> bool {
2701        self.durable_catalog.is_savepoint()
2702    }
2703
2704    fn commit_op(&mut self) {
2705        self.op_id += 1;
2706    }
2707
2708    pub fn op_id(&self) -> Timestamp {
2709        self.op_id
2710    }
2711
2712    pub fn upper(&self) -> mz_repr::Timestamp {
2713        self.upper
2714    }
2715
2716    fn ensure_committable(&self) -> Result<(), CatalogError> {
2717        match self.commit_capability {
2718            Some(_) => Ok(()),
2719            None => Err(DurableCatalogError::DryRunTransaction.into()),
2720        }
2721    }
2722
2723    /// Verifies that this process has not missed catalog content updates.
2724    pub(super) async fn ensure_not_out_of_sync(&mut self) -> Result<(), CatalogError> {
2725        self.durable_catalog
2726            .ensure_not_out_of_sync(self.upper)
2727            .await
2728    }
2729
2730    pub(crate) fn into_parts(
2731        self,
2732    ) -> Result<(TransactionBatch, &'a mut dyn DurableCatalogState), CatalogError> {
2733        let commit_capability = self
2734            .commit_capability
2735            .ok_or(DurableCatalogError::DryRunTransaction)?;
2736        let audit_log_updates = self
2737            .audit_log_updates
2738            .into_iter()
2739            .map(|(k, diff, _op)| (k.into_proto(), (), diff))
2740            .collect();
2741
2742        let txn_batch = TransactionBatch {
2743            databases: self.databases.pending(),
2744            schemas: self.schemas.pending(),
2745            items: self.items.pending(),
2746            comments: self.comments.pending(),
2747            roles: self.roles.pending(),
2748            role_auth: self.role_auth.pending(),
2749            clusters: self.clusters.pending(),
2750            cluster_replicas: self.cluster_replicas.pending(),
2751            network_policies: self.network_policies.pending(),
2752            introspection_sources: self.introspection_sources.pending(),
2753            id_allocator: self.id_allocator.pending(),
2754            configs: self.configs.pending(),
2755            source_references: self.source_references.pending(),
2756            settings: self.settings.pending(),
2757            system_gid_mapping: self.system_gid_mapping.pending(),
2758            system_configurations: self.system_configurations.pending(),
2759            cluster_system_configurations: self.cluster_system_configurations.pending(),
2760            replica_system_configurations: self.replica_system_configurations.pending(),
2761            default_privileges: self.default_privileges.pending(),
2762            system_privileges: self.system_privileges.pending(),
2763            storage_collection_metadata: self.storage_collection_metadata.pending(),
2764            unfinalized_shards: self.unfinalized_shards.pending(),
2765            txn_wal_shard: self.txn_wal_shard.pending(),
2766            audit_log_updates,
2767            upper: self.upper,
2768            _commit_capability: commit_capability,
2769        };
2770        Ok((txn_batch, self.durable_catalog))
2771    }
2772
2773    /// Commits the storage transaction to durable storage.
2774    ///
2775    /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect
2776    /// programming error that leaves durable state unchanged. Any other error
2777    /// outside read-only mode indicates the catalog may be in an indeterminate
2778    /// state and needs to be fully re-read before proceeding. In general, such
2779    /// errors must be fatal to the calling process. We do not panic/halt here so
2780    /// initialization can report them.
2781    ///
2782    /// The transaction is committed at `commit_ts`.
2783    ///
2784    /// Returns what the upper was directly after the transaction committed.
2785    ///
2786    /// In read-only mode, this will return an error for non-empty transactions indicating that the
2787    /// catalog is not writeable.
2788    #[mz_ore::instrument(level = "debug")]
2789    pub(crate) async fn commit_internal(
2790        self,
2791        commit_ts: mz_repr::Timestamp,
2792    ) -> Result<(&'a mut dyn DurableCatalogState, mz_repr::Timestamp), CatalogError> {
2793        self.ensure_committable()?;
2794        let (mut txn_batch, durable_catalog) = self.into_parts()?;
2795        let TransactionBatch {
2796            databases,
2797            schemas,
2798            items,
2799            comments,
2800            roles,
2801            role_auth,
2802            clusters,
2803            cluster_replicas,
2804            network_policies,
2805            introspection_sources,
2806            id_allocator,
2807            configs,
2808            source_references,
2809            settings,
2810            system_gid_mapping,
2811            system_configurations,
2812            cluster_system_configurations,
2813            replica_system_configurations,
2814            default_privileges,
2815            system_privileges,
2816            storage_collection_metadata,
2817            unfinalized_shards,
2818            txn_wal_shard,
2819            audit_log_updates,
2820            upper: _,
2821            _commit_capability: _,
2822        } = &mut txn_batch;
2823        // Consolidate in memory because it will likely be faster than consolidating after the
2824        // transaction has been made durable.
2825        differential_dataflow::consolidation::consolidate_updates(databases);
2826        differential_dataflow::consolidation::consolidate_updates(schemas);
2827        differential_dataflow::consolidation::consolidate_updates(items);
2828        differential_dataflow::consolidation::consolidate_updates(comments);
2829        differential_dataflow::consolidation::consolidate_updates(roles);
2830        differential_dataflow::consolidation::consolidate_updates(role_auth);
2831        differential_dataflow::consolidation::consolidate_updates(clusters);
2832        differential_dataflow::consolidation::consolidate_updates(cluster_replicas);
2833        differential_dataflow::consolidation::consolidate_updates(network_policies);
2834        differential_dataflow::consolidation::consolidate_updates(introspection_sources);
2835        differential_dataflow::consolidation::consolidate_updates(id_allocator);
2836        differential_dataflow::consolidation::consolidate_updates(configs);
2837        differential_dataflow::consolidation::consolidate_updates(settings);
2838        differential_dataflow::consolidation::consolidate_updates(source_references);
2839        differential_dataflow::consolidation::consolidate_updates(system_gid_mapping);
2840        differential_dataflow::consolidation::consolidate_updates(system_configurations);
2841        differential_dataflow::consolidation::consolidate_updates(cluster_system_configurations);
2842        differential_dataflow::consolidation::consolidate_updates(replica_system_configurations);
2843        differential_dataflow::consolidation::consolidate_updates(default_privileges);
2844        differential_dataflow::consolidation::consolidate_updates(system_privileges);
2845        differential_dataflow::consolidation::consolidate_updates(storage_collection_metadata);
2846        differential_dataflow::consolidation::consolidate_updates(unfinalized_shards);
2847        differential_dataflow::consolidation::consolidate_updates(txn_wal_shard);
2848        differential_dataflow::consolidation::consolidate_updates(audit_log_updates);
2849
2850        let upper = durable_catalog
2851            .commit_transaction(txn_batch, commit_ts)
2852            .await?;
2853        Ok((durable_catalog, upper))
2854    }
2855
2856    /// Commits the storage transaction to durable storage.
2857    ///
2858    /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect
2859    /// programming error that leaves durable state unchanged. Any other error
2860    /// outside read-only mode indicates the catalog may be in an indeterminate
2861    /// state and needs to be fully re-read before proceeding. In general, such
2862    /// errors must be fatal to the calling process. We do not panic/halt here so
2863    /// initialization can report them.
2864    ///
2865    /// In read-only mode, this will return an error for non-empty transactions indicating that the
2866    /// catalog is not writeable.
2867    ///
2868    /// IMPORTANT: It is assumed that the committer of this transaction has already applied all
2869    /// updates from this transaction. Therefore, updates from this transaction will not be returned
2870    /// when calling [`crate::durable::ReadOnlyDurableCatalogState::sync_to_current_updates`] or
2871    /// [`crate::durable::ReadOnlyDurableCatalogState::sync_updates`].
2872    ///
2873    /// An alternative implementation would be for the caller to explicitly consume their updates
2874    /// after committing and only then apply the updates in-memory. While this removes assumptions
2875    /// about the caller in this method, in practice it results in duplicate work on every commit.
2876    #[mz_ore::instrument(level = "debug")]
2877    pub async fn commit(self, commit_ts: mz_repr::Timestamp) -> Result<(), CatalogError> {
2878        self.ensure_committable()?;
2879        let op_updates = self.get_op_updates();
2880        assert!(
2881            op_updates.is_empty(),
2882            "unconsumed transaction updates: {op_updates:?}"
2883        );
2884
2885        let (durable_storage, upper) = self.commit_internal(commit_ts).await?;
2886        // Drain all the updates from the commit since it is assumed that they were already applied.
2887        let updates = durable_storage.sync_updates(upper).await?;
2888        // Writable and savepoint catalogs should have consumed all updates before committing a
2889        // transaction, otherwise the commit was performed with an out of date state.
2890        // Read-only catalogs can only commit empty transactions, so they don't need to consume all
2891        // updates before committing.
2892        //
2893        // The off-loop group committer can advance the catalog upper without content while this
2894        // transaction is open. The commit then rebases above `commit_ts`. Its returned `upper` is
2895        // the exclusive upper of the successful write.
2896        soft_assert_no_log!(
2897            durable_storage.is_read_only()
2898                || updates
2899                    .iter()
2900                    .all(|update| update.ts >= commit_ts && update.ts < upper),
2901            "unconsumed updates existed before transaction commit: commit_ts={commit_ts:?}, upper={upper:?}, updates:{updates:?}"
2902        );
2903        Ok(())
2904    }
2905}
2906
2907use crate::durable::async_trait;
2908
2909use super::objects::{RoleAuthKey, RoleAuthValue};
2910
2911#[async_trait]
2912impl StorageTxn for Transaction<'_> {
2913    fn get_collection_metadata(&self) -> BTreeMap<GlobalId, ShardId> {
2914        self.storage_collection_metadata
2915            .items()
2916            .into_iter()
2917            .map(
2918                |(
2919                    StorageCollectionMetadataKey { id },
2920                    StorageCollectionMetadataValue { shard },
2921                )| { (*id, shard.clone()) },
2922            )
2923            .collect()
2924    }
2925
2926    fn insert_collection_metadata(
2927        &mut self,
2928        metadata: BTreeMap<GlobalId, ShardId>,
2929    ) -> Result<(), StorageError> {
2930        for (id, shard) in metadata {
2931            self.storage_collection_metadata
2932                .insert(
2933                    StorageCollectionMetadataKey { id },
2934                    StorageCollectionMetadataValue {
2935                        shard: shard.clone(),
2936                    },
2937                    self.op_id,
2938                )
2939                .map_err(|err| match err {
2940                    DurableCatalogError::DuplicateKey => {
2941                        StorageError::CollectionMetadataAlreadyExists(id)
2942                    }
2943                    DurableCatalogError::UniquenessViolation => {
2944                        StorageError::PersistShardAlreadyInUse(shard)
2945                    }
2946                    err => StorageError::Generic(anyhow::anyhow!(err)),
2947                })?;
2948        }
2949        Ok(())
2950    }
2951
2952    fn delete_collection_metadata(&mut self, ids: BTreeSet<GlobalId>) -> Vec<(GlobalId, ShardId)> {
2953        let ks: Vec<_> = ids
2954            .into_iter()
2955            .map(|id| StorageCollectionMetadataKey { id })
2956            .collect();
2957        self.storage_collection_metadata
2958            .delete_by_keys(ks, self.op_id)
2959            .into_iter()
2960            .map(
2961                |(
2962                    StorageCollectionMetadataKey { id },
2963                    StorageCollectionMetadataValue { shard },
2964                )| (id, shard),
2965            )
2966            .collect()
2967    }
2968
2969    fn get_unfinalized_shards(&self) -> BTreeSet<ShardId> {
2970        self.unfinalized_shards
2971            .items()
2972            .into_iter()
2973            .map(|(UnfinalizedShardKey { shard }, ())| *shard)
2974            .collect()
2975    }
2976
2977    fn insert_unfinalized_shards(&mut self, s: BTreeSet<ShardId>) -> Result<(), StorageError> {
2978        for shard in s {
2979            match self
2980                .unfinalized_shards
2981                .insert(UnfinalizedShardKey { shard }, (), self.op_id)
2982            {
2983                // Inserting duplicate keys has no effect.
2984                Ok(()) | Err(DurableCatalogError::DuplicateKey) => {}
2985                Err(e) => Err(StorageError::Generic(anyhow::anyhow!(e)))?,
2986            };
2987        }
2988        Ok(())
2989    }
2990
2991    fn remove_unfinalized_shards(&mut self, shards: BTreeSet<ShardId>) {
2992        let ks: Vec<_> = shards
2993            .into_iter()
2994            .map(|shard| UnfinalizedShardKey { shard })
2995            .collect();
2996        let _ = self.unfinalized_shards.delete_by_keys(ks, self.op_id);
2997    }
2998
2999    fn get_txn_wal_shard(&self) -> Option<ShardId> {
3000        self.txn_wal_shard
3001            .values()
3002            .iter()
3003            .next()
3004            .map(|TxnWalShardValue { shard }| *shard)
3005    }
3006
3007    fn write_txn_wal_shard(&mut self, shard: ShardId) -> Result<(), StorageError> {
3008        self.txn_wal_shard
3009            .insert((), TxnWalShardValue { shard }, self.op_id)
3010            .map_err(|err| match err {
3011                DurableCatalogError::DuplicateKey => StorageError::TxnWalShardAlreadyExists,
3012                err => StorageError::Generic(anyhow::anyhow!(err)),
3013            })
3014    }
3015}
3016
3017/// Describes a set of changes to apply as the result of a catalog transaction.
3018#[derive(Debug, PartialEq)]
3019pub struct TransactionBatch {
3020    pub(crate) databases: Vec<(proto::DatabaseKey, proto::DatabaseValue, Diff)>,
3021    pub(crate) schemas: Vec<(proto::SchemaKey, proto::SchemaValue, Diff)>,
3022    pub(crate) items: Vec<(proto::ItemKey, proto::ItemValue, Diff)>,
3023    pub(crate) comments: Vec<(proto::CommentKey, proto::CommentValue, Diff)>,
3024    pub(crate) roles: Vec<(proto::RoleKey, proto::RoleValue, Diff)>,
3025    pub(crate) role_auth: Vec<(proto::RoleAuthKey, proto::RoleAuthValue, Diff)>,
3026    pub(crate) clusters: Vec<(proto::ClusterKey, proto::ClusterValue, Diff)>,
3027    pub(crate) cluster_replicas: Vec<(proto::ClusterReplicaKey, proto::ClusterReplicaValue, Diff)>,
3028    pub(crate) network_policies: Vec<(proto::NetworkPolicyKey, proto::NetworkPolicyValue, Diff)>,
3029    pub(crate) introspection_sources: Vec<(
3030        proto::ClusterIntrospectionSourceIndexKey,
3031        proto::ClusterIntrospectionSourceIndexValue,
3032        Diff,
3033    )>,
3034    pub(crate) id_allocator: Vec<(proto::IdAllocKey, proto::IdAllocValue, Diff)>,
3035    pub(crate) configs: Vec<(proto::ConfigKey, proto::ConfigValue, Diff)>,
3036    pub(crate) settings: Vec<(proto::SettingKey, proto::SettingValue, Diff)>,
3037    pub(crate) system_gid_mapping: Vec<(proto::GidMappingKey, proto::GidMappingValue, Diff)>,
3038    pub(crate) system_configurations: Vec<(
3039        proto::ServerConfigurationKey,
3040        proto::ServerConfigurationValue,
3041        Diff,
3042    )>,
3043    pub(crate) cluster_system_configurations: Vec<(
3044        proto::ClusterSystemConfigurationKey,
3045        proto::ClusterSystemConfigurationValue,
3046        Diff,
3047    )>,
3048    pub(crate) replica_system_configurations: Vec<(
3049        proto::ReplicaSystemConfigurationKey,
3050        proto::ReplicaSystemConfigurationValue,
3051        Diff,
3052    )>,
3053    pub(crate) default_privileges: Vec<(
3054        proto::DefaultPrivilegesKey,
3055        proto::DefaultPrivilegesValue,
3056        Diff,
3057    )>,
3058    pub(crate) source_references: Vec<(
3059        proto::SourceReferencesKey,
3060        proto::SourceReferencesValue,
3061        Diff,
3062    )>,
3063    pub(crate) system_privileges: Vec<(
3064        proto::SystemPrivilegesKey,
3065        proto::SystemPrivilegesValue,
3066        Diff,
3067    )>,
3068    pub(crate) storage_collection_metadata: Vec<(
3069        proto::StorageCollectionMetadataKey,
3070        proto::StorageCollectionMetadataValue,
3071        Diff,
3072    )>,
3073    pub(crate) unfinalized_shards: Vec<(proto::UnfinalizedShardKey, (), Diff)>,
3074    pub(crate) txn_wal_shard: Vec<((), proto::TxnWalShardValue, Diff)>,
3075    pub(crate) audit_log_updates: Vec<(proto::AuditLogKey, (), Diff)>,
3076    /// The upper of the catalog when the transaction started.
3077    pub(crate) upper: mz_repr::Timestamp,
3078    // A private, non-cloneable capability keeps batches constructible only by
3079    // transaction decomposition.
3080    _commit_capability: CommitCapability,
3081}
3082
3083impl TransactionBatch {
3084    pub fn is_empty(&self) -> bool {
3085        let TransactionBatch {
3086            databases,
3087            schemas,
3088            items,
3089            comments,
3090            roles,
3091            role_auth,
3092            clusters,
3093            cluster_replicas,
3094            network_policies,
3095            introspection_sources,
3096            id_allocator,
3097            configs,
3098            settings,
3099            source_references,
3100            system_gid_mapping,
3101            system_configurations,
3102            cluster_system_configurations,
3103            replica_system_configurations,
3104            default_privileges,
3105            system_privileges,
3106            storage_collection_metadata,
3107            unfinalized_shards,
3108            txn_wal_shard,
3109            audit_log_updates,
3110            upper: _,
3111            _commit_capability: _,
3112        } = self;
3113        databases.is_empty()
3114            && schemas.is_empty()
3115            && items.is_empty()
3116            && comments.is_empty()
3117            && roles.is_empty()
3118            && role_auth.is_empty()
3119            && clusters.is_empty()
3120            && cluster_replicas.is_empty()
3121            && network_policies.is_empty()
3122            && introspection_sources.is_empty()
3123            && id_allocator.is_empty()
3124            && configs.is_empty()
3125            && settings.is_empty()
3126            && source_references.is_empty()
3127            && system_gid_mapping.is_empty()
3128            && system_configurations.is_empty()
3129            && cluster_system_configurations.is_empty()
3130            && replica_system_configurations.is_empty()
3131            && default_privileges.is_empty()
3132            && system_privileges.is_empty()
3133            && storage_collection_metadata.is_empty()
3134            && unfinalized_shards.is_empty()
3135            && txn_wal_shard.is_empty()
3136            && audit_log_updates.is_empty()
3137    }
3138}
3139
3140#[derive(Debug, Clone, PartialEq, Eq)]
3141struct TransactionUpdate<V> {
3142    value: V,
3143    ts: Timestamp,
3144    diff: Diff,
3145}
3146
3147/// Utility trait to check for plan validity.
3148trait UniqueName {
3149    /// Does the item have a unique name? If yes, we can check for name equality in validity
3150    /// checking.
3151    const HAS_UNIQUE_NAME: bool;
3152    /// The unique name, only returns a meaningful name if [`Self::HAS_UNIQUE_NAME`] is `true`.
3153    fn unique_name(&self) -> &str;
3154}
3155
3156mod unique_name {
3157    use crate::durable::objects::*;
3158
3159    macro_rules! impl_unique_name {
3160        ($($t:ty),* $(,)?) => {
3161            $(
3162                impl crate::durable::transaction::UniqueName for $t {
3163                    const HAS_UNIQUE_NAME: bool = true;
3164                    fn unique_name(&self) -> &str {
3165                        &self.name
3166                    }
3167                }
3168            )*
3169        };
3170    }
3171
3172    macro_rules! impl_no_unique_name {
3173        ($($t:ty),* $(,)?) => {
3174            $(
3175                impl crate::durable::transaction::UniqueName for $t {
3176                    const HAS_UNIQUE_NAME: bool = false;
3177                    fn unique_name(&self) -> &str {
3178                       ""
3179                    }
3180                }
3181            )*
3182        };
3183    }
3184
3185    impl_unique_name! {
3186        ClusterReplicaValue,
3187        ClusterValue,
3188        DatabaseValue,
3189        ItemValue,
3190        NetworkPolicyValue,
3191        RoleValue,
3192        SchemaValue,
3193    }
3194
3195    impl_no_unique_name!(
3196        (),
3197        ClusterIntrospectionSourceIndexValue,
3198        ClusterSystemConfigurationValue,
3199        CommentValue,
3200        ConfigValue,
3201        DefaultPrivilegesValue,
3202        GidMappingValue,
3203        IdAllocValue,
3204        ReplicaSystemConfigurationValue,
3205        ServerConfigurationValue,
3206        SettingValue,
3207        SourceReferencesValue,
3208        StorageCollectionMetadataValue,
3209        SystemPrivilegesValue,
3210        TxnWalShardValue,
3211        RoleAuthValue,
3212    );
3213
3214    #[cfg(test)]
3215    mod test {
3216        impl_no_unique_name!(String,);
3217    }
3218}
3219
3220/// A collection's uniqueness constraint.
3221///
3222/// `violation` reports whether two values conflict, i.e. cannot both exist.
3223///
3224/// `is_unique_key_unchanged_after_update` reports whether an update left
3225/// unchanged every field `violation` reads. It is used as an optimization
3226/// since `violation` is an expensive operation to run.
3227#[derive(Debug)]
3228struct UniquenessCheck<V> {
3229    violation: fn(a: &V, b: &V) -> bool,
3230    is_unique_key_unchanged_after_update: fn(prev: &V, next: &V) -> bool,
3231}
3232
3233/// TableTransaction emulates some features of a typical SQL transaction over
3234/// table for a Collection.
3235///
3236/// It supports:
3237/// - uniqueness constraints
3238/// - transactional reads and writes (including read-your-writes before commit)
3239///
3240/// `K` is the primary key type. Multiple entries with the same key are disallowed.
3241/// `V` is the an arbitrary value type.
3242#[derive(Debug)]
3243struct TableTransaction<K, V> {
3244    initial: BTreeMap<K, V>,
3245    // The desired updates to keys after commit.
3246    // Invariant: Value is sorted by `ts`.
3247    pending: BTreeMap<K, Vec<TransactionUpdate<V>>>,
3248    // `None` for collections with no uniqueness constraint.
3249    uniqueness_check: Option<UniquenessCheck<V>>,
3250}
3251
3252impl<K, V> TableTransaction<K, V>
3253where
3254    K: Ord + Eq + Clone + Debug,
3255    V: Ord + Clone + Debug + UniqueName,
3256{
3257    /// Create a new TableTransaction with initial data.
3258    ///
3259    /// Internally the catalog serializes data as protobuf. All fields in a proto message are
3260    /// optional, which makes using them in Rust cumbersome. Generic parameters `KP` and `VP` are
3261    /// protobuf types which deserialize to `K` and `V` that a [`TableTransaction`] is generic
3262    /// over.
3263    fn new<KP, VP>(initial: BTreeMap<KP, VP>) -> Result<Self, TryFromProtoError>
3264    where
3265        K: RustType<KP>,
3266        V: RustType<VP>,
3267    {
3268        let initial = initial
3269            .into_iter()
3270            .map(RustType::from_proto)
3271            .collect::<Result<_, _>>()?;
3272
3273        Ok(Self {
3274            initial,
3275            pending: BTreeMap::new(),
3276            uniqueness_check: None,
3277        })
3278    }
3279
3280    /// Like [`Self::new`], but with the collection's uniqueness constraint.
3281    /// See [`UniquenessCheck`] for more details on the uniqueness constraint.
3282    fn new_with_uniqueness_fn<KP, VP>(
3283        initial: BTreeMap<KP, VP>,
3284        uniqueness_violation: fn(a: &V, b: &V) -> bool,
3285        is_unique_key_unchanged_after_update: fn(prev: &V, next: &V) -> bool,
3286    ) -> Result<Self, TryFromProtoError>
3287    where
3288        K: RustType<KP>,
3289        V: RustType<VP>,
3290    {
3291        let initial = initial
3292            .into_iter()
3293            .map(RustType::from_proto)
3294            .collect::<Result<_, _>>()?;
3295
3296        Ok(Self {
3297            initial,
3298            pending: BTreeMap::new(),
3299            uniqueness_check: Some(UniquenessCheck {
3300                violation: uniqueness_violation,
3301                is_unique_key_unchanged_after_update,
3302            }),
3303        })
3304    }
3305
3306    /// Consumes and returns the pending changes and their diffs. `Diff` is
3307    /// guaranteed to be 1 or -1.
3308    fn pending<KP, VP>(self) -> Vec<(KP, VP, Diff)>
3309    where
3310        K: RustType<KP>,
3311        V: RustType<VP>,
3312    {
3313        soft_assert_no_log!(self.verify().is_ok());
3314        // Pending describes the desired final state for some keys. K,V pairs should be
3315        // retracted if they already exist and were deleted or are being updated.
3316        self.pending
3317            .into_iter()
3318            .flat_map(|(k, v)| {
3319                let mut v: Vec<_> = v
3320                    .into_iter()
3321                    .map(|TransactionUpdate { value, ts: _, diff }| (value, diff))
3322                    .collect();
3323                differential_dataflow::consolidation::consolidate(&mut v);
3324                v.into_iter().map(move |(v, diff)| (k.clone(), v, diff))
3325            })
3326            .map(|(key, val, diff)| (key.into_proto(), val.into_proto(), diff))
3327            .collect()
3328    }
3329
3330    /// Verifies that no items in `self` violate `self.uniqueness_check`.
3331    ///
3332    /// Runtime is O(n^2), where n is the number of items in `self`, if
3333    /// [`UniqueName::HAS_UNIQUE_NAME`] is false for `V`. Prefer using [`Self::verify_keys`].
3334    fn verify(&self) -> Result<(), DurableCatalogError> {
3335        if let Some(check) = &self.uniqueness_check {
3336            // Compare each value to each other value and ensure they are unique.
3337            let items = self.values();
3338            if V::HAS_UNIQUE_NAME {
3339                let by_name: BTreeMap<_, _> = items
3340                    .iter()
3341                    .enumerate()
3342                    .map(|(v, vi)| (vi.unique_name(), (v, vi)))
3343                    .collect();
3344                for (i, vi) in items.iter().enumerate() {
3345                    if let Some((j, vj)) = by_name.get(vi.unique_name()) {
3346                        if i != *j && (check.violation)(vi, *vj) {
3347                            return Err(DurableCatalogError::UniquenessViolation);
3348                        }
3349                    }
3350                }
3351            } else {
3352                for (i, vi) in items.iter().enumerate() {
3353                    for (j, vj) in items.iter().enumerate() {
3354                        if i != j && (check.violation)(vi, vj) {
3355                            return Err(DurableCatalogError::UniquenessViolation);
3356                        }
3357                    }
3358                }
3359            }
3360        }
3361        soft_assert_no_log!(
3362            self.pending
3363                .values()
3364                .all(|pending| { pending.is_sorted_by(|a, b| a.ts <= b.ts) }),
3365            "pending should be sorted by timestamp: {:?}",
3366            self.pending
3367        );
3368        Ok(())
3369    }
3370
3371    /// Verifies that no items in `self` violate `self.uniqueness_check` with `keys`.
3372    ///
3373    /// Runtime is O(n * k), where n is the number of items in `self` and k is the number of
3374    /// items in `keys`.
3375    fn verify_keys<'a>(
3376        &self,
3377        keys: impl IntoIterator<Item = &'a K>,
3378    ) -> Result<(), DurableCatalogError>
3379    where
3380        K: 'a,
3381    {
3382        if let Some(check) = &self.uniqueness_check {
3383            let entries: Vec<_> = keys
3384                .into_iter()
3385                .filter_map(|key| self.get(key).map(|value| (key, value)))
3386                .collect();
3387            // Compare each value in `entries` to each value in `self` and ensure they are unique.
3388            for (ki, vi) in self.items() {
3389                for (kj, vj) in &entries {
3390                    if ki != *kj && (check.violation)(vi, vj) {
3391                        return Err(DurableCatalogError::UniquenessViolation);
3392                    }
3393                }
3394            }
3395        }
3396        soft_assert_no_log!(self.verify().is_ok());
3397        Ok(())
3398    }
3399
3400    /// Iterates over the items viewable in the current transaction in arbitrary
3401    /// order and applies `f` on all key, value pairs.
3402    fn for_values<'a, F: FnMut(&'a K, &'a V)>(&'a self, mut f: F) {
3403        let mut seen = BTreeSet::new();
3404        for k in self.pending.keys() {
3405            seen.insert(k);
3406            let v = self.get(k);
3407            // Deleted items don't exist so shouldn't be visited, but still suppress
3408            // visiting the key later.
3409            if let Some(v) = v {
3410                f(k, v);
3411            }
3412        }
3413        for (k, v) in self.initial.iter() {
3414            // Add on initial items that don't have updates.
3415            if !seen.contains(k) {
3416                f(k, v);
3417            }
3418        }
3419    }
3420
3421    /// Returns the current value of `k`.
3422    fn get(&self, k: &K) -> Option<&V> {
3423        let pending = self.pending.get(k).map(Vec::as_slice).unwrap_or_default();
3424        let mut updates = Vec::with_capacity(pending.len() + 1);
3425        if let Some(initial) = self.initial.get(k) {
3426            updates.push((initial, Diff::ONE));
3427        }
3428        updates.extend(
3429            pending
3430                .into_iter()
3431                .map(|TransactionUpdate { value, ts: _, diff }| (value, *diff)),
3432        );
3433
3434        differential_dataflow::consolidation::consolidate(&mut updates);
3435        assert!(updates.len() <= 1);
3436        updates.into_iter().next().map(|(v, _)| v)
3437    }
3438
3439    /// Returns the items viewable in the current transaction. The items are
3440    /// cloned, so this is an expensive operation. Prefer using [`Self::items`], or
3441    /// [`Self::for_values`].
3442    // Used by tests.
3443    #[cfg(test)]
3444    fn items_cloned(&self) -> BTreeMap<K, V> {
3445        let mut items = BTreeMap::new();
3446        self.for_values(|k, v| {
3447            items.insert(k.clone(), v.clone());
3448        });
3449        items
3450    }
3451
3452    /// Returns the current items as proto-typed key-value pairs, suitable for
3453    /// constructing a [`Snapshot`]. This merges `initial` and `pending` to
3454    /// produce the current view and converts back to proto types.
3455    fn current_items_proto<KP, VP>(&self) -> BTreeMap<KP, VP>
3456    where
3457        K: RustType<KP>,
3458        V: RustType<VP>,
3459        KP: Ord,
3460    {
3461        let mut items = BTreeMap::new();
3462        self.for_values(|k, v| {
3463            items.insert(k.into_proto(), v.into_proto());
3464        });
3465        items
3466    }
3467
3468    /// Returns the items viewable in the current transaction as references. Returns a map
3469    /// of references.
3470    fn items(&self) -> BTreeMap<&K, &V> {
3471        let mut items = BTreeMap::new();
3472        self.for_values(|k, v| {
3473            items.insert(k, v);
3474        });
3475        items
3476    }
3477
3478    /// Returns the values viewable in the current transaction as references.
3479    fn values(&self) -> BTreeSet<&V> {
3480        let mut items = BTreeSet::new();
3481        self.for_values(|_, v| {
3482            items.insert(v);
3483        });
3484        items
3485    }
3486
3487    /// Returns the number of items viewable in the current transaction.
3488    fn len(&self) -> usize {
3489        let mut count = 0;
3490        self.for_values(|_, _| {
3491            count += 1;
3492        });
3493        count
3494    }
3495
3496    /// Iterates over the items viewable in the current transaction, and provides a
3497    /// map where additional pending items can be inserted, which will be appended
3498    /// to current pending items. Does not verify uniqueness.
3499    fn for_values_mut<F: FnMut(&mut BTreeMap<K, Vec<TransactionUpdate<V>>>, &K, &V)>(
3500        &mut self,
3501        mut f: F,
3502    ) {
3503        let mut pending = BTreeMap::new();
3504        self.for_values(|k, v| f(&mut pending, k, v));
3505        for (k, updates) in pending {
3506            self.pending.entry(k).or_default().extend(updates);
3507        }
3508    }
3509
3510    /// Inserts a new k,v pair.
3511    ///
3512    /// Returns an error if the uniqueness check failed or the key already exists.
3513    fn insert(&mut self, k: K, v: V, ts: Timestamp) -> Result<(), DurableCatalogError> {
3514        let mut violation = None;
3515        let uniqueness_violation = self.uniqueness_check.as_ref().map(|check| check.violation);
3516        self.for_values(|for_k, for_v| {
3517            if &k == for_k {
3518                violation = Some(DurableCatalogError::DuplicateKey);
3519            }
3520            if let Some(uniqueness_violation) = uniqueness_violation {
3521                if uniqueness_violation(for_v, &v) {
3522                    violation = Some(DurableCatalogError::UniquenessViolation);
3523                }
3524            }
3525        });
3526        if let Some(violation) = violation {
3527            return Err(violation);
3528        }
3529        self.pending.entry(k).or_default().push(TransactionUpdate {
3530            value: v,
3531            ts,
3532            diff: Diff::ONE,
3533        });
3534        soft_assert_no_log!(self.verify().is_ok());
3535        Ok(())
3536    }
3537
3538    /// Updates k, v pairs. `f` is a function that can return `Some(V)` if the
3539    /// value should be updated, otherwise `None`. Returns the number of changed
3540    /// entries.
3541    ///
3542    /// Returns an error if the uniqueness check failed.
3543    ///
3544    /// Prefer using [`Self::update_by_key`] or [`Self::update_by_keys`], which generally have
3545    /// better performance.
3546    fn update<F: Fn(&K, &V) -> Option<V>>(
3547        &mut self,
3548        f: F,
3549        ts: Timestamp,
3550    ) -> Result<Diff, DurableCatalogError> {
3551        let mut changed = Diff::ZERO;
3552        let mut keys = BTreeSet::new();
3553        // Keep a copy of pending in case of uniqueness violation.
3554        let pending = self.pending.clone();
3555        self.for_values_mut(|p, k, v| {
3556            if let Some(next) = f(k, v) {
3557                changed += Diff::ONE;
3558                keys.insert(k.clone());
3559                let updates = p.entry(k.clone()).or_default();
3560                updates.push(TransactionUpdate {
3561                    value: v.clone(),
3562                    ts,
3563                    diff: Diff::MINUS_ONE,
3564                });
3565                updates.push(TransactionUpdate {
3566                    value: next,
3567                    ts,
3568                    diff: Diff::ONE,
3569                });
3570            }
3571        });
3572        // Check for uniqueness violation.
3573        if let Err(err) = self.verify_keys(&keys) {
3574            self.pending = pending;
3575            Err(err)
3576        } else {
3577            Ok(changed)
3578        }
3579    }
3580
3581    /// Updates `k`, `v` pair if `k` already exists in `self`.
3582    ///
3583    /// Returns `true` if `k` was updated, `false` otherwise.
3584    /// Returns an error if the uniqueness check failed.
3585    fn update_by_key(&mut self, k: K, v: V, ts: Timestamp) -> Result<bool, DurableCatalogError> {
3586        if let Some(cur_v) = self.get(&k) {
3587            if v != *cur_v {
3588                self.set(k, Some(v), ts)?;
3589            }
3590            Ok(true)
3591        } else {
3592            Ok(false)
3593        }
3594    }
3595
3596    /// Updates k, v pairs. Keys that don't already exist in `self` are ignored.
3597    ///
3598    /// Returns the number of changed entries.
3599    /// Returns an error if the uniqueness check failed.
3600    fn update_by_keys(
3601        &mut self,
3602        kvs: impl IntoIterator<Item = (K, V)>,
3603        ts: Timestamp,
3604    ) -> Result<Diff, DurableCatalogError> {
3605        let kvs: Vec<_> = kvs
3606            .into_iter()
3607            .filter_map(|(k, v)| match self.get(&k) {
3608                // Record if updating this entry would be a no-op.
3609                Some(cur_v) => Some((*cur_v == v, k, v)),
3610                None => None,
3611            })
3612            .collect();
3613        let changed = kvs.len();
3614        let changed =
3615            Diff::try_from(changed).map_err(|e| DurableCatalogError::Internal(e.to_string()))?;
3616        let kvs = kvs
3617            .into_iter()
3618            // Filter out no-ops to save some work.
3619            .filter(|(no_op, _, _)| !no_op)
3620            .map(|(_, k, v)| (k, Some(v)))
3621            .collect();
3622        self.set_many(kvs, ts)?;
3623        Ok(changed)
3624    }
3625
3626    /// Whether changing a key's value from `prev` to `next` can introduce a uniqueness violation
3627    fn update_needs_uniqueness_check(&self, prev: Option<&V>, next: Option<&V>) -> bool {
3628        match (&self.uniqueness_check, prev, next) {
3629            // No uniqueness constraint, or a delete: nothing to check.
3630            (None, _, _) | (_, _, None) => false,
3631            // An update: a scan is only needed if it changed the unique key.
3632            (Some(check), Some(prev), Some(next)) => {
3633                !(check.is_unique_key_unchanged_after_update)(prev, next)
3634            }
3635            // An insert: must check.
3636            (Some(_), None, Some(_)) => true,
3637        }
3638    }
3639
3640    /// Set the value for a key. Returns the previous entry if the key existed,
3641    /// otherwise None.
3642    ///
3643    /// Returns an error if the uniqueness check failed.
3644    ///
3645    /// DO NOT call this function in a loop, use [`Self::set_many`] instead.
3646    fn set(&mut self, k: K, v: Option<V>, ts: Timestamp) -> Result<Option<V>, DurableCatalogError> {
3647        let prev = self.get(&k).cloned();
3648        let needs_uniqueness_check = self.update_needs_uniqueness_check(prev.as_ref(), v.as_ref());
3649        let entry = self.pending.entry(k.clone()).or_default();
3650        let restore_len = entry.len();
3651
3652        match (v, prev.clone()) {
3653            (Some(v), Some(prev)) => {
3654                entry.push(TransactionUpdate {
3655                    value: prev,
3656                    ts,
3657                    diff: Diff::MINUS_ONE,
3658                });
3659                entry.push(TransactionUpdate {
3660                    value: v,
3661                    ts,
3662                    diff: Diff::ONE,
3663                });
3664            }
3665            (Some(v), None) => {
3666                entry.push(TransactionUpdate {
3667                    value: v,
3668                    ts,
3669                    diff: Diff::ONE,
3670                });
3671            }
3672            (None, Some(prev)) => {
3673                entry.push(TransactionUpdate {
3674                    value: prev,
3675                    ts,
3676                    diff: Diff::MINUS_ONE,
3677                });
3678            }
3679            (None, None) => {}
3680        }
3681
3682        // Check for uniqueness violation
3683        if needs_uniqueness_check {
3684            if let Err(err) = self.verify_keys([&k]) {
3685                // Revert self.pending to the state it was in before calling this
3686                // function.
3687                let pending = self.pending.get_mut(&k).expect("inserted above");
3688                pending.truncate(restore_len);
3689                return Err(err);
3690            }
3691        }
3692        Ok(prev)
3693    }
3694
3695    /// Set the values for many keys. Returns the previous entry for each key if the key existed,
3696    /// otherwise None.
3697    ///
3698    /// Returns an error if any uniqueness check failed.
3699    fn set_many(
3700        &mut self,
3701        kvs: BTreeMap<K, Option<V>>,
3702        ts: Timestamp,
3703    ) -> Result<BTreeMap<K, Option<V>>, DurableCatalogError> {
3704        if kvs.is_empty() {
3705            return Ok(BTreeMap::new());
3706        }
3707
3708        let mut prevs = BTreeMap::new();
3709        let mut restores = BTreeMap::new();
3710        // Only the keys whose update can introduce a uniqueness violation need scanning.
3711        let mut keys_to_verify_uniqueness = Vec::new();
3712
3713        for (k, v) in kvs {
3714            let prev = self.get(&k).cloned();
3715            if self.update_needs_uniqueness_check(prev.as_ref(), v.as_ref()) {
3716                keys_to_verify_uniqueness.push(k.clone());
3717            }
3718            let entry = self.pending.entry(k.clone()).or_default();
3719            restores.insert(k.clone(), entry.len());
3720
3721            match (v, prev.clone()) {
3722                (Some(v), Some(prev)) => {
3723                    entry.push(TransactionUpdate {
3724                        value: prev,
3725                        ts,
3726                        diff: Diff::MINUS_ONE,
3727                    });
3728                    entry.push(TransactionUpdate {
3729                        value: v,
3730                        ts,
3731                        diff: Diff::ONE,
3732                    });
3733                }
3734                (Some(v), None) => {
3735                    entry.push(TransactionUpdate {
3736                        value: v,
3737                        ts,
3738                        diff: Diff::ONE,
3739                    });
3740                }
3741                (None, Some(prev)) => {
3742                    entry.push(TransactionUpdate {
3743                        value: prev,
3744                        ts,
3745                        diff: Diff::MINUS_ONE,
3746                    });
3747                }
3748                (None, None) => {}
3749            }
3750
3751            prevs.insert(k, prev);
3752        }
3753
3754        // Check for uniqueness violation.
3755        if let Err(err) = self.verify_keys(keys_to_verify_uniqueness.iter()) {
3756            for (k, restore_len) in restores {
3757                // Revert self.pending to the state it was in before calling this
3758                // function.
3759                let pending = self.pending.get_mut(&k).expect("inserted above");
3760                pending.truncate(restore_len);
3761            }
3762            Err(err)
3763        } else {
3764            Ok(prevs)
3765        }
3766    }
3767
3768    /// Deletes items for which `f` returns true. Returns the keys and values of
3769    /// the deleted entries.
3770    ///
3771    /// Prefer using [`Self::delete_by_key`] or [`Self::delete_by_keys`], which generally have
3772    /// better performance.
3773    fn delete<F: Fn(&K, &V) -> bool>(&mut self, f: F, ts: Timestamp) -> Vec<(K, V)> {
3774        let mut deleted = Vec::new();
3775        self.for_values_mut(|p, k, v| {
3776            if f(k, v) {
3777                deleted.push((k.clone(), v.clone()));
3778                p.entry(k.clone()).or_default().push(TransactionUpdate {
3779                    value: v.clone(),
3780                    ts,
3781                    diff: Diff::MINUS_ONE,
3782                });
3783            }
3784        });
3785        soft_assert_no_log!(self.verify().is_ok());
3786        deleted
3787    }
3788
3789    /// Deletes item with key `k`.
3790    ///
3791    /// Returns the value of the deleted entry, if it existed.
3792    fn delete_by_key(&mut self, k: K, ts: Timestamp) -> Option<V> {
3793        self.set(k, None, ts)
3794            .expect("deleting an entry cannot violate uniqueness")
3795    }
3796
3797    /// Deletes items with key in `ks`.
3798    ///
3799    /// Returns the keys and values of the deleted entries.
3800    fn delete_by_keys(&mut self, ks: impl IntoIterator<Item = K>, ts: Timestamp) -> Vec<(K, V)> {
3801        let kvs = ks.into_iter().map(|k| (k, None)).collect();
3802        let prevs = self
3803            .set_many(kvs, ts)
3804            .expect("deleting entries cannot violate uniqueness");
3805        prevs
3806            .into_iter()
3807            .filter_map(|(k, v)| v.map(|v| (k, v)))
3808            .collect()
3809    }
3810}
3811
3812#[cfg(test)]
3813#[allow(clippy::unwrap_used)]
3814mod tests {
3815    use super::*;
3816
3817    use mz_controller::clusters::ReplicaLogging;
3818    use mz_ore::now::SYSTEM_TIME;
3819    use mz_ore::{assert_none, assert_ok};
3820    use mz_persist_client::cache::PersistClientCache;
3821    use mz_persist_types::PersistLocation;
3822    use semver::Version;
3823
3824    use crate::durable::{
3825        ReplicaConfig, ReplicaLocation, TestCatalogStateBuilder, test_bootstrap_args,
3826    };
3827    use crate::memory;
3828
3829    #[mz_ore::test]
3830    fn test_table_transaction_simple() {
3831        fn uniqueness_violation(a: &String, b: &String) -> bool {
3832            a == b
3833        }
3834        let mut table = TableTransaction::new_with_uniqueness_fn(
3835            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "a".to_string())]),
3836            uniqueness_violation,
3837            uniqueness_violation,
3838        )
3839        .unwrap();
3840
3841        // Ideally, we compare for errors here, but it's hard/impossible to implement PartialEq
3842        // for DurableCatalogError.
3843        assert_ok!(table.insert(2i64.to_le_bytes().to_vec(), "b".to_string(), 0));
3844        assert_ok!(table.insert(3i64.to_le_bytes().to_vec(), "c".to_string(), 0));
3845        assert!(
3846            table
3847                .insert(1i64.to_le_bytes().to_vec(), "c".to_string(), 0)
3848                .is_err()
3849        );
3850        assert!(
3851            table
3852                .insert(4i64.to_le_bytes().to_vec(), "c".to_string(), 0)
3853                .is_err()
3854        );
3855    }
3856
3857    #[mz_ore::test]
3858    fn test_skip_scan_when_unique_key_unchanged() {
3859        fn first_char_same(prev: &String, next: &String) -> bool {
3860            prev.chars().next() == next.chars().next()
3861        }
3862
3863        // Panics if the uniqueness scan ever runs
3864        fn panic_uniqueness_violation(_: &String, _: &String) -> bool {
3865            panic!("uniqueness scan ran for an update that kept the same unique key");
3866        }
3867        let mut table = TableTransaction::new_with_uniqueness_fn(
3868            BTreeMap::from([
3869                (1i64.to_le_bytes().to_vec(), "a1".to_string()),
3870                (2i64.to_le_bytes().to_vec(), "b1".to_string()),
3871            ]),
3872            panic_uniqueness_violation,
3873            // We treat the first character as the unique key.
3874            first_char_same,
3875        )
3876        .unwrap();
3877        // Same first char, so the unique key didn't change and the update must not scan
3878        // for uniqueness.
3879        assert!(
3880            table
3881                .update_by_key(1i64.to_le_bytes().to_vec(), "a2".to_string(), 0)
3882                .unwrap()
3883        );
3884
3885        // An update that changes the unique key must still run the scan and detect a collision.
3886        fn real_uniqueness_violation(a: &String, b: &String) -> bool {
3887            a.chars().next() == b.chars().next()
3888        }
3889        let mut table = TableTransaction::new_with_uniqueness_fn(
3890            BTreeMap::from([
3891                (1i64.to_le_bytes().to_vec(), "a1".to_string()),
3892                (2i64.to_le_bytes().to_vec(), "b1".to_string()),
3893            ]),
3894            real_uniqueness_violation,
3895            first_char_same,
3896        )
3897        .unwrap();
3898        // Changing key 1's first char from 'a' to 'b' changes its unique key and collides
3899        // with key 2.
3900        assert!(
3901            table
3902                .update_by_key(1i64.to_le_bytes().to_vec(), "b2".to_string(), 0)
3903                .is_err()
3904        );
3905    }
3906
3907    #[mz_ore::test]
3908    fn test_table_transaction() {
3909        fn uniqueness_violation(a: &String, b: &String) -> bool {
3910            a == b
3911        }
3912        let mut table: BTreeMap<Vec<u8>, String> = BTreeMap::new();
3913
3914        fn commit(
3915            table: &mut BTreeMap<Vec<u8>, String>,
3916            mut pending: Vec<(Vec<u8>, String, Diff)>,
3917        ) {
3918            // Sort by diff so that we process retractions first.
3919            pending.sort_by(|a, b| a.2.cmp(&b.2));
3920            for (k, v, diff) in pending {
3921                if diff == Diff::MINUS_ONE {
3922                    let prev = table.remove(&k);
3923                    assert_eq!(prev, Some(v));
3924                } else if diff == Diff::ONE {
3925                    let prev = table.insert(k, v);
3926                    assert_eq!(prev, None);
3927                } else {
3928                    panic!("unexpected diff: {diff}");
3929                }
3930            }
3931        }
3932
3933        table.insert(1i64.to_le_bytes().to_vec(), "v1".to_string());
3934        table.insert(2i64.to_le_bytes().to_vec(), "v2".to_string());
3935        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
3936            table.clone(),
3937            uniqueness_violation,
3938            uniqueness_violation,
3939        )
3940        .unwrap();
3941        assert_eq!(table_txn.items_cloned(), table);
3942        assert_eq!(table_txn.delete(|_k, _v| false, 0).len(), 0);
3943        assert_eq!(table_txn.delete(|_k, v| v == "v2", 1).len(), 1);
3944        assert_eq!(
3945            table_txn.items_cloned(),
3946            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v1".to_string())])
3947        );
3948        assert_eq!(
3949            table_txn
3950                .update(|_k, _v| Some("v3".to_string()), 2)
3951                .unwrap(),
3952            Diff::ONE
3953        );
3954
3955        // Uniqueness violation.
3956        table_txn
3957            .insert(3i64.to_le_bytes().to_vec(), "v3".to_string(), 3)
3958            .unwrap_err();
3959
3960        table_txn
3961            .insert(3i64.to_le_bytes().to_vec(), "v4".to_string(), 4)
3962            .unwrap();
3963        assert_eq!(
3964            table_txn.items_cloned(),
3965            BTreeMap::from([
3966                (1i64.to_le_bytes().to_vec(), "v3".to_string()),
3967                (3i64.to_le_bytes().to_vec(), "v4".to_string()),
3968            ])
3969        );
3970        let err = table_txn
3971            .update(|_k, _v| Some("v1".to_string()), 5)
3972            .unwrap_err();
3973        assert!(
3974            matches!(err, DurableCatalogError::UniquenessViolation),
3975            "unexpected err: {err:?}"
3976        );
3977        let pending = table_txn.pending();
3978        assert_eq!(
3979            pending,
3980            vec![
3981                (
3982                    1i64.to_le_bytes().to_vec(),
3983                    "v1".to_string(),
3984                    Diff::MINUS_ONE
3985                ),
3986                (1i64.to_le_bytes().to_vec(), "v3".to_string(), Diff::ONE),
3987                (
3988                    2i64.to_le_bytes().to_vec(),
3989                    "v2".to_string(),
3990                    Diff::MINUS_ONE
3991                ),
3992                (3i64.to_le_bytes().to_vec(), "v4".to_string(), Diff::ONE),
3993            ]
3994        );
3995        commit(&mut table, pending);
3996        assert_eq!(
3997            table,
3998            BTreeMap::from([
3999                (1i64.to_le_bytes().to_vec(), "v3".to_string()),
4000                (3i64.to_le_bytes().to_vec(), "v4".to_string())
4001            ])
4002        );
4003
4004        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4005            table.clone(),
4006            uniqueness_violation,
4007            uniqueness_violation,
4008        )
4009        .unwrap();
4010        // Deleting then creating an item that has a uniqueness violation should work.
4011        assert_eq!(
4012            table_txn.delete(|k, _v| k == &1i64.to_le_bytes(), 0).len(),
4013            1
4014        );
4015        table_txn
4016            .insert(1i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4017            .unwrap();
4018        // Uniqueness violation in value.
4019        table_txn
4020            .insert(5i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4021            .unwrap_err();
4022        // Key already exists, expect error.
4023        table_txn
4024            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 0)
4025            .unwrap_err();
4026        assert_eq!(
4027            table_txn.delete(|k, _v| k == &1i64.to_le_bytes(), 0).len(),
4028            1
4029        );
4030        // Both the inserts work now because the key and uniqueness violation are gone.
4031        table_txn
4032            .insert(5i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4033            .unwrap();
4034        table_txn
4035            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 0)
4036            .unwrap();
4037        let pending = table_txn.pending();
4038        assert_eq!(
4039            pending,
4040            vec![
4041                (
4042                    1i64.to_le_bytes().to_vec(),
4043                    "v3".to_string(),
4044                    Diff::MINUS_ONE
4045                ),
4046                (1i64.to_le_bytes().to_vec(), "v5".to_string(), Diff::ONE),
4047                (5i64.to_le_bytes().to_vec(), "v3".to_string(), Diff::ONE),
4048            ]
4049        );
4050        commit(&mut table, pending);
4051        assert_eq!(
4052            table,
4053            BTreeMap::from([
4054                (1i64.to_le_bytes().to_vec(), "v5".to_string()),
4055                (3i64.to_le_bytes().to_vec(), "v4".to_string()),
4056                (5i64.to_le_bytes().to_vec(), "v3".to_string()),
4057            ])
4058        );
4059
4060        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4061            table.clone(),
4062            uniqueness_violation,
4063            uniqueness_violation,
4064        )
4065        .unwrap();
4066        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 3);
4067        table_txn
4068            .insert(1i64.to_le_bytes().to_vec(), "v1".to_string(), 0)
4069            .unwrap();
4070
4071        commit(&mut table, table_txn.pending());
4072        assert_eq!(
4073            table,
4074            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v1".to_string()),])
4075        );
4076
4077        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4078            table.clone(),
4079            uniqueness_violation,
4080            uniqueness_violation,
4081        )
4082        .unwrap();
4083        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 1);
4084        table_txn
4085            .insert(1i64.to_le_bytes().to_vec(), "v2".to_string(), 0)
4086            .unwrap();
4087        commit(&mut table, table_txn.pending());
4088        assert_eq!(
4089            table,
4090            BTreeMap::from([(1i64.to_le_bytes().to_vec(), "v2".to_string()),])
4091        );
4092
4093        // Verify we don't try to delete v3 or v4 during commit.
4094        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4095            table.clone(),
4096            uniqueness_violation,
4097            uniqueness_violation,
4098        )
4099        .unwrap();
4100        assert_eq!(table_txn.delete(|_k, _v| true, 0).len(), 1);
4101        table_txn
4102            .insert(1i64.to_le_bytes().to_vec(), "v3".to_string(), 0)
4103            .unwrap();
4104        table_txn
4105            .insert(1i64.to_le_bytes().to_vec(), "v4".to_string(), 1)
4106            .unwrap_err();
4107        assert_eq!(table_txn.delete(|_k, _v| true, 1).len(), 1);
4108        table_txn
4109            .insert(1i64.to_le_bytes().to_vec(), "v5".to_string(), 1)
4110            .unwrap();
4111        commit(&mut table, table_txn.pending());
4112        assert_eq!(
4113            table.clone().into_iter().collect::<Vec<_>>(),
4114            vec![(1i64.to_le_bytes().to_vec(), "v5".to_string())]
4115        );
4116
4117        // Test `set`.
4118        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4119            table.clone(),
4120            uniqueness_violation,
4121            uniqueness_violation,
4122        )
4123        .unwrap();
4124        // Uniqueness violation.
4125        table_txn
4126            .set(2i64.to_le_bytes().to_vec(), Some("v5".to_string()), 0)
4127            .unwrap_err();
4128        table_txn
4129            .set(3i64.to_le_bytes().to_vec(), Some("v6".to_string()), 1)
4130            .unwrap();
4131        table_txn.set(2i64.to_le_bytes().to_vec(), None, 2).unwrap();
4132        table_txn.set(1i64.to_le_bytes().to_vec(), None, 2).unwrap();
4133        let pending = table_txn.pending();
4134        assert_eq!(
4135            pending,
4136            vec![
4137                (
4138                    1i64.to_le_bytes().to_vec(),
4139                    "v5".to_string(),
4140                    Diff::MINUS_ONE
4141                ),
4142                (3i64.to_le_bytes().to_vec(), "v6".to_string(), Diff::ONE),
4143            ]
4144        );
4145        commit(&mut table, pending);
4146        assert_eq!(
4147            table,
4148            BTreeMap::from([(3i64.to_le_bytes().to_vec(), "v6".to_string())])
4149        );
4150
4151        // Duplicate `set`.
4152        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4153            table.clone(),
4154            uniqueness_violation,
4155            uniqueness_violation,
4156        )
4157        .unwrap();
4158        table_txn
4159            .set(3i64.to_le_bytes().to_vec(), Some("v6".to_string()), 0)
4160            .unwrap();
4161        let pending = table_txn.pending::<Vec<u8>, String>();
4162        assert!(pending.is_empty());
4163
4164        // Test `set_many`.
4165        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4166            table.clone(),
4167            uniqueness_violation,
4168            uniqueness_violation,
4169        )
4170        .unwrap();
4171        // Uniqueness violation.
4172        table_txn
4173            .set_many(
4174                BTreeMap::from([
4175                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4176                    (42i64.to_le_bytes().to_vec(), Some("v1".to_string())),
4177                ]),
4178                0,
4179            )
4180            .unwrap_err();
4181        table_txn
4182            .set_many(
4183                BTreeMap::from([
4184                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4185                    (3i64.to_le_bytes().to_vec(), Some("v1".to_string())),
4186                ]),
4187                1,
4188            )
4189            .unwrap();
4190        table_txn
4191            .set_many(
4192                BTreeMap::from([
4193                    (42i64.to_le_bytes().to_vec(), Some("v7".to_string())),
4194                    (3i64.to_le_bytes().to_vec(), None),
4195                ]),
4196                2,
4197            )
4198            .unwrap();
4199        let pending = table_txn.pending();
4200        assert_eq!(
4201            pending,
4202            vec![
4203                (1i64.to_le_bytes().to_vec(), "v6".to_string(), Diff::ONE),
4204                (
4205                    3i64.to_le_bytes().to_vec(),
4206                    "v6".to_string(),
4207                    Diff::MINUS_ONE
4208                ),
4209                (42i64.to_le_bytes().to_vec(), "v7".to_string(), Diff::ONE),
4210            ]
4211        );
4212        commit(&mut table, pending);
4213        assert_eq!(
4214            table,
4215            BTreeMap::from([
4216                (1i64.to_le_bytes().to_vec(), "v6".to_string()),
4217                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4218            ])
4219        );
4220
4221        // Duplicate `set_many`.
4222        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4223            table.clone(),
4224            uniqueness_violation,
4225            uniqueness_violation,
4226        )
4227        .unwrap();
4228        table_txn
4229            .set_many(
4230                BTreeMap::from([
4231                    (1i64.to_le_bytes().to_vec(), Some("v6".to_string())),
4232                    (42i64.to_le_bytes().to_vec(), Some("v7".to_string())),
4233                ]),
4234                0,
4235            )
4236            .unwrap();
4237        let pending = table_txn.pending::<Vec<u8>, String>();
4238        assert!(pending.is_empty());
4239        commit(&mut table, pending);
4240        assert_eq!(
4241            table,
4242            BTreeMap::from([
4243                (1i64.to_le_bytes().to_vec(), "v6".to_string()),
4244                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4245            ])
4246        );
4247
4248        // Test `update_by_key`
4249        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4250            table.clone(),
4251            uniqueness_violation,
4252            uniqueness_violation,
4253        )
4254        .unwrap();
4255        // Uniqueness violation.
4256        table_txn
4257            .update_by_key(1i64.to_le_bytes().to_vec(), "v7".to_string(), 0)
4258            .unwrap_err();
4259        assert!(
4260            table_txn
4261                .update_by_key(1i64.to_le_bytes().to_vec(), "v8".to_string(), 1)
4262                .unwrap()
4263        );
4264        assert!(
4265            !table_txn
4266                .update_by_key(5i64.to_le_bytes().to_vec(), "v8".to_string(), 2)
4267                .unwrap()
4268        );
4269        let pending = table_txn.pending();
4270        assert_eq!(
4271            pending,
4272            vec![
4273                (
4274                    1i64.to_le_bytes().to_vec(),
4275                    "v6".to_string(),
4276                    Diff::MINUS_ONE
4277                ),
4278                (1i64.to_le_bytes().to_vec(), "v8".to_string(), Diff::ONE),
4279            ]
4280        );
4281        commit(&mut table, pending);
4282        assert_eq!(
4283            table,
4284            BTreeMap::from([
4285                (1i64.to_le_bytes().to_vec(), "v8".to_string()),
4286                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4287            ])
4288        );
4289
4290        // Duplicate `update_by_key`.
4291        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4292            table.clone(),
4293            uniqueness_violation,
4294            uniqueness_violation,
4295        )
4296        .unwrap();
4297        assert!(
4298            table_txn
4299                .update_by_key(1i64.to_le_bytes().to_vec(), "v8".to_string(), 0)
4300                .unwrap()
4301        );
4302        let pending = table_txn.pending::<Vec<u8>, String>();
4303        assert!(pending.is_empty());
4304        commit(&mut table, pending);
4305        assert_eq!(
4306            table,
4307            BTreeMap::from([
4308                (1i64.to_le_bytes().to_vec(), "v8".to_string()),
4309                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4310            ])
4311        );
4312
4313        // Test `update_by_keys`
4314        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4315            table.clone(),
4316            uniqueness_violation,
4317            uniqueness_violation,
4318        )
4319        .unwrap();
4320        // Uniqueness violation.
4321        table_txn
4322            .update_by_keys(
4323                [
4324                    (1i64.to_le_bytes().to_vec(), "v7".to_string()),
4325                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4326                ],
4327                0,
4328            )
4329            .unwrap_err();
4330        let n = table_txn
4331            .update_by_keys(
4332                [
4333                    (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4334                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4335                ],
4336                1,
4337            )
4338            .unwrap();
4339        assert_eq!(n, Diff::ONE);
4340        let n = table_txn
4341            .update_by_keys(
4342                [
4343                    (15i64.to_le_bytes().to_vec(), "v9".to_string()),
4344                    (5i64.to_le_bytes().to_vec(), "v7".to_string()),
4345                ],
4346                2,
4347            )
4348            .unwrap();
4349        assert_eq!(n, Diff::ZERO);
4350        let pending = table_txn.pending();
4351        assert_eq!(
4352            pending,
4353            vec![
4354                (
4355                    1i64.to_le_bytes().to_vec(),
4356                    "v8".to_string(),
4357                    Diff::MINUS_ONE
4358                ),
4359                (1i64.to_le_bytes().to_vec(), "v9".to_string(), Diff::ONE),
4360            ]
4361        );
4362        commit(&mut table, pending);
4363        assert_eq!(
4364            table,
4365            BTreeMap::from([
4366                (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4367                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4368            ])
4369        );
4370
4371        // Duplicate `update_by_keys`.
4372        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4373            table.clone(),
4374            uniqueness_violation,
4375            uniqueness_violation,
4376        )
4377        .unwrap();
4378        let n = table_txn
4379            .update_by_keys(
4380                [
4381                    (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4382                    (42i64.to_le_bytes().to_vec(), "v7".to_string()),
4383                ],
4384                0,
4385            )
4386            .unwrap();
4387        assert_eq!(n, Diff::from(2));
4388        let pending = table_txn.pending::<Vec<u8>, String>();
4389        assert!(pending.is_empty());
4390        commit(&mut table, pending);
4391        assert_eq!(
4392            table,
4393            BTreeMap::from([
4394                (1i64.to_le_bytes().to_vec(), "v9".to_string()),
4395                (42i64.to_le_bytes().to_vec(), "v7".to_string())
4396            ])
4397        );
4398
4399        // Test `delete_by_key`
4400        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4401            table.clone(),
4402            uniqueness_violation,
4403            uniqueness_violation,
4404        )
4405        .unwrap();
4406        let prev = table_txn.delete_by_key(1i64.to_le_bytes().to_vec(), 0);
4407        assert_eq!(prev, Some("v9".to_string()));
4408        let prev = table_txn.delete_by_key(5i64.to_le_bytes().to_vec(), 1);
4409        assert_none!(prev);
4410        let prev = table_txn.delete_by_key(1i64.to_le_bytes().to_vec(), 2);
4411        assert_none!(prev);
4412        let pending = table_txn.pending();
4413        assert_eq!(
4414            pending,
4415            vec![(
4416                1i64.to_le_bytes().to_vec(),
4417                "v9".to_string(),
4418                Diff::MINUS_ONE
4419            ),]
4420        );
4421        commit(&mut table, pending);
4422        assert_eq!(
4423            table,
4424            BTreeMap::from([(42i64.to_le_bytes().to_vec(), "v7".to_string())])
4425        );
4426
4427        // Test `delete_by_keys`
4428        let mut table_txn = TableTransaction::new_with_uniqueness_fn(
4429            table.clone(),
4430            uniqueness_violation,
4431            uniqueness_violation,
4432        )
4433        .unwrap();
4434        let prevs = table_txn.delete_by_keys(
4435            [42i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4436            0,
4437        );
4438        assert_eq!(
4439            prevs,
4440            vec![(42i64.to_le_bytes().to_vec(), "v7".to_string())]
4441        );
4442        let prevs = table_txn.delete_by_keys(
4443            [42i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4444            1,
4445        );
4446        assert_eq!(prevs, vec![]);
4447        let prevs = table_txn.delete_by_keys(
4448            [10i64.to_le_bytes().to_vec(), 55i64.to_le_bytes().to_vec()],
4449            2,
4450        );
4451        assert_eq!(prevs, vec![]);
4452        let pending = table_txn.pending();
4453        assert_eq!(
4454            pending,
4455            vec![(
4456                42i64.to_le_bytes().to_vec(),
4457                "v7".to_string(),
4458                Diff::MINUS_ONE
4459            ),]
4460        );
4461        commit(&mut table, pending);
4462        assert_eq!(table, BTreeMap::new());
4463    }
4464
4465    #[mz_ore::test(tokio::test)]
4466    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4467    async fn test_savepoint() {
4468        const VERSION: Version = Version::new(26, 0, 0);
4469        let mut persist_cache = PersistClientCache::new_no_metrics();
4470        persist_cache.cfg.build_version = VERSION;
4471        let persist_client = persist_cache
4472            .open(PersistLocation::new_in_mem())
4473            .await
4474            .unwrap();
4475        let state_builder = TestCatalogStateBuilder::new(persist_client)
4476            .with_default_deploy_generation()
4477            .with_version(VERSION);
4478
4479        // Initialize catalog.
4480        let _ = state_builder
4481            .clone()
4482            .unwrap_build()
4483            .await
4484            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4485            .await
4486            .unwrap();
4487        let mut savepoint_state = state_builder
4488            .unwrap_build()
4489            .await
4490            .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args())
4491            .await
4492            .unwrap();
4493
4494        let initial_snapshot = savepoint_state.sync_to_current_updates().await.unwrap();
4495        assert!(!initial_snapshot.is_empty());
4496
4497        let db_name = "db";
4498        let db_owner = RoleId::User(42);
4499        let db_privileges = Vec::new();
4500        let mut txn = savepoint_state.transaction().await.unwrap();
4501        let (db_id, db_oid) = txn
4502            .insert_user_database(db_name, db_owner, db_privileges.clone(), &HashSet::new())
4503            .unwrap();
4504        let commit_ts = txn.upper();
4505        txn.commit_internal(commit_ts).await.unwrap();
4506        let updates = savepoint_state.sync_to_current_updates().await.unwrap();
4507        let update = updates.into_element();
4508
4509        assert_eq!(update.diff, StateDiff::Addition);
4510
4511        let db = match update.kind {
4512            memory::objects::StateUpdateKind::Database(db) => db,
4513            update => panic!("unexpected update: {update:?}"),
4514        };
4515
4516        assert_eq!(db_id, db.id);
4517        assert_eq!(db_oid, db.oid);
4518        assert_eq!(db_name, db.name);
4519        assert_eq!(db_owner, db.owner_id);
4520        assert_eq!(db_privileges, db.privileges);
4521    }
4522
4523    #[mz_ore::test(tokio::test)]
4524    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4525    async fn test_dry_run_transaction_rejects_internal_commit() {
4526        const VERSION: Version = Version::new(26, 0, 0);
4527        let mut persist_cache = PersistClientCache::new_no_metrics();
4528        persist_cache.cfg.build_version = VERSION;
4529        let persist_client = persist_cache
4530            .open(PersistLocation::new_in_mem())
4531            .await
4532            .unwrap();
4533        let mut state = TestCatalogStateBuilder::new(persist_client)
4534            .with_default_deploy_generation()
4535            .with_version(VERSION)
4536            .unwrap_build()
4537            .await
4538            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4539            .await
4540            .unwrap();
4541        let _ = state.sync_to_current_updates().await.unwrap();
4542
4543        let initial_id = state.get_next_id(USER_ITEM_ALLOC_KEY).await.unwrap();
4544        let initial_upper = state.current_upper().await;
4545        let snapshot = state.snapshot().await.unwrap();
4546        let mut dry_run = state.transaction_from_snapshot(snapshot).unwrap();
4547        let ids = dry_run
4548            .transaction_mut()
4549            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), 1)
4550            .unwrap();
4551        assert_eq!(ids, vec![initial_id]);
4552
4553        let transaction = dry_run.transaction;
4554        let err = transaction
4555            .commit_internal(initial_upper)
4556            .await
4557            .unwrap_err();
4558        assert!(matches!(
4559            err,
4560            CatalogError::Durable(DurableCatalogError::DryRunTransaction)
4561        ));
4562        assert_eq!(state.current_upper().await, initial_upper);
4563        assert_eq!(
4564            state.get_next_id(USER_ITEM_ALLOC_KEY).await.unwrap(),
4565            initial_id
4566        );
4567    }
4568
4569    #[mz_ore::test(tokio::test)]
4570    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4571    async fn test_dry_run_transaction_rejects_into_parts_escape() {
4572        const VERSION: Version = Version::new(26, 0, 0);
4573        let mut persist_cache = PersistClientCache::new_no_metrics();
4574        persist_cache.cfg.build_version = VERSION;
4575        let persist_client = persist_cache
4576            .open(PersistLocation::new_in_mem())
4577            .await
4578            .unwrap();
4579        let mut dry_run_state = TestCatalogStateBuilder::new(persist_client.clone())
4580            .with_default_deploy_generation()
4581            .with_version(VERSION)
4582            .unwrap_build()
4583            .await
4584            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4585            .await
4586            .unwrap();
4587        let mut replacement_state = TestCatalogStateBuilder::new(persist_client)
4588            .with_default_deploy_generation()
4589            .with_version(VERSION)
4590            .unwrap_build()
4591            .await
4592            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4593            .await
4594            .unwrap();
4595        let _ = dry_run_state.sync_to_current_updates().await.unwrap();
4596        let _ = replacement_state.sync_to_current_updates().await.unwrap();
4597
4598        let initial_id = dry_run_state
4599            .get_next_id(USER_ITEM_ALLOC_KEY)
4600            .await
4601            .unwrap();
4602        let initial_upper = dry_run_state.current_upper().await;
4603        let snapshot = dry_run_state.snapshot().await.unwrap();
4604        let mut dry_run = dry_run_state.transaction_from_snapshot(snapshot).unwrap();
4605        let ids = dry_run
4606            .transaction_mut()
4607            .get_and_increment_id_by(USER_ITEM_ALLOC_KEY.to_string(), 1)
4608            .unwrap();
4609        assert_eq!(ids, vec![initial_id]);
4610
4611        let replacement = replacement_state.transaction().await.unwrap();
4612        let escaped = std::mem::replace(dry_run.transaction_mut(), replacement);
4613        drop(dry_run);
4614
4615        let err = match escaped.into_parts() {
4616            Ok(_) => panic!("dry-run transaction decomposed into committable parts"),
4617            Err(err) => err,
4618        };
4619        assert!(matches!(
4620            err,
4621            CatalogError::Durable(DurableCatalogError::DryRunTransaction)
4622        ));
4623        assert_eq!(dry_run_state.current_upper().await, initial_upper);
4624        assert_eq!(
4625            dry_run_state
4626                .get_next_id(USER_ITEM_ALLOC_KEY)
4627                .await
4628                .unwrap(),
4629            initial_id
4630        );
4631    }
4632
4633    /// Regression test for DB-147: inserting a replica with an explicit id must not consume the
4634    /// `IdAlloc` counter, and the durable allocator must advance independently so a later
4635    /// allocation never collides with an explicitly inserted id.
4636    #[mz_ore::test(tokio::test)]
4637    #[cfg_attr(miri, ignore)] //  unsupported operation: can't call foreign function `TLS_client_method` on OS `linux`
4638    async fn test_insert_replica_with_id_does_not_consume_allocator() {
4639        const VERSION: Version = Version::new(26, 0, 0);
4640        let mut persist_cache = PersistClientCache::new_no_metrics();
4641        persist_cache.cfg.build_version = VERSION;
4642        let persist_client = persist_cache
4643            .open(PersistLocation::new_in_mem())
4644            .await
4645            .unwrap();
4646        let state_builder = TestCatalogStateBuilder::new(persist_client)
4647            .with_default_deploy_generation()
4648            .with_version(VERSION);
4649        let mut state = state_builder
4650            .unwrap_build()
4651            .await
4652            .open(SYSTEM_TIME().into(), &test_bootstrap_args())
4653            .await
4654            .unwrap();
4655
4656        // The cluster does not need to exist: `insert_cluster_replica_with_id` only writes a
4657        // `cluster_replicas` row and does not validate the referenced cluster.
4658        let cluster_id = ClusterId::User(1);
4659        let owner_id = RoleId::User(1);
4660        let config = ReplicaConfig {
4661            location: ReplicaLocation::Managed {
4662                size: "1".to_string(),
4663                availability_zones: Vec::new(),
4664                internal: false,
4665                billed_as: None,
4666                pending: false,
4667            },
4668            logging: ReplicaLogging {
4669                log_logging: false,
4670                interval: Some(Duration::from_secs(1)),
4671            },
4672            arrangement_compression: false,
4673        };
4674
4675        // Step 1: allocate one user replica id out-of-band via the durable allocator.
4676        let commit_ts = state.current_upper().await;
4677        let a = state
4678            .allocate_user_replica_ids(1, commit_ts)
4679            .await
4680            .unwrap()
4681            .into_element();
4682        assert!(a.is_user());
4683
4684        let initial_updates = state.sync_to_current_updates().await.unwrap();
4685        assert!(!initial_updates.is_empty());
4686
4687        // Step 2: insert a replica with that explicit id and commit.
4688        let mut txn = state.transaction().await.unwrap();
4689        txn.insert_cluster_replica_with_id(cluster_id, a, "explicit", config, owner_id)
4690            .unwrap();
4691        let commit_ts = txn.upper();
4692        txn.commit_internal(commit_ts).await.unwrap();
4693        let _ = state.sync_to_current_updates().await.unwrap();
4694
4695        // Step 3: allocate one more user replica id.
4696        let commit_ts = state.current_upper().await;
4697        let b = state
4698            .allocate_user_replica_ids(1, commit_ts)
4699            .await
4700            .unwrap()
4701            .into_element();
4702
4703        // Step 4: the explicit insert consumed zero ids, so the allocator's next value is the
4704        // direct successor of the first allocation.
4705        assert_eq!(b.inner_id(), a.inner_id() + 1);
4706
4707        // Step 5: the explicitly inserted replica is present in the committed state.
4708        let txn = state.transaction().await.unwrap();
4709        let found = txn
4710            .get_cluster_replicas()
4711            .any(|replica| replica.replica_id == a);
4712        assert!(found, "explicitly inserted replica {a} not found");
4713    }
4714
4715    #[mz_ore::test]
4716    fn test_allocate_introspection_source_index_id() {
4717        let cluster_variant: u8 = 0b0000_0001;
4718        let cluster_id_inner: u64 =
4719            0b0000_0000_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010;
4720        let timely_messages_received_log_variant: u8 = 0b0000_1000;
4721
4722        let cluster_id = ClusterId::System(cluster_id_inner);
4723        let log_variant = LogVariant::Timely(TimelyLog::MessagesReceived);
4724
4725        let introspection_source_index_id: u64 =
4726            0b0000_0001_1100_0101_1100_0011_1010_1101_0000_1011_1111_1001_0110_1010_0000_1000;
4727
4728        // Sanity check that `introspection_source_index_id` contains `cluster_variant`.
4729        {
4730            let mut cluster_variant_mask = 0xFF << 56;
4731            cluster_variant_mask &= introspection_source_index_id;
4732            cluster_variant_mask >>= 56;
4733            assert_eq!(cluster_variant_mask, u64::from(cluster_variant));
4734        }
4735
4736        // Sanity check that `introspection_source_index_id` contains `cluster_id_inner`.
4737        {
4738            let mut cluster_id_inner_mask = 0xFFFF_FFFF_FFFF << 8;
4739            cluster_id_inner_mask &= introspection_source_index_id;
4740            cluster_id_inner_mask >>= 8;
4741            assert_eq!(cluster_id_inner_mask, cluster_id_inner);
4742        }
4743
4744        // Sanity check that `introspection_source_index_id` contains `timely_messages_received_log_variant`.
4745        {
4746            let mut log_variant_mask = 0xFF;
4747            log_variant_mask &= introspection_source_index_id;
4748            assert_eq!(
4749                log_variant_mask,
4750                u64::from(timely_messages_received_log_variant)
4751            );
4752        }
4753
4754        let (catalog_item_id, global_id) =
4755            Transaction::allocate_introspection_source_index_id(&cluster_id, log_variant);
4756
4757        assert_eq!(
4758            catalog_item_id,
4759            CatalogItemId::IntrospectionSourceIndex(introspection_source_index_id)
4760        );
4761        assert_eq!(
4762            global_id,
4763            GlobalId::IntrospectionSourceIndex(introspection_source_index_id)
4764        );
4765    }
4766}