1use std::fmt::Debug;
37use std::sync::LazyLock;
38
39use mz_ore::collections::HashSet;
40use mz_proto::{ProtoType, RustType, TryFromProtoError};
41use mz_repr::Diff;
42use mz_repr::adt::jsonb::Jsonb;
43use mz_storage_types::StorageDiff;
44use mz_storage_types::sources::SourceData;
45#[cfg(test)]
46use proptest_derive::Arbitrary;
47use tracing::error;
48
49use crate::durable::debug::CollectionType;
50use crate::durable::objects::serialization::proto;
51use crate::durable::objects::{DurableType, FenceToken};
52use crate::durable::persist::Timestamp;
53use crate::durable::transaction::TransactionBatch;
54use crate::durable::{DurableCatalogError, Epoch};
55use crate::memory;
56
57pub trait IntoStateUpdateKindJson:
59 Into<StateUpdateKindJson> + PartialEq + Eq + PartialOrd + Ord + Debug + Clone
60{
61 type Error: Debug;
62
63 fn try_from(raw: StateUpdateKindJson) -> Result<Self, Self::Error>;
64}
65impl<
66 T: Into<StateUpdateKindJson>
67 + TryFrom<StateUpdateKindJson>
68 + PartialEq
69 + Eq
70 + PartialOrd
71 + Ord
72 + Debug
73 + Clone,
74> IntoStateUpdateKindJson for T
75where
76 T::Error: Debug,
77{
78 type Error = T::Error;
79
80 fn try_from(raw: StateUpdateKindJson) -> Result<Self, Self::Error> {
81 <T as TryFrom<StateUpdateKindJson>>::try_from(raw)
82 }
83}
84
85pub(crate) trait TryIntoStateUpdateKind: IntoStateUpdateKindJson {
87 type Error: Debug;
88
89 fn try_into(self) -> Result<StateUpdateKind, <Self as TryIntoStateUpdateKind>::Error>;
90}
91impl<T: IntoStateUpdateKindJson + TryInto<StateUpdateKind>> TryIntoStateUpdateKind for T
92where
93 <T as TryInto<StateUpdateKind>>::Error: Debug,
94{
95 type Error = <T as TryInto<StateUpdateKind>>::Error;
96
97 fn try_into(self) -> Result<StateUpdateKind, <T as TryInto<StateUpdateKind>>::Error> {
98 <T as TryInto<StateUpdateKind>>::try_into(self)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
104pub struct StateUpdate<T: IntoStateUpdateKindJson = StateUpdateKind> {
105 pub kind: T,
107 pub ts: Timestamp,
109 pub diff: Diff,
111}
112
113impl StateUpdate {
114 pub(crate) fn from_txn_batch_ts(
116 txn_batch: TransactionBatch,
117 ts: Timestamp,
118 ) -> impl Iterator<Item = StateUpdate> {
119 Self::from_txn_batch(txn_batch).map(move |(kind, diff)| StateUpdate { kind, ts, diff })
120 }
121
122 pub(crate) fn from_txn_batch(
124 txn_batch: TransactionBatch,
125 ) -> impl Iterator<Item = (StateUpdateKind, Diff)> {
126 fn from_batch<K, V>(
127 batch: Vec<(K, V, Diff)>,
128 kind: fn(K, V) -> StateUpdateKind,
129 ) -> impl Iterator<Item = (StateUpdateKind, Diff)> {
130 batch
131 .into_iter()
132 .map(move |(k, v, diff)| (kind(k, v), diff))
133 }
134 let TransactionBatch {
135 databases,
136 schemas,
137 items,
138 comments,
139 roles,
140 role_auth,
141 clusters,
142 cluster_replicas,
143 network_policies,
144 introspection_sources,
145 id_allocator,
146 configs,
147 settings,
148 source_references,
149 system_gid_mapping,
150 system_configurations,
151 cluster_system_configurations,
152 replica_system_configurations,
153 default_privileges,
154 system_privileges,
155 storage_collection_metadata,
156 unfinalized_shards,
157 txn_wal_shard,
158 audit_log_updates,
159 upper: _,
160 ..
161 } = txn_batch;
162 let databases = from_batch(databases, StateUpdateKind::Database);
163 let schemas = from_batch(schemas, StateUpdateKind::Schema);
164 let items = from_batch(items, StateUpdateKind::Item);
165 let comments = from_batch(comments, StateUpdateKind::Comment);
166 let roles = from_batch(roles, StateUpdateKind::Role);
167 let role_auth = from_batch(role_auth, StateUpdateKind::RoleAuth);
168 let clusters = from_batch(clusters, StateUpdateKind::Cluster);
169 let cluster_replicas = from_batch(cluster_replicas, StateUpdateKind::ClusterReplica);
170 let network_policies = from_batch(network_policies, StateUpdateKind::NetworkPolicy);
171 let introspection_sources = from_batch(
172 introspection_sources,
173 StateUpdateKind::IntrospectionSourceIndex,
174 );
175 let id_allocators = from_batch(id_allocator, StateUpdateKind::IdAllocator);
176 let configs = from_batch(configs, StateUpdateKind::Config);
177 let settings = from_batch(settings, StateUpdateKind::Setting);
178 let system_object_mappings =
179 from_batch(system_gid_mapping, StateUpdateKind::SystemObjectMapping);
180 let system_configurations =
181 from_batch(system_configurations, StateUpdateKind::SystemConfiguration);
182 let cluster_system_configurations = from_batch(
183 cluster_system_configurations,
184 StateUpdateKind::ClusterSystemConfiguration,
185 );
186 let replica_system_configurations = from_batch(
187 replica_system_configurations,
188 StateUpdateKind::ReplicaSystemConfiguration,
189 );
190 let default_privileges = from_batch(default_privileges, StateUpdateKind::DefaultPrivilege);
191 let source_references = from_batch(source_references, StateUpdateKind::SourceReferences);
192 let system_privileges = from_batch(system_privileges, StateUpdateKind::SystemPrivilege);
193 let storage_collection_metadata = from_batch(
194 storage_collection_metadata,
195 StateUpdateKind::StorageCollectionMetadata,
196 );
197 let unfinalized_shards = from_batch(unfinalized_shards, StateUpdateKind::UnfinalizedShard);
198 let txn_wal_shard = from_batch(txn_wal_shard, StateUpdateKind::TxnWalShard);
199 let audit_logs = from_batch(audit_log_updates, StateUpdateKind::AuditLog);
200
201 databases
202 .chain(schemas)
203 .chain(items)
204 .chain(comments)
205 .chain(roles)
206 .chain(role_auth)
207 .chain(clusters)
208 .chain(cluster_replicas)
209 .chain(network_policies)
210 .chain(introspection_sources)
211 .chain(id_allocators)
212 .chain(configs)
213 .chain(settings)
214 .chain(source_references)
215 .chain(system_object_mappings)
216 .chain(system_configurations)
217 .chain(cluster_system_configurations)
218 .chain(replica_system_configurations)
219 .chain(default_privileges)
220 .chain(system_privileges)
221 .chain(storage_collection_metadata)
222 .chain(unfinalized_shards)
223 .chain(txn_wal_shard)
224 .chain(audit_logs)
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
233#[cfg_attr(test, derive(Arbitrary))]
234pub enum StateUpdateKind {
235 AuditLog(proto::AuditLogKey, ()),
236 Cluster(proto::ClusterKey, proto::ClusterValue),
237 ClusterReplica(proto::ClusterReplicaKey, proto::ClusterReplicaValue),
238 Comment(proto::CommentKey, proto::CommentValue),
239 Config(proto::ConfigKey, proto::ConfigValue),
240 Database(proto::DatabaseKey, proto::DatabaseValue),
241 DefaultPrivilege(proto::DefaultPrivilegesKey, proto::DefaultPrivilegesValue),
242 FenceToken(FenceToken),
243 IdAllocator(proto::IdAllocKey, proto::IdAllocValue),
244 IntrospectionSourceIndex(
245 proto::ClusterIntrospectionSourceIndexKey,
246 proto::ClusterIntrospectionSourceIndexValue,
247 ),
248 Item(proto::ItemKey, proto::ItemValue),
249 NetworkPolicy(proto::NetworkPolicyKey, proto::NetworkPolicyValue),
250 Role(proto::RoleKey, proto::RoleValue),
251 RoleAuth(proto::RoleAuthKey, proto::RoleAuthValue),
252 Schema(proto::SchemaKey, proto::SchemaValue),
253 Setting(proto::SettingKey, proto::SettingValue),
254 SourceReferences(proto::SourceReferencesKey, proto::SourceReferencesValue),
255 SystemConfiguration(
256 proto::ServerConfigurationKey,
257 proto::ServerConfigurationValue,
258 ),
259 ClusterSystemConfiguration(
260 proto::ClusterSystemConfigurationKey,
261 proto::ClusterSystemConfigurationValue,
262 ),
263 ReplicaSystemConfiguration(
264 proto::ReplicaSystemConfigurationKey,
265 proto::ReplicaSystemConfigurationValue,
266 ),
267 SystemObjectMapping(proto::GidMappingKey, proto::GidMappingValue),
268 SystemPrivilege(proto::SystemPrivilegesKey, proto::SystemPrivilegesValue),
269 StorageCollectionMetadata(
270 proto::StorageCollectionMetadataKey,
271 proto::StorageCollectionMetadataValue,
272 ),
273 UnfinalizedShard(proto::UnfinalizedShardKey, ()),
274 TxnWalShard((), proto::TxnWalShardValue),
275}
276
277impl StateUpdateKind {
278 pub(crate) fn collection_type(&self) -> Option<CollectionType> {
279 match self {
280 StateUpdateKind::AuditLog(_, _) => Some(CollectionType::AuditLog),
281 StateUpdateKind::Cluster(_, _) => Some(CollectionType::ComputeInstance),
282 StateUpdateKind::ClusterReplica(_, _) => Some(CollectionType::ComputeReplicas),
283 StateUpdateKind::Comment(_, _) => Some(CollectionType::Comments),
284 StateUpdateKind::Config(_, _) => Some(CollectionType::Config),
285 StateUpdateKind::Database(_, _) => Some(CollectionType::Database),
286 StateUpdateKind::DefaultPrivilege(_, _) => Some(CollectionType::DefaultPrivileges),
287 StateUpdateKind::FenceToken(_) => None,
288 StateUpdateKind::IdAllocator(_, _) => Some(CollectionType::IdAlloc),
289 StateUpdateKind::IntrospectionSourceIndex(_, _) => {
290 Some(CollectionType::ComputeIntrospectionSourceIndex)
291 }
292 StateUpdateKind::Item(_, _) => Some(CollectionType::Item),
293 StateUpdateKind::NetworkPolicy(_, _) => Some(CollectionType::NetworkPolicy),
294 StateUpdateKind::Role(_, _) => Some(CollectionType::Role),
295 StateUpdateKind::RoleAuth(_, _) => Some(CollectionType::RoleAuth),
296 StateUpdateKind::Schema(_, _) => Some(CollectionType::Schema),
297 StateUpdateKind::Setting(_, _) => Some(CollectionType::Setting),
298 StateUpdateKind::SourceReferences(_, _) => Some(CollectionType::SourceReferences),
299 StateUpdateKind::SystemConfiguration(_, _) => Some(CollectionType::SystemConfiguration),
300 StateUpdateKind::ClusterSystemConfiguration(_, _) => {
301 Some(CollectionType::ClusterSystemConfiguration)
302 }
303 StateUpdateKind::ReplicaSystemConfiguration(_, _) => {
304 Some(CollectionType::ReplicaSystemConfiguration)
305 }
306 StateUpdateKind::SystemObjectMapping(_, _) => Some(CollectionType::SystemGidMapping),
307 StateUpdateKind::SystemPrivilege(_, _) => Some(CollectionType::SystemPrivileges),
308 StateUpdateKind::StorageCollectionMetadata(_, _) => {
309 Some(CollectionType::StorageCollectionMetadata)
310 }
311 StateUpdateKind::UnfinalizedShard(_, _) => Some(CollectionType::UnfinalizedShard),
312 StateUpdateKind::TxnWalShard(_, _) => Some(CollectionType::TxnWalShard),
313 }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
319pub struct StateUpdateKindJson(Jsonb);
320
321impl StateUpdateKindJson {
322 pub(crate) fn from_serde<S: serde::Serialize>(s: S) -> Self {
323 let serde_value = serde_json::to_value(s).expect("valid json");
324 let row = Jsonb::from_serde_json(serde_value).expect("valid json");
325 StateUpdateKindJson(row)
326 }
327
328 pub(crate) fn to_serde<D: serde::de::DeserializeOwned>(&self) -> D {
329 self.try_to_serde().expect("jsonb should roundtrip")
330 }
331
332 pub(crate) fn try_to_serde<D: serde::de::DeserializeOwned>(
333 &self,
334 ) -> Result<D, serde_json::error::Error> {
335 let serde_value = self.0.as_ref().to_serde_json();
336 serde_json::from_value::<D>(serde_value)
337 }
338
339 fn kind(&self) -> &str {
340 let row = self.0.row();
341 let mut iter = row.unpack_first().unwrap_map().iter();
342 let datum = iter
343 .find_map(|(field, datum)| if field == "kind" { Some(datum) } else { None })
344 .expect("kind field must exist");
345 datum.unwrap_str()
346 }
347
348 pub(crate) fn is_always_deserializable(&self) -> bool {
350 static DESERIALIZABLE_KINDS: LazyLock<HashSet<String>> = LazyLock::new(|| {
353 [
354 StateUpdateKind::FenceToken(FenceToken {
355 deploy_generation: 1,
356 epoch: Epoch::new(1).expect("non-zero"),
357 }),
358 StateUpdateKind::Config(
359 proto::ConfigKey { key: String::new() },
360 proto::ConfigValue { value: 1 },
361 ),
362 StateUpdateKind::Setting(
363 proto::SettingKey {
364 name: String::new(),
365 },
366 proto::SettingValue {
367 value: String::new(),
368 },
369 ),
370 StateUpdateKind::AuditLog(
371 proto::AuditLogKey {
372 event: proto::AuditLogEvent::V1(proto::AuditLogEventV1 {
373 id: 1,
374 event_type: proto::audit_log_event_v1::EventType::Create,
375 object_type: proto::audit_log_event_v1::ObjectType::Cluster,
376 user: None,
377 occurred_at: proto::EpochMillis { millis: 1 },
378 details: proto::audit_log_event_v1::Details::ResetAllV1(
379 proto::Empty {},
380 ),
381 }),
382 },
383 (),
384 ),
385 ]
386 .into_iter()
387 .map(|kind| {
388 let json_kind: StateUpdateKindJson = kind.into();
389 json_kind.kind().to_string()
390 })
391 .collect()
392 });
393 DESERIALIZABLE_KINDS.contains(self.kind())
394 }
395
396 pub(crate) fn is_audit_log(&self) -> bool {
398 static AUDIT_LOG_KIND: LazyLock<String> = LazyLock::new(|| {
401 let audit_log = StateUpdateKind::AuditLog(
402 proto::AuditLogKey {
403 event: proto::AuditLogEvent::V1(proto::AuditLogEventV1 {
404 id: 1,
405 event_type: proto::audit_log_event_v1::EventType::Create,
406 object_type: proto::audit_log_event_v1::ObjectType::Cluster,
407 user: None,
408 occurred_at: proto::EpochMillis { millis: 1 },
409 details: proto::audit_log_event_v1::Details::ResetAllV1(proto::Empty {}),
410 }),
411 },
412 (),
413 );
414 let json_kind: StateUpdateKindJson = audit_log.into();
415 json_kind.kind().to_string()
416 });
417 &*AUDIT_LOG_KIND == self.kind()
418 }
419}
420
421type PersistStateUpdate = ((SourceData, ()), Timestamp, StorageDiff);
423
424impl TryFrom<&StateUpdate<StateUpdateKind>> for Option<memory::objects::StateUpdate> {
425 type Error = DurableCatalogError;
426
427 fn try_from(
428 StateUpdate { kind, ts, diff }: &StateUpdate<StateUpdateKind>,
429 ) -> Result<Self, Self::Error> {
430 let kind: Option<memory::objects::StateUpdateKind> = TryInto::try_into(kind)?;
431 let update = kind.map(|kind| memory::objects::StateUpdate {
432 kind,
433 ts: ts.clone(),
434 diff: diff.clone().try_into().expect("invalid diff"),
435 });
436 Ok(update)
437 }
438}
439
440impl TryFrom<&StateUpdateKind> for Option<memory::objects::StateUpdateKind> {
441 type Error = DurableCatalogError;
442
443 fn try_from(kind: &StateUpdateKind) -> Result<Self, Self::Error> {
444 fn into_durable<PK, PV, T>(key: &PK, value: &PV) -> Result<T, DurableCatalogError>
445 where
446 PK: ProtoType<T::Key> + Clone,
447 PV: ProtoType<T::Value> + Clone,
448 T: DurableType,
449 {
450 let key = key.clone().into_rust()?;
451 let value = value.clone().into_rust()?;
452 Ok(T::from_key_value(key, value))
453 }
454
455 Ok(match kind {
456 StateUpdateKind::AuditLog(key, value) => {
457 let audit_log = into_durable(key, value)?;
458 Some(memory::objects::StateUpdateKind::AuditLog(audit_log))
459 }
460 StateUpdateKind::Cluster(key, value) => {
461 let cluster = into_durable(key, value)?;
462 Some(memory::objects::StateUpdateKind::Cluster(cluster))
463 }
464 StateUpdateKind::ClusterReplica(key, value) => {
465 let cluster_replica = into_durable(key, value)?;
466 Some(memory::objects::StateUpdateKind::ClusterReplica(
467 cluster_replica,
468 ))
469 }
470 StateUpdateKind::Comment(key, value) => {
471 let comment = into_durable(key, value)?;
472 Some(memory::objects::StateUpdateKind::Comment(comment))
473 }
474 StateUpdateKind::Database(key, value) => {
475 let database = into_durable(key, value)?;
476 Some(memory::objects::StateUpdateKind::Database(database))
477 }
478 StateUpdateKind::DefaultPrivilege(key, value) => {
479 let default_privilege = into_durable(key, value)?;
480 Some(memory::objects::StateUpdateKind::DefaultPrivilege(
481 default_privilege,
482 ))
483 }
484 StateUpdateKind::Item(key, value) => {
485 let item = into_durable(key, value)?;
486 Some(memory::objects::StateUpdateKind::Item(item))
487 }
488 StateUpdateKind::IntrospectionSourceIndex(key, value) => {
489 let introspection_source_index = into_durable(key, value)?;
490 Some(memory::objects::StateUpdateKind::IntrospectionSourceIndex(
491 introspection_source_index,
492 ))
493 }
494 StateUpdateKind::NetworkPolicy(key, value) => {
495 let policy = into_durable(key, value)?;
496 Some(memory::objects::StateUpdateKind::NetworkPolicy(policy))
497 }
498 StateUpdateKind::Role(key, value) => {
499 let role = into_durable(key, value)?;
500 Some(memory::objects::StateUpdateKind::Role(role))
501 }
502 StateUpdateKind::RoleAuth(key, value) => {
503 let role_auth = into_durable(key, value)?;
504 Some(memory::objects::StateUpdateKind::RoleAuth(role_auth))
505 }
506 StateUpdateKind::Schema(key, value) => {
507 let schema = into_durable(key, value)?;
508 Some(memory::objects::StateUpdateKind::Schema(schema))
509 }
510 StateUpdateKind::SourceReferences(key, value) => {
511 let source_references = into_durable(key, value)?;
512 Some(memory::objects::StateUpdateKind::SourceReferences(
513 source_references,
514 ))
515 }
516 StateUpdateKind::StorageCollectionMetadata(key, value) => {
517 let storage_collection_metadata = into_durable(key, value)?;
518 Some(memory::objects::StateUpdateKind::StorageCollectionMetadata(
519 storage_collection_metadata,
520 ))
521 }
522 StateUpdateKind::SystemConfiguration(key, value) => {
523 let system_configuration = into_durable(key, value)?;
524 Some(memory::objects::StateUpdateKind::SystemConfiguration(
525 system_configuration,
526 ))
527 }
528 StateUpdateKind::ClusterSystemConfiguration(key, value) => {
529 let cluster_system_configuration = into_durable(key, value)?;
530 Some(
531 memory::objects::StateUpdateKind::ClusterSystemConfiguration(
532 cluster_system_configuration,
533 ),
534 )
535 }
536 StateUpdateKind::ReplicaSystemConfiguration(key, value) => {
537 let replica_system_configuration = into_durable(key, value)?;
538 Some(
539 memory::objects::StateUpdateKind::ReplicaSystemConfiguration(
540 replica_system_configuration,
541 ),
542 )
543 }
544 StateUpdateKind::SystemObjectMapping(key, value) => {
545 let system_object_mapping = into_durable(key, value)?;
546 Some(memory::objects::StateUpdateKind::SystemObjectMapping(
547 system_object_mapping,
548 ))
549 }
550 StateUpdateKind::SystemPrivilege(key, value) => {
551 let system_privilege = into_durable(key, value)?;
552 Some(memory::objects::StateUpdateKind::SystemPrivilege(
553 system_privilege,
554 ))
555 }
556 StateUpdateKind::UnfinalizedShard(key, value) => {
557 let unfinalized_shard = into_durable(key, value)?;
558 Some(memory::objects::StateUpdateKind::UnfinalizedShard(
559 unfinalized_shard,
560 ))
561 }
562 StateUpdateKind::Config(_, _)
564 | StateUpdateKind::FenceToken(_)
565 | StateUpdateKind::IdAllocator(_, _)
566 | StateUpdateKind::Setting(_, _)
567 | StateUpdateKind::TxnWalShard(_, _) => None,
568 })
569 }
570}
571
572impl TryFrom<StateUpdate<StateUpdateKindJson>> for StateUpdate<StateUpdateKind> {
573 type Error = String;
574
575 fn try_from(update: StateUpdate<StateUpdateKindJson>) -> Result<Self, Self::Error> {
576 Ok(StateUpdate {
577 kind: TryInto::try_into(update.kind)?,
578 ts: update.ts,
579 diff: update.diff,
580 })
581 }
582}
583
584impl TryFrom<StateUpdateKindJson> for StateUpdateKind {
585 type Error = String;
586
587 fn try_from(value: StateUpdateKindJson) -> Result<Self, Self::Error> {
588 let kind: proto::StateUpdateKind = value.try_to_serde().map_err(|err| err.to_string())?;
589 StateUpdateKind::from_proto(kind).map_err(|err| err.to_string())
590 }
591}
592
593impl TryFrom<&StateUpdateKindJson> for StateUpdateKind {
594 type Error = String;
595
596 fn try_from(value: &StateUpdateKindJson) -> Result<Self, Self::Error> {
597 let kind: proto::StateUpdateKind = value.try_to_serde().map_err(|err| err.to_string())?;
598 StateUpdateKind::from_proto(kind).map_err(|err| err.to_string())
599 }
600}
601
602impl From<StateUpdateKind> for StateUpdateKindJson {
603 fn from(value: StateUpdateKind) -> Self {
604 let kind = value.into_proto_owned();
605 StateUpdateKindJson::from_serde(kind)
606 }
607}
608
609impl RustType<proto::StateUpdateKind> for StateUpdateKind {
613 fn into_proto(&self) -> proto::StateUpdateKind {
614 error!("unexpected clone of catalog data");
615 self.clone().into_proto_owned()
616 }
617
618 fn into_proto_owned(self) -> proto::StateUpdateKind {
619 match self {
620 StateUpdateKind::AuditLog(key, ()) => {
621 proto::StateUpdateKind::AuditLog(proto::AuditLog { key })
622 }
623 StateUpdateKind::Cluster(key, value) => {
624 proto::StateUpdateKind::Cluster(proto::Cluster { key, value })
625 }
626 StateUpdateKind::ClusterReplica(key, value) => {
627 proto::StateUpdateKind::ClusterReplica(proto::ClusterReplica { key, value })
628 }
629 StateUpdateKind::Comment(key, value) => {
630 proto::StateUpdateKind::Comment(proto::Comment { key, value })
631 }
632 StateUpdateKind::Config(key, value) => {
633 proto::StateUpdateKind::Config(proto::Config { key, value })
634 }
635 StateUpdateKind::Database(key, value) => {
636 proto::StateUpdateKind::Database(proto::Database { key, value })
637 }
638 StateUpdateKind::DefaultPrivilege(key, value) => {
639 proto::StateUpdateKind::DefaultPrivileges(proto::DefaultPrivileges { key, value })
640 }
641 StateUpdateKind::FenceToken(fence_token) => {
642 proto::StateUpdateKind::FenceToken(proto::FenceToken {
643 deploy_generation: fence_token.deploy_generation,
644 epoch: fence_token.epoch.get(),
645 })
646 }
647 StateUpdateKind::IdAllocator(key, value) => {
648 proto::StateUpdateKind::IdAlloc(proto::IdAlloc { key, value })
649 }
650 StateUpdateKind::IntrospectionSourceIndex(key, value) => {
651 proto::StateUpdateKind::ClusterIntrospectionSourceIndex(
652 proto::ClusterIntrospectionSourceIndex { key, value },
653 )
654 }
655 StateUpdateKind::Item(key, value) => {
656 proto::StateUpdateKind::Item(proto::Item { key, value })
657 }
658 StateUpdateKind::NetworkPolicy(key, value) => {
659 proto::StateUpdateKind::NetworkPolicy(proto::NetworkPolicy { key, value })
660 }
661 StateUpdateKind::Role(key, value) => {
662 proto::StateUpdateKind::Role(proto::Role { key, value })
663 }
664 StateUpdateKind::RoleAuth(key, value) => {
665 proto::StateUpdateKind::RoleAuth(proto::RoleAuth { key, value })
666 }
667 StateUpdateKind::Schema(key, value) => {
668 proto::StateUpdateKind::Schema(proto::Schema { key, value })
669 }
670 StateUpdateKind::Setting(key, value) => {
671 proto::StateUpdateKind::Setting(proto::Setting { key, value })
672 }
673 StateUpdateKind::SourceReferences(key, value) => {
674 proto::StateUpdateKind::SourceReferences(proto::SourceReferences { key, value })
675 }
676 StateUpdateKind::SystemConfiguration(key, value) => {
677 proto::StateUpdateKind::ServerConfiguration(proto::ServerConfiguration {
678 key,
679 value,
680 })
681 }
682 StateUpdateKind::ClusterSystemConfiguration(key, value) => {
683 proto::StateUpdateKind::ClusterSystemConfiguration(
684 proto::ClusterSystemConfiguration { key, value },
685 )
686 }
687 StateUpdateKind::ReplicaSystemConfiguration(key, value) => {
688 proto::StateUpdateKind::ReplicaSystemConfiguration(
689 proto::ReplicaSystemConfiguration { key, value },
690 )
691 }
692 StateUpdateKind::SystemObjectMapping(key, value) => {
693 proto::StateUpdateKind::GidMapping(proto::GidMapping { key, value })
694 }
695 StateUpdateKind::SystemPrivilege(key, value) => {
696 proto::StateUpdateKind::SystemPrivileges(proto::SystemPrivileges { key, value })
697 }
698 StateUpdateKind::StorageCollectionMetadata(key, value) => {
699 proto::StateUpdateKind::StorageCollectionMetadata(
700 proto::StorageCollectionMetadata { key, value },
701 )
702 }
703 StateUpdateKind::UnfinalizedShard(key, ()) => {
704 proto::StateUpdateKind::UnfinalizedShard(proto::UnfinalizedShard { key })
705 }
706 StateUpdateKind::TxnWalShard((), value) => {
707 proto::StateUpdateKind::TxnWalShard(proto::TxnWalShard { value })
708 }
709 }
710 }
711
712 fn from_proto(proto: proto::StateUpdateKind) -> Result<StateUpdateKind, TryFromProtoError> {
713 Ok(match proto {
714 proto::StateUpdateKind::AuditLog(proto::AuditLog { key }) => {
715 StateUpdateKind::AuditLog(key, ())
716 }
717 proto::StateUpdateKind::Cluster(proto::Cluster { key, value }) => {
718 StateUpdateKind::Cluster(key, value)
719 }
720 proto::StateUpdateKind::ClusterReplica(proto::ClusterReplica { key, value }) => {
721 StateUpdateKind::ClusterReplica(key, value)
722 }
723 proto::StateUpdateKind::Comment(proto::Comment { key, value }) => {
724 StateUpdateKind::Comment(key, value)
725 }
726 proto::StateUpdateKind::Config(proto::Config { key, value }) => {
727 StateUpdateKind::Config(key, value)
728 }
729 proto::StateUpdateKind::Database(proto::Database { key, value }) => {
730 StateUpdateKind::Database(key, value)
731 }
732 proto::StateUpdateKind::DefaultPrivileges(proto::DefaultPrivileges { key, value }) => {
733 StateUpdateKind::DefaultPrivilege(key, value)
734 }
735 proto::StateUpdateKind::FenceToken(proto::FenceToken {
736 deploy_generation,
737 epoch,
738 }) => StateUpdateKind::FenceToken(FenceToken {
739 deploy_generation,
740 epoch: Epoch::new(epoch).ok_or_else(|| {
741 TryFromProtoError::missing_field("state_update_kind::Epoch::epoch")
742 })?,
743 }),
744 proto::StateUpdateKind::IdAlloc(proto::IdAlloc { key, value }) => {
745 StateUpdateKind::IdAllocator(key, value)
746 }
747 proto::StateUpdateKind::ClusterIntrospectionSourceIndex(
748 proto::ClusterIntrospectionSourceIndex { key, value },
749 ) => StateUpdateKind::IntrospectionSourceIndex(key, value),
750 proto::StateUpdateKind::Item(proto::Item { key, value }) => {
751 StateUpdateKind::Item(key, value)
752 }
753 proto::StateUpdateKind::Role(proto::Role { key, value }) => {
754 StateUpdateKind::Role(key, value)
755 }
756 proto::StateUpdateKind::RoleAuth(proto::RoleAuth { key, value }) => {
757 StateUpdateKind::RoleAuth(key, value)
758 }
759 proto::StateUpdateKind::Schema(proto::Schema { key, value }) => {
760 StateUpdateKind::Schema(key, value)
761 }
762 proto::StateUpdateKind::Setting(proto::Setting { key, value }) => {
763 StateUpdateKind::Setting(key, value)
764 }
765 proto::StateUpdateKind::ServerConfiguration(proto::ServerConfiguration {
766 key,
767 value,
768 }) => StateUpdateKind::SystemConfiguration(key, value),
769 proto::StateUpdateKind::ClusterSystemConfiguration(
770 proto::ClusterSystemConfiguration { key, value },
771 ) => StateUpdateKind::ClusterSystemConfiguration(key, value),
772 proto::StateUpdateKind::ReplicaSystemConfiguration(
773 proto::ReplicaSystemConfiguration { key, value },
774 ) => StateUpdateKind::ReplicaSystemConfiguration(key, value),
775 proto::StateUpdateKind::GidMapping(proto::GidMapping { key, value }) => {
776 StateUpdateKind::SystemObjectMapping(key, value)
777 }
778 proto::StateUpdateKind::SystemPrivileges(proto::SystemPrivileges { key, value }) => {
779 StateUpdateKind::SystemPrivilege(key, value)
780 }
781 proto::StateUpdateKind::StorageCollectionMetadata(
782 proto::StorageCollectionMetadata { key, value },
783 ) => StateUpdateKind::StorageCollectionMetadata(key, value),
784 proto::StateUpdateKind::UnfinalizedShard(proto::UnfinalizedShard { key }) => {
785 StateUpdateKind::UnfinalizedShard(key, ())
786 }
787 proto::StateUpdateKind::TxnWalShard(proto::TxnWalShard { value }) => {
788 StateUpdateKind::TxnWalShard((), value)
789 }
790 proto::StateUpdateKind::SourceReferences(proto::SourceReferences { key, value }) => {
791 StateUpdateKind::SourceReferences(key, value)
792 }
793 proto::StateUpdateKind::NetworkPolicy(proto::NetworkPolicy { key, value }) => {
794 StateUpdateKind::NetworkPolicy(key, value)
795 }
796 })
797 }
798}
799
800impl From<PersistStateUpdate> for StateUpdate<StateUpdateKindJson> {
803 fn from(kvtd: PersistStateUpdate) -> Self {
804 let ((key, ()), ts, diff) = kvtd;
805 StateUpdate {
806 kind: StateUpdateKindJson::from(key),
807 ts,
808 diff: diff.into(),
809 }
810 }
811}
812
813impl From<StateUpdateKindJson> for SourceData {
814 fn from(value: StateUpdateKindJson) -> SourceData {
815 let row = value.0.into_row();
816 SourceData(Ok(row))
817 }
818}
819
820impl From<SourceData> for StateUpdateKindJson {
821 fn from(value: SourceData) -> Self {
822 let row = value.0.expect("only Ok values stored in catalog shard");
823 StateUpdateKindJson(Jsonb::from_row(row))
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use mz_persist_types::Codec;
830 use mz_repr::{RelationDesc, SqlScalarType};
831 use mz_storage_types::sources::SourceData;
832 use proptest::prelude::*;
833
834 use crate::durable::Epoch;
835 use crate::durable::objects::FenceToken;
836 use crate::durable::objects::serialization::proto;
837 use crate::durable::objects::state_update::{StateUpdateKind, StateUpdateKindJson};
838
839 #[mz_ore::test]
840 #[cfg_attr(miri, ignore)]
841 fn kind_test() {
842 let test_cases = [
843 (
844 StateUpdateKind::FenceToken(FenceToken {
845 deploy_generation: 1,
846 epoch: Epoch::new(1).expect("non-zero"),
847 }),
848 "FenceToken",
849 ),
850 (
851 StateUpdateKind::Config(
852 proto::ConfigKey { key: String::new() },
853 proto::ConfigValue { value: 1 },
854 ),
855 "Config",
856 ),
857 (
858 StateUpdateKind::Setting(
859 proto::SettingKey {
860 name: String::new(),
861 },
862 proto::SettingValue {
863 value: String::new(),
864 },
865 ),
866 "Setting",
867 ),
868 (
869 StateUpdateKind::AuditLog(
870 proto::AuditLogKey {
871 event: proto::AuditLogEvent::V1(proto::AuditLogEventV1 {
872 id: 1,
873 event_type: proto::audit_log_event_v1::EventType::Create,
874 object_type: proto::audit_log_event_v1::ObjectType::Cluster,
875 user: None,
876 occurred_at: proto::EpochMillis { millis: 4 },
877 details: proto::audit_log_event_v1::Details::ResetAllV1(
878 proto::Empty {},
879 ),
880 }),
881 },
882 (),
883 ),
884 "AuditLog",
885 ),
886 ];
887
888 for (kind, expected) in test_cases {
889 let json_kind: StateUpdateKindJson = kind.into();
890 let kind = json_kind.kind().to_string();
891 assert_eq!(expected, kind);
892 }
893 }
894
895 proptest! {
896 #[mz_ore::test]
897 #[cfg_attr(miri, ignore)] fn proptest_state_update_kind_roundtrip(kind: StateUpdateKind) {
899 let raw = StateUpdateKindJson::from(kind.clone());
902 let desc = RelationDesc::builder().with_column("a", SqlScalarType::Jsonb.nullable(false)).finish();
903
904 let source_data = SourceData::from(raw.clone());
906 let mut encoded = Vec::new();
907 source_data.encode(&mut encoded);
908 let decoded = SourceData::decode(&encoded, &desc).expect("should be valid SourceData");
909 prop_assert_eq!(&source_data, &decoded);
910 let decoded = StateUpdateKindJson::from(decoded);
911 prop_assert_eq!(&raw, &decoded);
912
913 let decoded = StateUpdateKind::try_from(decoded).expect("should be valid StateUpdateKind");
915 prop_assert_eq!(&kind, &decoded);
916 }
917 }
918}