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