1use std::cmp::Ordering;
11use std::collections::BTreeMap;
12use std::fmt::{Debug, Formatter};
13use std::hash::{Hash, Hasher};
14use std::marker::PhantomData;
15use std::str::FromStr;
16use std::sync::Arc;
17
18use bytes::{Buf, Bytes};
19use differential_dataflow::lattice::Lattice;
20use differential_dataflow::trace::Description;
21use mz_ore::cast::CastInto;
22use mz_ore::{halt, soft_panic_or_log};
23use mz_persist::indexed::encoding::{BatchColumnarFormat, BlobTraceBatchPart, BlobTraceUpdates};
24use mz_persist::location::{SeqNo, VersionedData};
25use mz_persist::metrics::ColumnarMetrics;
26use mz_persist_types::schema::SchemaId;
27use mz_persist_types::stats::{PartStats, ProtoStructStats};
28use mz_persist_types::{Codec, Codec64};
29use mz_proto::{IntoRustIfSome, ProtoMapEntry, ProtoType, RustType, TryFromProtoError};
30use proptest::prelude::Arbitrary;
31use proptest::strategy::Strategy;
32use prost::Message;
33use semver::Version;
34use serde::ser::SerializeStruct;
35use serde::{Deserialize, Serialize, Serializer};
36use timely::progress::{Antichain, Timestamp};
37use uuid::Uuid;
38
39use crate::critical::{CriticalReaderId, Opaque};
40use crate::error::{CodecMismatch, CodecMismatchT};
41use crate::internal::metrics::Metrics;
42use crate::internal::paths::{PartialBatchKey, PartialRollupKey};
43use crate::internal::state::{
44 ActiveGc, ActiveRollup, BatchPart, CriticalReaderState, EncodedSchemas, HandleDebugState,
45 HollowBatch, HollowBatchPart, HollowRollup, HollowRun, HollowRunRef, IdempotencyToken,
46 LeasedReaderState, ProtoActiveGc, ProtoActiveRollup, ProtoCompaction, ProtoCriticalReaderState,
47 ProtoEncodedSchemas, ProtoHandleDebugState, ProtoHollowBatch, ProtoHollowBatchPart,
48 ProtoHollowRollup, ProtoHollowRun, ProtoHollowRunRef, ProtoIdHollowBatch, ProtoIdMerge,
49 ProtoIdSpineBatch, ProtoInlineBatchPart, ProtoInlinedDiffs, ProtoLeasedReaderState, ProtoMerge,
50 ProtoRollup, ProtoRunMeta, ProtoRunOrder, ProtoSpineBatch, ProtoSpineId, ProtoStateDiff,
51 ProtoStateField, ProtoStateFieldDiffType, ProtoStateFieldDiffs, ProtoTrace, ProtoU64Antichain,
52 ProtoU64Description, ProtoVersionedData, ProtoWriterState, RunId, RunMeta, RunOrder, RunPart,
53 State, StateCollections, TypedState, WriterState, proto_hollow_batch_part,
54};
55use crate::internal::state_diff::{
56 ProtoStateFieldDiff, ProtoStateFieldDiffsWriter, StateDiff, StateFieldDiff, StateFieldValDiff,
57};
58use crate::internal::trace::{
59 ActiveCompaction, FlatTrace, SpineId, ThinMerge, ThinSpineBatch, Trace,
60};
61use crate::read::{LeasedReaderId, READER_LEASE_DURATION};
62use crate::{PersistConfig, ShardId, WriterId, cfg};
63
64#[derive(Debug)]
66pub struct Schemas<K: Codec, V: Codec> {
67 pub id: Option<SchemaId>,
70 pub key: Arc<K::Schema>,
72 pub val: Arc<V::Schema>,
74}
75
76impl<K: Codec, V: Codec> Clone for Schemas<K, V> {
77 fn clone(&self) -> Self {
78 Self {
79 id: self.id,
80 key: Arc::clone(&self.key),
81 val: Arc::clone(&self.val),
82 }
83 }
84}
85
86#[derive(Clone, Serialize, Deserialize)]
109pub struct LazyProto<T> {
110 buf: Bytes,
111 _phantom: PhantomData<fn() -> T>,
112}
113
114impl<T: Message + Default + Debug> Debug for LazyProto<T> {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 match self.decode() {
117 Ok(proto) => Debug::fmt(&proto, f),
118 Err(err) => f
119 .debug_struct(&format!("LazyProto<{}>", std::any::type_name::<T>()))
120 .field("err", &err)
121 .finish(),
122 }
123 }
124}
125
126impl<T> PartialEq for LazyProto<T> {
127 fn eq(&self, other: &Self) -> bool {
128 self.cmp(other) == Ordering::Equal
129 }
130}
131
132impl<T> Eq for LazyProto<T> {}
133
134impl<T> PartialOrd for LazyProto<T> {
135 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
136 Some(self.cmp(other))
137 }
138}
139
140impl<T> Ord for LazyProto<T> {
141 fn cmp(&self, other: &Self) -> Ordering {
142 let LazyProto {
143 buf: self_buf,
144 _phantom: _,
145 } = self;
146 let LazyProto {
147 buf: other_buf,
148 _phantom: _,
149 } = other;
150 self_buf.cmp(other_buf)
151 }
152}
153
154impl<T> Hash for LazyProto<T> {
155 fn hash<H: Hasher>(&self, state: &mut H) {
156 let LazyProto { buf, _phantom } = self;
157 buf.hash(state);
158 }
159}
160
161impl<T: Message + Default> From<&T> for LazyProto<T> {
162 fn from(value: &T) -> Self {
163 let buf = Bytes::from(value.encode_to_vec());
164 LazyProto {
165 buf,
166 _phantom: PhantomData,
167 }
168 }
169}
170
171impl<T: Message + Default> LazyProto<T> {
172 pub fn decode(&self) -> Result<T, prost::DecodeError> {
173 T::decode(&*self.buf)
174 }
175
176 pub fn decode_to<R: RustType<T>>(&self) -> anyhow::Result<R> {
177 Ok(T::decode(&*self.buf)?.into_rust()?)
178 }
179}
180
181impl<T: Message + Default> RustType<Bytes> for LazyProto<T> {
182 fn into_proto(&self) -> Bytes {
183 self.buf.clone()
184 }
185
186 fn from_proto(buf: Bytes) -> Result<Self, TryFromProtoError> {
187 Ok(Self {
188 buf,
189 _phantom: PhantomData,
190 })
191 }
192}
193
194#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
206pub(crate) struct MetadataMap(BTreeMap<String, Bytes>);
207
208#[allow(unused)]
213#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
214pub(crate) struct MetadataKey<V, P = V> {
215 name: &'static str,
216 type_: PhantomData<(V, P)>,
217}
218
219impl<V, P> MetadataKey<V, P> {
220 #[allow(unused)]
221 pub(crate) const fn new(name: &'static str) -> Self {
222 MetadataKey {
223 name,
224 type_: PhantomData,
225 }
226 }
227}
228
229impl serde::Serialize for MetadataMap {
230 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231 where
232 S: Serializer,
233 {
234 serializer.collect_map(self.0.iter())
235 }
236}
237
238impl MetadataMap {
239 pub fn is_empty(&self) -> bool {
241 self.0.is_empty()
242 }
243
244 #[allow(unused)]
246 pub fn set<V: RustType<P>, P: prost::Message>(&mut self, key: MetadataKey<V, P>, value: V) {
247 self.0.insert(
248 String::from(key.name),
249 Bytes::from(value.into_proto_owned().encode_to_vec()),
250 );
251 }
252
253 #[allow(unused)]
255 pub fn get<V: RustType<P>, P: prost::Message + Default>(
256 &self,
257 key: MetadataKey<V, P>,
258 ) -> Option<V> {
259 let proto = match P::decode(self.0.get(key.name)?.as_ref()) {
260 Ok(decoded) => decoded,
261 Err(err) => {
262 soft_panic_or_log!(
264 "error when decoding {key}; was it redefined? {err}",
265 key = key.name
266 );
267 return None;
268 }
269 };
270
271 match proto.into_rust() {
272 Ok(proto) => Some(proto),
273 Err(err) => {
274 soft_panic_or_log!(
276 "error when decoding {key}; was it redefined? {err}",
277 key = key.name
278 );
279 None
280 }
281 }
282 }
283}
284impl RustType<BTreeMap<String, Bytes>> for MetadataMap {
285 fn into_proto(&self) -> BTreeMap<String, Bytes> {
286 self.0.clone()
287 }
288 fn from_proto(proto: BTreeMap<String, Bytes>) -> Result<Self, TryFromProtoError> {
289 Ok(MetadataMap(proto))
290 }
291}
292
293pub(crate) fn parse_id(id_prefix: &str, id_type: &str, encoded: &str) -> Result<[u8; 16], String> {
294 let uuid_encoded = match encoded.strip_prefix(id_prefix) {
295 Some(x) => x,
296 None => return Err(format!("invalid {} {}: incorrect prefix", id_type, encoded)),
297 };
298 let uuid = Uuid::parse_str(uuid_encoded)
299 .map_err(|err| format!("invalid {} {}: {}", id_type, encoded, err))?;
300 Ok(*uuid.as_bytes())
301}
302
303pub(crate) fn assert_code_can_read_data(code_version: &Version, data_version: &Version) {
304 if !cfg::code_can_read_data(code_version, data_version) {
305 if cfg!(test) {
308 panic!("code at version {code_version} cannot read data with version {data_version}");
309 } else {
310 halt!("code at version {code_version} cannot read data with version {data_version}");
311 }
312 }
313}
314
315impl RustType<String> for LeasedReaderId {
316 fn into_proto(&self) -> String {
317 self.to_string()
318 }
319
320 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
321 match proto.parse() {
322 Ok(x) => Ok(x),
323 Err(_) => Err(TryFromProtoError::InvalidShardId(proto)),
324 }
325 }
326}
327
328impl RustType<String> for CriticalReaderId {
329 fn into_proto(&self) -> String {
330 self.to_string()
331 }
332
333 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
334 match proto.parse() {
335 Ok(x) => Ok(x),
336 Err(_) => Err(TryFromProtoError::InvalidShardId(proto)),
337 }
338 }
339}
340
341impl RustType<String> for WriterId {
342 fn into_proto(&self) -> String {
343 self.to_string()
344 }
345
346 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
347 match proto.parse() {
348 Ok(x) => Ok(x),
349 Err(_) => Err(TryFromProtoError::InvalidShardId(proto)),
350 }
351 }
352}
353
354impl RustType<ProtoEncodedSchemas> for EncodedSchemas {
355 fn into_proto(&self) -> ProtoEncodedSchemas {
356 ProtoEncodedSchemas {
357 key: self.key.clone(),
358 key_data_type: self.key_data_type.clone(),
359 val: self.val.clone(),
360 val_data_type: self.val_data_type.clone(),
361 }
362 }
363
364 fn from_proto(proto: ProtoEncodedSchemas) -> Result<Self, TryFromProtoError> {
365 Ok(EncodedSchemas {
366 key: proto.key,
367 key_data_type: proto.key_data_type,
368 val: proto.val,
369 val_data_type: proto.val_data_type,
370 })
371 }
372}
373
374impl RustType<String> for IdempotencyToken {
375 fn into_proto(&self) -> String {
376 self.to_string()
377 }
378
379 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
380 match proto.parse() {
381 Ok(x) => Ok(x),
382 Err(_) => Err(TryFromProtoError::InvalidShardId(proto)),
383 }
384 }
385}
386
387impl RustType<String> for PartialBatchKey {
388 fn into_proto(&self) -> String {
389 self.0.clone()
390 }
391
392 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
393 Ok(PartialBatchKey(proto))
394 }
395}
396
397impl RustType<String> for PartialRollupKey {
398 fn into_proto(&self) -> String {
399 self.0.clone()
400 }
401
402 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
403 Ok(PartialRollupKey(proto))
404 }
405}
406
407impl<T: Timestamp + Lattice + Codec64> StateDiff<T> {
408 pub fn encode<B>(&self, buf: &mut B)
409 where
410 B: bytes::BufMut,
411 {
412 self.into_proto()
413 .encode(buf)
414 .expect("no required fields means no initialization errors");
415 }
416
417 pub fn decode(build_version: &Version, buf: Bytes) -> Self {
418 let proto = ProtoStateDiff::decode(buf)
419 .expect("internal error: invalid encoded state");
424 let diff = Self::from_proto(proto).expect("internal error: invalid encoded state");
425 assert_code_can_read_data(build_version, &diff.applier_version);
426 diff
427 }
428}
429
430impl<T: Timestamp + Codec64> RustType<ProtoStateDiff> for StateDiff<T> {
431 fn into_proto(&self) -> ProtoStateDiff {
432 let StateDiff {
434 applier_version,
435 seqno_from,
436 seqno_to,
437 walltime_ms,
438 latest_rollup_key,
439 rollups,
440 active_rollup,
441 active_gc,
442 hostname,
443 last_gc_req,
444 leased_readers,
445 critical_readers,
446 writers,
447 schemas,
448 since,
449 legacy_batches,
450 hollow_batches,
451 spine_batches,
452 merges,
453 } = self;
454
455 let proto = ProtoStateFieldDiffs::default();
456
457 let mut writer = proto.into_writer();
459
460 field_diffs_into_proto(ProtoStateField::Hostname, hostname, &mut writer);
461 field_diffs_into_proto(ProtoStateField::LastGcReq, last_gc_req, &mut writer);
462 field_diffs_into_proto(ProtoStateField::Rollups, rollups, &mut writer);
463 field_diffs_into_proto(ProtoStateField::ActiveRollup, active_rollup, &mut writer);
464 field_diffs_into_proto(ProtoStateField::ActiveGc, active_gc, &mut writer);
465 field_diffs_into_proto(ProtoStateField::LeasedReaders, leased_readers, &mut writer);
466 field_diffs_into_proto(
467 ProtoStateField::CriticalReaders,
468 critical_readers,
469 &mut writer,
470 );
471 field_diffs_into_proto(ProtoStateField::Writers, writers, &mut writer);
472 field_diffs_into_proto(ProtoStateField::Schemas, schemas, &mut writer);
473 field_diffs_into_proto(ProtoStateField::Since, since, &mut writer);
474 field_diffs_into_proto(ProtoStateField::LegacyBatches, legacy_batches, &mut writer);
475 field_diffs_into_proto(ProtoStateField::HollowBatches, hollow_batches, &mut writer);
476 field_diffs_into_proto(ProtoStateField::SpineBatches, spine_batches, &mut writer);
477 field_diffs_into_proto(ProtoStateField::SpineMerges, merges, &mut writer);
478
479 let field_diffs = writer.into_proto();
481
482 debug_assert_eq!(field_diffs.validate(), Ok(()));
483 ProtoStateDiff {
484 applier_version: applier_version.to_string(),
485 seqno_from: seqno_from.into_proto(),
486 seqno_to: seqno_to.into_proto(),
487 walltime_ms: walltime_ms.into_proto(),
488 latest_rollup_key: latest_rollup_key.into_proto(),
489 field_diffs: Some(field_diffs),
490 }
491 }
492
493 fn from_proto(proto: ProtoStateDiff) -> Result<Self, TryFromProtoError> {
494 let applier_version = if proto.applier_version.is_empty() {
495 semver::Version::new(0, 0, 0)
499 } else {
500 semver::Version::parse(&proto.applier_version).map_err(|err| {
501 TryFromProtoError::InvalidSemverVersion(format!(
502 "invalid applier_version {}: {}",
503 proto.applier_version, err
504 ))
505 })?
506 };
507 let mut state_diff = StateDiff::new(
508 applier_version,
509 proto.seqno_from.into_rust()?,
510 proto.seqno_to.into_rust()?,
511 proto.walltime_ms,
512 proto.latest_rollup_key.into_rust()?,
513 );
514 if let Some(field_diffs) = proto.field_diffs {
515 field_diffs
517 .validate()
518 .map_err(TryFromProtoError::InvalidPersistState)?;
519 for field_diff in field_diffs.iter() {
520 let (field, diff) = field_diff?;
521 match field {
522 ProtoStateField::Hostname => field_diff_into_rust::<(), String, _, _, _, _>(
523 diff,
524 &mut state_diff.hostname,
525 |()| Ok(()),
526 |v| v.into_rust(),
527 )?,
528 ProtoStateField::LastGcReq => field_diff_into_rust::<(), u64, _, _, _, _>(
529 diff,
530 &mut state_diff.last_gc_req,
531 |()| Ok(()),
532 |v| v.into_rust(),
533 )?,
534 ProtoStateField::ActiveGc => {
535 field_diff_into_rust::<(), ProtoActiveGc, _, _, _, _>(
536 diff,
537 &mut state_diff.active_gc,
538 |()| Ok(()),
539 |v| v.into_rust(),
540 )?
541 }
542 ProtoStateField::ActiveRollup => {
543 field_diff_into_rust::<(), ProtoActiveRollup, _, _, _, _>(
544 diff,
545 &mut state_diff.active_rollup,
546 |()| Ok(()),
547 |v| v.into_rust(),
548 )?
549 }
550 ProtoStateField::Rollups => {
551 field_diff_into_rust::<u64, ProtoHollowRollup, _, _, _, _>(
552 diff,
553 &mut state_diff.rollups,
554 |k| k.into_rust(),
555 |v| v.into_rust(),
556 )?
557 }
558 ProtoStateField::DeprecatedRollups => {
562 field_diff_into_rust::<u64, String, _, _, _, _>(
563 diff,
564 &mut state_diff.rollups,
565 |k| k.into_rust(),
566 |v| {
567 Ok(HollowRollup {
568 key: v.into_rust()?,
569 encoded_size_bytes: None,
570 })
571 },
572 )?
573 }
574 ProtoStateField::LeasedReaders => {
575 field_diff_into_rust::<String, ProtoLeasedReaderState, _, _, _, _>(
576 diff,
577 &mut state_diff.leased_readers,
578 |k| k.into_rust(),
579 |v| v.into_rust(),
580 )?
581 }
582 ProtoStateField::CriticalReaders => {
583 field_diff_into_rust::<String, ProtoCriticalReaderState, _, _, _, _>(
584 diff,
585 &mut state_diff.critical_readers,
586 |k| k.into_rust(),
587 |v| v.into_rust(),
588 )?
589 }
590 ProtoStateField::Writers => {
591 field_diff_into_rust::<String, ProtoWriterState, _, _, _, _>(
592 diff,
593 &mut state_diff.writers,
594 |k| k.into_rust(),
595 |v| v.into_rust(),
596 )?
597 }
598 ProtoStateField::Schemas => {
599 field_diff_into_rust::<u64, ProtoEncodedSchemas, _, _, _, _>(
600 diff,
601 &mut state_diff.schemas,
602 |k| k.into_rust(),
603 |v| v.into_rust(),
604 )?
605 }
606 ProtoStateField::Since => {
607 field_diff_into_rust::<(), ProtoU64Antichain, _, _, _, _>(
608 diff,
609 &mut state_diff.since,
610 |()| Ok(()),
611 |v| v.into_rust(),
612 )?
613 }
614 ProtoStateField::LegacyBatches => {
615 field_diff_into_rust::<ProtoHollowBatch, (), _, _, _, _>(
616 diff,
617 &mut state_diff.legacy_batches,
618 |k| k.into_rust(),
619 |()| Ok(()),
620 )?
621 }
622 ProtoStateField::HollowBatches => {
623 field_diff_into_rust::<ProtoSpineId, ProtoHollowBatch, _, _, _, _>(
624 diff,
625 &mut state_diff.hollow_batches,
626 |k| k.into_rust(),
627 |v| v.into_rust(),
628 )?
629 }
630 ProtoStateField::SpineBatches => {
631 field_diff_into_rust::<ProtoSpineId, ProtoSpineBatch, _, _, _, _>(
632 diff,
633 &mut state_diff.spine_batches,
634 |k| k.into_rust(),
635 |v| v.into_rust(),
636 )?
637 }
638 ProtoStateField::SpineMerges => {
639 field_diff_into_rust::<ProtoSpineId, ProtoMerge, _, _, _, _>(
640 diff,
641 &mut state_diff.merges,
642 |k| k.into_rust(),
643 |v| v.into_rust(),
644 )?
645 }
646 }
647 }
648 }
649 Ok(state_diff)
650 }
651}
652
653fn field_diffs_into_proto<K, KP, V, VP>(
654 field: ProtoStateField,
655 diffs: &[StateFieldDiff<K, V>],
656 writer: &mut ProtoStateFieldDiffsWriter,
657) where
658 KP: prost::Message,
659 K: RustType<KP>,
660 VP: prost::Message,
661 V: RustType<VP>,
662{
663 for diff in diffs.iter() {
664 field_diff_into_proto(field, diff, writer);
665 }
666}
667
668fn field_diff_into_proto<K, KP, V, VP>(
669 field: ProtoStateField,
670 diff: &StateFieldDiff<K, V>,
671 writer: &mut ProtoStateFieldDiffsWriter,
672) where
673 KP: prost::Message,
674 K: RustType<KP>,
675 VP: prost::Message,
676 V: RustType<VP>,
677{
678 writer.push_field(field);
679 writer.encode_proto(&diff.key.into_proto());
680 match &diff.val {
681 StateFieldValDiff::Insert(to) => {
682 writer.push_diff_type(ProtoStateFieldDiffType::Insert);
683 writer.encode_proto(&to.into_proto());
684 }
685 StateFieldValDiff::Update(from, to) => {
686 writer.push_diff_type(ProtoStateFieldDiffType::Update);
687 writer.encode_proto(&from.into_proto());
688 writer.encode_proto(&to.into_proto());
689 }
690 StateFieldValDiff::Delete(from) => {
691 writer.push_diff_type(ProtoStateFieldDiffType::Delete);
692 writer.encode_proto(&from.into_proto());
693 }
694 };
695}
696
697fn field_diff_into_rust<KP, VP, K, V, KFn, VFn>(
698 proto: ProtoStateFieldDiff<'_>,
699 diffs: &mut Vec<StateFieldDiff<K, V>>,
700 k_fn: KFn,
701 v_fn: VFn,
702) -> Result<(), TryFromProtoError>
703where
704 KP: prost::Message + Default,
705 VP: prost::Message + Default,
706 KFn: Fn(KP) -> Result<K, TryFromProtoError>,
707 VFn: Fn(VP) -> Result<V, TryFromProtoError>,
708{
709 let val = match proto.diff_type {
710 ProtoStateFieldDiffType::Insert => {
711 let to = VP::decode(proto.to)
712 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
713 StateFieldValDiff::Insert(v_fn(to)?)
714 }
715 ProtoStateFieldDiffType::Update => {
716 let from = VP::decode(proto.from)
717 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
718 let to = VP::decode(proto.to)
719 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
720
721 StateFieldValDiff::Update(v_fn(from)?, v_fn(to)?)
722 }
723 ProtoStateFieldDiffType::Delete => {
724 let from = VP::decode(proto.from)
725 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
726 StateFieldValDiff::Delete(v_fn(from)?)
727 }
728 };
729 let key = KP::decode(proto.key)
730 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
731 diffs.push(StateFieldDiff {
732 key: k_fn(key)?,
733 val,
734 });
735 Ok(())
736}
737
738#[derive(Debug)]
741#[cfg_attr(any(test, debug_assertions), derive(Clone, PartialEq))]
742pub struct UntypedState<T> {
743 pub(crate) key_codec: String,
744 pub(crate) val_codec: String,
745 pub(crate) ts_codec: String,
746 pub(crate) diff_codec: String,
747
748 state: State<T>,
752}
753
754impl<T: Timestamp + Lattice + Codec64> UntypedState<T> {
755 pub fn seqno(&self) -> SeqNo {
756 self.state.seqno
757 }
758
759 pub fn rollups(&self) -> &BTreeMap<SeqNo, HollowRollup> {
760 &self.state.collections.rollups
761 }
762
763 pub fn latest_rollup(&self) -> (&SeqNo, &HollowRollup) {
764 self.state.latest_rollup()
765 }
766
767 pub fn apply_encoded_diffs<'a, I: IntoIterator<Item = &'a VersionedData>>(
768 &mut self,
769 cfg: &PersistConfig,
770 metrics: &Metrics,
771 diffs: I,
772 ) {
773 if T::codec_name() != self.ts_codec {
777 return;
778 }
779 self.state.apply_encoded_diffs(cfg, metrics, diffs);
780 }
781
782 pub fn check_codecs<K: Codec, V: Codec, D: Codec64>(
783 self,
784 shard_id: &ShardId,
785 ) -> Result<TypedState<K, V, T, D>, Box<CodecMismatch>> {
786 assert_eq!(shard_id, &self.state.shard_id);
789 if K::codec_name() != self.key_codec
790 || V::codec_name() != self.val_codec
791 || T::codec_name() != self.ts_codec
792 || D::codec_name() != self.diff_codec
793 {
794 return Err(Box::new(CodecMismatch {
795 requested: (
796 K::codec_name(),
797 V::codec_name(),
798 T::codec_name(),
799 D::codec_name(),
800 None,
801 ),
802 actual: (
803 self.key_codec,
804 self.val_codec,
805 self.ts_codec,
806 self.diff_codec,
807 None,
808 ),
809 }));
810 }
811 Ok(TypedState {
812 state: self.state,
813 _phantom: PhantomData,
814 })
815 }
816
817 pub(crate) fn check_ts_codec(self, shard_id: &ShardId) -> Result<State<T>, CodecMismatchT> {
818 assert_eq!(shard_id, &self.state.shard_id);
821 if T::codec_name() != self.ts_codec {
822 return Err(CodecMismatchT {
823 requested: T::codec_name(),
824 actual: self.ts_codec,
825 });
826 }
827 Ok(self.state)
828 }
829
830 pub fn decode(build_version: &Version, buf: impl Buf) -> Self {
831 let proto = ProtoRollup::decode(buf)
832 .expect("internal error: invalid encoded state");
837 let state = Rollup::from_proto(proto)
838 .expect("internal error: invalid encoded state")
839 .state;
840 assert_code_can_read_data(build_version, &state.state.collections.version);
841 state
842 }
843}
844
845impl<K, V, T, D> From<TypedState<K, V, T, D>> for UntypedState<T>
846where
847 K: Codec,
848 V: Codec,
849 T: Codec64,
850 D: Codec64,
851{
852 fn from(typed_state: TypedState<K, V, T, D>) -> Self {
853 UntypedState {
854 key_codec: K::codec_name(),
855 val_codec: V::codec_name(),
856 ts_codec: T::codec_name(),
857 diff_codec: D::codec_name(),
858 state: typed_state.state,
859 }
860 }
861}
862
863#[derive(Debug)]
870pub struct Rollup<T> {
871 pub(crate) state: UntypedState<T>,
872 pub(crate) diffs: Option<InlinedDiffs>,
873}
874
875impl<T: Timestamp + Lattice + Codec64> Rollup<T> {
876 pub(crate) fn from(state: UntypedState<T>, diffs: Vec<VersionedData>) -> Self {
880 let latest_rollup_seqno = *state.latest_rollup().0;
881 let mut verify_seqno = latest_rollup_seqno;
882 for diff in &diffs {
883 assert_eq!(verify_seqno.next(), diff.seqno);
884 verify_seqno = diff.seqno;
885 }
886 assert_eq!(verify_seqno, state.seqno());
887
888 let diffs = Some(InlinedDiffs::from(
889 latest_rollup_seqno.next(),
890 state.seqno().next(),
891 diffs,
892 ));
893
894 Self { state, diffs }
895 }
896
897 pub(crate) fn from_untyped_state_without_diffs(state: UntypedState<T>) -> Self {
898 Self { state, diffs: None }
899 }
900
901 pub(crate) fn from_state_without_diffs(
902 state: State<T>,
903 key_codec: String,
904 val_codec: String,
905 ts_codec: String,
906 diff_codec: String,
907 ) -> Self {
908 Self::from_untyped_state_without_diffs(UntypedState {
909 key_codec,
910 val_codec,
911 ts_codec,
912 diff_codec,
913 state,
914 })
915 }
916}
917
918#[derive(Debug)]
919pub(crate) struct InlinedDiffs {
920 pub(crate) lower: SeqNo,
921 pub(crate) upper: SeqNo,
922 pub(crate) diffs: Vec<VersionedData>,
923}
924
925impl InlinedDiffs {
926 pub(crate) fn description(&self) -> Description<SeqNo> {
927 Description::new(
928 Antichain::from_elem(self.lower),
929 Antichain::from_elem(self.upper),
930 Antichain::from_elem(SeqNo::minimum()),
931 )
932 }
933
934 fn from(lower: SeqNo, upper: SeqNo, diffs: Vec<VersionedData>) -> Self {
935 for diff in &diffs {
936 assert!(diff.seqno >= lower);
937 assert!(diff.seqno < upper);
938 }
939 Self {
940 lower,
941 upper,
942 diffs,
943 }
944 }
945}
946
947impl RustType<ProtoInlinedDiffs> for InlinedDiffs {
948 fn into_proto(&self) -> ProtoInlinedDiffs {
949 ProtoInlinedDiffs {
950 lower: self.lower.into_proto(),
951 upper: self.upper.into_proto(),
952 diffs: self.diffs.into_proto(),
953 }
954 }
955
956 fn from_proto(proto: ProtoInlinedDiffs) -> Result<Self, TryFromProtoError> {
957 Ok(Self {
958 lower: proto.lower.into_rust()?,
959 upper: proto.upper.into_rust()?,
960 diffs: proto.diffs.into_rust()?,
961 })
962 }
963}
964
965impl<T: Timestamp + Lattice + Codec64> RustType<ProtoRollup> for Rollup<T> {
966 fn into_proto(&self) -> ProtoRollup {
967 ProtoRollup {
968 applier_version: self.state.state.collections.version.to_string(),
969 shard_id: self.state.state.shard_id.into_proto(),
970 seqno: self.state.state.seqno.into_proto(),
971 walltime_ms: self.state.state.walltime_ms.into_proto(),
972 hostname: self.state.state.hostname.into_proto(),
973 key_codec: self.state.key_codec.into_proto(),
974 val_codec: self.state.val_codec.into_proto(),
975 ts_codec: T::codec_name(),
976 diff_codec: self.state.diff_codec.into_proto(),
977 last_gc_req: self.state.state.collections.last_gc_req.into_proto(),
978 active_rollup: self.state.state.collections.active_rollup.into_proto(),
979 active_gc: self.state.state.collections.active_gc.into_proto(),
980 rollups: self
981 .state
982 .state
983 .collections
984 .rollups
985 .iter()
986 .map(|(seqno, key)| (seqno.into_proto(), key.into_proto()))
987 .collect(),
988 deprecated_rollups: Default::default(),
989 leased_readers: self
990 .state
991 .state
992 .collections
993 .leased_readers
994 .iter()
995 .map(|(id, state)| (id.into_proto(), state.into_proto()))
996 .collect(),
997 critical_readers: self
998 .state
999 .state
1000 .collections
1001 .critical_readers
1002 .iter()
1003 .map(|(id, state)| (id.into_proto(), state.into_proto()))
1004 .collect(),
1005 writers: self
1006 .state
1007 .state
1008 .collections
1009 .writers
1010 .iter()
1011 .map(|(id, state)| (id.into_proto(), state.into_proto()))
1012 .collect(),
1013 schemas: self
1014 .state
1015 .state
1016 .collections
1017 .schemas
1018 .iter()
1019 .map(|(id, schema)| (id.into_proto(), schema.into_proto()))
1020 .collect(),
1021 trace: Some(self.state.state.collections.trace.into_proto()),
1022 diffs: self.diffs.as_ref().map(|x| x.into_proto()),
1023 }
1024 }
1025
1026 fn from_proto(x: ProtoRollup) -> Result<Self, TryFromProtoError> {
1027 let applier_version = if x.applier_version.is_empty() {
1028 semver::Version::new(0, 0, 0)
1032 } else {
1033 semver::Version::parse(&x.applier_version).map_err(|err| {
1034 TryFromProtoError::InvalidSemverVersion(format!(
1035 "invalid applier_version {}: {}",
1036 x.applier_version, err
1037 ))
1038 })?
1039 };
1040
1041 let mut rollups = BTreeMap::new();
1042 for (seqno, rollup) in x.rollups {
1043 rollups.insert(seqno.into_rust()?, rollup.into_rust()?);
1044 }
1045 for (seqno, key) in x.deprecated_rollups {
1046 rollups.insert(
1047 seqno.into_rust()?,
1048 HollowRollup {
1049 key: key.into_rust()?,
1050 encoded_size_bytes: None,
1051 },
1052 );
1053 }
1054 let mut leased_readers = BTreeMap::new();
1055 for (id, state) in x.leased_readers {
1056 leased_readers.insert(id.into_rust()?, state.into_rust()?);
1057 }
1058 let mut critical_readers = BTreeMap::new();
1059 for (id, state) in x.critical_readers {
1060 critical_readers.insert(id.into_rust()?, state.into_rust()?);
1061 }
1062 let mut writers = BTreeMap::new();
1063 for (id, state) in x.writers {
1064 writers.insert(id.into_rust()?, state.into_rust()?);
1065 }
1066 let mut schemas = BTreeMap::new();
1067 for (id, x) in x.schemas {
1068 schemas.insert(id.into_rust()?, x.into_rust()?);
1069 }
1070 let active_rollup = x
1071 .active_rollup
1072 .map(|rollup| rollup.into_rust())
1073 .transpose()?;
1074 let active_gc = x.active_gc.map(|gc| gc.into_rust()).transpose()?;
1075 let collections = StateCollections {
1076 version: applier_version.clone(),
1077 rollups,
1078 active_rollup,
1079 active_gc,
1080 last_gc_req: x.last_gc_req.into_rust()?,
1081 leased_readers,
1082 critical_readers,
1083 writers,
1084 schemas,
1085 trace: x.trace.into_rust_if_some("trace")?,
1086 };
1087 let state = State {
1088 shard_id: x.shard_id.into_rust()?,
1089 seqno: x.seqno.into_rust()?,
1090 walltime_ms: x.walltime_ms,
1091 hostname: x.hostname,
1092 collections,
1093 };
1094
1095 let diffs: Option<InlinedDiffs> = x.diffs.map(|diffs| diffs.into_rust()).transpose()?;
1096 if let Some(diffs) = &diffs {
1097 if state.collections.rollups.is_empty() {
1101 return Err(TryFromProtoError::InvalidPersistState(
1102 "rollup state has diffs but no rollups".into(),
1103 ));
1104 }
1105 if diffs.lower != state.latest_rollup().0.next() {
1106 return Err(TryFromProtoError::InvalidPersistState(format!(
1107 "diffs lower ({}) should match latest rollup's successor: ({})",
1108 diffs.lower,
1109 state.latest_rollup().0.next()
1110 )));
1111 }
1112 if diffs.upper != state.seqno.next() {
1113 return Err(TryFromProtoError::InvalidPersistState(format!(
1114 "diffs upper ({}) should match state's successor: ({})",
1115 diffs.lower,
1116 state.seqno.next()
1117 )));
1118 }
1119 }
1120
1121 Ok(Rollup {
1122 state: UntypedState {
1123 state,
1124 key_codec: x.key_codec.into_rust()?,
1125 val_codec: x.val_codec.into_rust()?,
1126 ts_codec: x.ts_codec.into_rust()?,
1127 diff_codec: x.diff_codec.into_rust()?,
1128 },
1129 diffs,
1130 })
1131 }
1132}
1133
1134impl RustType<ProtoVersionedData> for VersionedData {
1135 fn into_proto(&self) -> ProtoVersionedData {
1136 ProtoVersionedData {
1137 seqno: self.seqno.into_proto(),
1138 data: Bytes::clone(&self.data),
1139 }
1140 }
1141
1142 fn from_proto(proto: ProtoVersionedData) -> Result<Self, TryFromProtoError> {
1143 Ok(Self {
1144 seqno: proto.seqno.into_rust()?,
1145 data: proto.data,
1146 })
1147 }
1148}
1149
1150impl RustType<ProtoSpineId> for SpineId {
1151 fn into_proto(&self) -> ProtoSpineId {
1152 ProtoSpineId {
1153 lo: self.0.into_proto(),
1154 hi: self.1.into_proto(),
1155 }
1156 }
1157
1158 fn from_proto(proto: ProtoSpineId) -> Result<Self, TryFromProtoError> {
1159 Ok(SpineId(proto.lo.into_rust()?, proto.hi.into_rust()?))
1160 }
1161}
1162
1163impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, Arc<HollowBatch<T>>> for ProtoIdHollowBatch {
1164 fn from_rust<'a>(entry: (&'a SpineId, &'a Arc<HollowBatch<T>>)) -> Self {
1165 let (id, batch) = entry;
1166 ProtoIdHollowBatch {
1167 id: Some(id.into_proto()),
1168 batch: Some(batch.into_proto()),
1169 }
1170 }
1171
1172 fn into_rust(self) -> Result<(SpineId, Arc<HollowBatch<T>>), TryFromProtoError> {
1173 let id = self.id.into_rust_if_some("ProtoIdHollowBatch::id")?;
1174 let batch = Arc::new(self.batch.into_rust_if_some("ProtoIdHollowBatch::batch")?);
1175 Ok((id, batch))
1176 }
1177}
1178
1179impl<T: Timestamp + Codec64> RustType<ProtoSpineBatch> for ThinSpineBatch<T> {
1180 fn into_proto(&self) -> ProtoSpineBatch {
1181 ProtoSpineBatch {
1182 desc: Some(self.desc.into_proto()),
1183 parts: self.parts.into_proto(),
1184 level: self.level.into_proto(),
1185 descs: self.descs.into_proto(),
1186 }
1187 }
1188
1189 fn from_proto(proto: ProtoSpineBatch) -> Result<Self, TryFromProtoError> {
1190 let level = proto.level.into_rust()?;
1191 let desc = proto.desc.into_rust_if_some("ProtoSpineBatch::desc")?;
1192 let parts = proto.parts.into_rust()?;
1193 let descs = proto.descs.into_rust()?;
1194 Ok(ThinSpineBatch {
1195 level,
1196 desc,
1197 parts,
1198 descs,
1199 })
1200 }
1201}
1202
1203impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, ThinSpineBatch<T>> for ProtoIdSpineBatch {
1204 fn from_rust<'a>(entry: (&'a SpineId, &'a ThinSpineBatch<T>)) -> Self {
1205 let (id, batch) = entry;
1206 ProtoIdSpineBatch {
1207 id: Some(id.into_proto()),
1208 batch: Some(batch.into_proto()),
1209 }
1210 }
1211
1212 fn into_rust(self) -> Result<(SpineId, ThinSpineBatch<T>), TryFromProtoError> {
1213 let id = self.id.into_rust_if_some("ProtoHollowBatch::id")?;
1214 let batch = self.batch.into_rust_if_some("ProtoHollowBatch::batch")?;
1215 Ok((id, batch))
1216 }
1217}
1218
1219impl RustType<ProtoCompaction> for ActiveCompaction {
1220 fn into_proto(&self) -> ProtoCompaction {
1221 ProtoCompaction {
1222 start_ms: self.start_ms,
1223 }
1224 }
1225
1226 fn from_proto(proto: ProtoCompaction) -> Result<Self, TryFromProtoError> {
1227 Ok(Self {
1228 start_ms: proto.start_ms,
1229 })
1230 }
1231}
1232
1233impl<T: Timestamp + Codec64> RustType<ProtoMerge> for ThinMerge<T> {
1234 fn into_proto(&self) -> ProtoMerge {
1235 ProtoMerge {
1236 since: Some(self.since.into_proto()),
1237 remaining_work: self.remaining_work.into_proto(),
1238 active_compaction: self.active_compaction.into_proto(),
1239 }
1240 }
1241
1242 fn from_proto(proto: ProtoMerge) -> Result<Self, TryFromProtoError> {
1243 let since = proto.since.into_rust_if_some("ProtoMerge::since")?;
1244 let remaining_work = proto.remaining_work.into_rust()?;
1245 let active_compaction = proto.active_compaction.into_rust()?;
1246 Ok(Self {
1247 since,
1248 remaining_work,
1249 active_compaction,
1250 })
1251 }
1252}
1253
1254impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, ThinMerge<T>> for ProtoIdMerge {
1255 fn from_rust<'a>((id, merge): (&'a SpineId, &'a ThinMerge<T>)) -> Self {
1256 ProtoIdMerge {
1257 id: Some(id.into_proto()),
1258 merge: Some(merge.into_proto()),
1259 }
1260 }
1261
1262 fn into_rust(self) -> Result<(SpineId, ThinMerge<T>), TryFromProtoError> {
1263 let id = self.id.into_rust_if_some("ProtoIdMerge::id")?;
1264 let merge = self.merge.into_rust_if_some("ProtoIdMerge::merge")?;
1265 Ok((id, merge))
1266 }
1267}
1268
1269impl<T: Timestamp + Codec64> RustType<ProtoTrace> for FlatTrace<T> {
1270 fn into_proto(&self) -> ProtoTrace {
1271 let since = self.since.into_proto();
1272 let legacy_batches = self
1273 .legacy_batches
1274 .iter()
1275 .map(|(b, _)| b.into_proto())
1276 .collect();
1277 let hollow_batches = self.hollow_batches.into_proto();
1278 let spine_batches = self.spine_batches.into_proto();
1279 let merges = self.merges.into_proto();
1280 ProtoTrace {
1281 since: Some(since),
1282 legacy_batches,
1283 hollow_batches,
1284 spine_batches,
1285 merges,
1286 }
1287 }
1288
1289 fn from_proto(proto: ProtoTrace) -> Result<Self, TryFromProtoError> {
1290 let since = proto.since.into_rust_if_some("ProtoTrace::since")?;
1291 let legacy_batches = proto
1292 .legacy_batches
1293 .into_iter()
1294 .map(|b| b.into_rust().map(|b| (b, ())))
1295 .collect::<Result<_, _>>()?;
1296 let hollow_batches = proto.hollow_batches.into_rust()?;
1297 let spine_batches = proto.spine_batches.into_rust()?;
1298 let merges = proto.merges.into_rust()?;
1299 Ok(FlatTrace {
1300 since,
1301 legacy_batches,
1302 hollow_batches,
1303 spine_batches,
1304 merges,
1305 })
1306 }
1307}
1308
1309impl<T: Timestamp + Lattice + Codec64> RustType<ProtoTrace> for Trace<T> {
1310 fn into_proto(&self) -> ProtoTrace {
1311 self.flatten().into_proto()
1312 }
1313
1314 fn from_proto(proto: ProtoTrace) -> Result<Self, TryFromProtoError> {
1315 Trace::unflatten(proto.into_rust()?).map_err(TryFromProtoError::InvalidPersistState)
1316 }
1317}
1318
1319impl<T: Timestamp + Codec64> RustType<ProtoLeasedReaderState> for LeasedReaderState<T> {
1320 fn into_proto(&self) -> ProtoLeasedReaderState {
1321 ProtoLeasedReaderState {
1322 seqno: self.seqno.into_proto(),
1323 since: Some(self.since.into_proto()),
1324 last_heartbeat_timestamp_ms: self.last_heartbeat_timestamp_ms.into_proto(),
1325 lease_duration_ms: self.lease_duration_ms.into_proto(),
1326 debug: Some(self.debug.into_proto()),
1327 }
1328 }
1329
1330 fn from_proto(proto: ProtoLeasedReaderState) -> Result<Self, TryFromProtoError> {
1331 let mut lease_duration_ms = proto.lease_duration_ms.into_rust()?;
1332 if lease_duration_ms == 0 {
1337 lease_duration_ms = u64::try_from(READER_LEASE_DURATION.default().as_millis())
1338 .expect("lease duration as millis should fit within u64");
1339 }
1340 let debug = proto.debug.unwrap_or_default().into_rust()?;
1343 Ok(LeasedReaderState {
1344 seqno: proto.seqno.into_rust()?,
1345 since: proto
1346 .since
1347 .into_rust_if_some("ProtoLeasedReaderState::since")?,
1348 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms.into_rust()?,
1349 lease_duration_ms,
1350 debug,
1351 })
1352 }
1353}
1354
1355impl<T: Timestamp + Codec64> RustType<ProtoCriticalReaderState> for CriticalReaderState<T> {
1356 fn into_proto(&self) -> ProtoCriticalReaderState {
1357 ProtoCriticalReaderState {
1358 since: Some(self.since.into_proto()),
1359 opaque: i64::from_le_bytes(self.opaque.1),
1360 opaque_codec: self.opaque.0.clone(),
1361 debug: Some(self.debug.into_proto()),
1362 }
1363 }
1364
1365 fn from_proto(proto: ProtoCriticalReaderState) -> Result<Self, TryFromProtoError> {
1366 let debug = proto.debug.unwrap_or_default().into_rust()?;
1369 Ok(CriticalReaderState {
1370 since: proto
1371 .since
1372 .into_rust_if_some("ProtoCriticalReaderState::since")?,
1373 opaque: Opaque(proto.opaque_codec, i64::to_le_bytes(proto.opaque)),
1374 debug,
1375 })
1376 }
1377}
1378
1379impl<T: Timestamp + Codec64> RustType<ProtoWriterState> for WriterState<T> {
1380 fn into_proto(&self) -> ProtoWriterState {
1381 ProtoWriterState {
1382 last_heartbeat_timestamp_ms: self.last_heartbeat_timestamp_ms.into_proto(),
1383 lease_duration_ms: self.lease_duration_ms.into_proto(),
1384 most_recent_write_token: self.most_recent_write_token.into_proto(),
1385 most_recent_write_upper: Some(self.most_recent_write_upper.into_proto()),
1386 debug: Some(self.debug.into_proto()),
1387 }
1388 }
1389
1390 fn from_proto(proto: ProtoWriterState) -> Result<Self, TryFromProtoError> {
1391 let most_recent_write_token = if proto.most_recent_write_token.is_empty() {
1397 IdempotencyToken::SENTINEL
1398 } else {
1399 proto.most_recent_write_token.into_rust()?
1400 };
1401 let most_recent_write_upper = match proto.most_recent_write_upper {
1402 Some(x) => x.into_rust()?,
1403 None => Antichain::from_elem(T::minimum()),
1404 };
1405 let debug = proto.debug.unwrap_or_default().into_rust()?;
1408 Ok(WriterState {
1409 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms.into_rust()?,
1410 lease_duration_ms: proto.lease_duration_ms.into_rust()?,
1411 most_recent_write_token,
1412 most_recent_write_upper,
1413 debug,
1414 })
1415 }
1416}
1417
1418impl RustType<ProtoHandleDebugState> for HandleDebugState {
1419 fn into_proto(&self) -> ProtoHandleDebugState {
1420 ProtoHandleDebugState {
1421 hostname: self.hostname.into_proto(),
1422 purpose: self.purpose.into_proto(),
1423 }
1424 }
1425
1426 fn from_proto(proto: ProtoHandleDebugState) -> Result<Self, TryFromProtoError> {
1427 Ok(HandleDebugState {
1428 hostname: proto.hostname,
1429 purpose: proto.purpose,
1430 })
1431 }
1432}
1433
1434impl<T: Timestamp + Codec64> RustType<ProtoHollowRun> for HollowRun<T> {
1435 fn into_proto(&self) -> ProtoHollowRun {
1436 ProtoHollowRun {
1437 parts: self.parts.into_proto(),
1438 }
1439 }
1440
1441 fn from_proto(proto: ProtoHollowRun) -> Result<Self, TryFromProtoError> {
1442 Ok(HollowRun {
1443 parts: proto.parts.into_rust()?,
1444 })
1445 }
1446}
1447
1448impl<T: Timestamp + Codec64> RustType<ProtoHollowBatch> for HollowBatch<T> {
1449 fn into_proto(&self) -> ProtoHollowBatch {
1450 let mut run_meta = self.run_meta.into_proto();
1451 let run_meta_default = RunMeta::default().into_proto();
1453 while run_meta.last() == Some(&run_meta_default) {
1454 run_meta.pop();
1455 }
1456 ProtoHollowBatch {
1457 desc: Some(self.desc.into_proto()),
1458 parts: self.parts.into_proto(),
1459 len: self.len.into_proto(),
1460 runs: self.run_splits.into_proto(),
1461 run_meta,
1462 deprecated_keys: vec![],
1463 }
1464 }
1465
1466 fn from_proto(proto: ProtoHollowBatch) -> Result<Self, TryFromProtoError> {
1467 let mut parts: Vec<RunPart<T>> = proto.parts.into_rust()?;
1468 parts.extend(proto.deprecated_keys.into_iter().map(|key| {
1471 RunPart::Single(BatchPart::Hollow(HollowBatchPart {
1472 key: PartialBatchKey(key),
1473 meta: Default::default(),
1474 encoded_size_bytes: 0,
1475 key_lower: vec![],
1476 structured_key_lower: None,
1477 stats: None,
1478 ts_rewrite: None,
1479 diffs_sum: None,
1480 format: None,
1481 schema_id: None,
1482 deprecated_schema_id: None,
1483 }))
1484 }));
1485 let run_splits: Vec<usize> = proto.runs.into_rust()?;
1487 let num_runs = if parts.is_empty() {
1488 0
1489 } else {
1490 run_splits.len() + 1
1491 };
1492 let mut run_meta: Vec<RunMeta> = proto.run_meta.into_rust()?;
1493 run_meta.resize(num_runs, RunMeta::default());
1494 Ok(HollowBatch {
1495 desc: proto.desc.into_rust_if_some("desc")?,
1496 parts,
1497 len: proto.len.into_rust()?,
1498 run_splits,
1499 run_meta,
1500 })
1501 }
1502}
1503
1504impl RustType<String> for RunId {
1505 fn into_proto(&self) -> String {
1506 self.to_string()
1507 }
1508
1509 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
1510 RunId::from_str(&proto).map_err(|_| {
1511 TryFromProtoError::InvalidPersistState(format!("invalid RunId: {}", proto))
1512 })
1513 }
1514}
1515
1516impl RustType<ProtoRunMeta> for RunMeta {
1517 fn into_proto(&self) -> ProtoRunMeta {
1518 let order = match self.order {
1519 None => ProtoRunOrder::Unknown,
1520 Some(RunOrder::Unordered) => ProtoRunOrder::Unordered,
1521 Some(RunOrder::Codec) => ProtoRunOrder::Codec,
1522 Some(RunOrder::Structured) => ProtoRunOrder::Structured,
1523 };
1524 ProtoRunMeta {
1525 order: order.into(),
1526 schema_id: self.schema.into_proto(),
1527 deprecated_schema_id: self.deprecated_schema.into_proto(),
1528 id: self.id.into_proto(),
1529 len: self.len.into_proto(),
1530 meta: self.meta.into_proto(),
1531 }
1532 }
1533
1534 fn from_proto(proto: ProtoRunMeta) -> Result<Self, TryFromProtoError> {
1535 let order = match ProtoRunOrder::try_from(proto.order)? {
1536 ProtoRunOrder::Unknown => None,
1537 ProtoRunOrder::Unordered => Some(RunOrder::Unordered),
1538 ProtoRunOrder::Codec => Some(RunOrder::Codec),
1539 ProtoRunOrder::Structured => Some(RunOrder::Structured),
1540 };
1541 Ok(Self {
1542 order,
1543 schema: proto.schema_id.into_rust()?,
1544 deprecated_schema: proto.deprecated_schema_id.into_rust()?,
1545 id: proto.id.into_rust()?,
1546 len: proto.len.into_rust()?,
1547 meta: proto.meta.into_rust()?,
1548 })
1549 }
1550}
1551
1552impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for RunPart<T> {
1553 fn into_proto(&self) -> ProtoHollowBatchPart {
1554 match self {
1555 RunPart::Single(part) => part.into_proto(),
1556 RunPart::Many(runs) => runs.into_proto(),
1557 }
1558 }
1559
1560 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1561 let run_part = if let Some(proto_hollow_batch_part::Kind::RunRef(_)) = proto.kind {
1562 RunPart::Many(proto.into_rust()?)
1563 } else {
1564 RunPart::Single(proto.into_rust()?)
1565 };
1566 Ok(run_part)
1567 }
1568}
1569
1570impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for HollowRunRef<T> {
1571 fn into_proto(&self) -> ProtoHollowBatchPart {
1572 let part = ProtoHollowBatchPart {
1573 kind: Some(proto_hollow_batch_part::Kind::RunRef(ProtoHollowRunRef {
1574 key: self.key.into_proto(),
1575 max_part_bytes: self.max_part_bytes.into_proto(),
1576 })),
1577 encoded_size_bytes: self.hollow_bytes.into_proto(),
1578 key_lower: Bytes::copy_from_slice(&self.key_lower),
1579 diffs_sum: self.diffs_sum.map(i64::from_le_bytes),
1580 key_stats: None,
1581 ts_rewrite: None,
1582 format: None,
1583 schema_id: None,
1584 structured_key_lower: self.structured_key_lower.into_proto(),
1585 deprecated_schema_id: None,
1586 metadata: BTreeMap::default(),
1587 };
1588 part
1589 }
1590
1591 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1592 let run_proto = match proto.kind {
1593 Some(proto_hollow_batch_part::Kind::RunRef(proto_ref)) => proto_ref,
1594 _ => Err(TryFromProtoError::UnknownEnumVariant(
1595 "ProtoHollowBatchPart::kind".to_string(),
1596 ))?,
1597 };
1598 Ok(Self {
1599 key: run_proto.key.into_rust()?,
1600 hollow_bytes: proto.encoded_size_bytes.into_rust()?,
1601 max_part_bytes: run_proto.max_part_bytes.into_rust()?,
1602 key_lower: proto.key_lower.to_vec(),
1603 structured_key_lower: proto.structured_key_lower.into_rust()?,
1604 diffs_sum: proto.diffs_sum.as_ref().map(|x| i64::to_le_bytes(*x)),
1605 _phantom_data: Default::default(),
1606 })
1607 }
1608}
1609
1610impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for BatchPart<T> {
1611 fn into_proto(&self) -> ProtoHollowBatchPart {
1612 match self {
1613 BatchPart::Hollow(x) => ProtoHollowBatchPart {
1614 kind: Some(proto_hollow_batch_part::Kind::Key(x.key.into_proto())),
1615 encoded_size_bytes: x.encoded_size_bytes.into_proto(),
1616 key_lower: Bytes::copy_from_slice(&x.key_lower),
1617 structured_key_lower: x.structured_key_lower.as_ref().map(|lazy| lazy.buf.clone()),
1618 key_stats: x.stats.into_proto(),
1619 ts_rewrite: x.ts_rewrite.as_ref().map(|x| x.into_proto()),
1620 diffs_sum: x.diffs_sum.as_ref().map(|x| i64::from_le_bytes(*x)),
1621 format: x.format.map(|f| f.into_proto()),
1622 schema_id: x.schema_id.into_proto(),
1623 deprecated_schema_id: x.deprecated_schema_id.into_proto(),
1624 metadata: BTreeMap::default(),
1625 },
1626 BatchPart::Inline {
1627 updates,
1628 ts_rewrite,
1629 schema_id,
1630 deprecated_schema_id,
1631 } => ProtoHollowBatchPart {
1632 kind: Some(proto_hollow_batch_part::Kind::Inline(updates.into_proto())),
1633 encoded_size_bytes: 0,
1634 key_lower: Bytes::new(),
1635 structured_key_lower: None,
1636 key_stats: None,
1637 ts_rewrite: ts_rewrite.as_ref().map(|x| x.into_proto()),
1638 diffs_sum: None,
1639 format: None,
1640 schema_id: schema_id.into_proto(),
1641 deprecated_schema_id: deprecated_schema_id.into_proto(),
1642 metadata: BTreeMap::default(),
1643 },
1644 }
1645 }
1646
1647 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1648 let ts_rewrite = match proto.ts_rewrite {
1649 Some(ts_rewrite) => Some(ts_rewrite.into_rust()?),
1650 None => None,
1651 };
1652 let schema_id = proto.schema_id.into_rust()?;
1653 let deprecated_schema_id = proto.deprecated_schema_id.into_rust()?;
1654 match proto.kind {
1655 Some(proto_hollow_batch_part::Kind::Key(key)) => {
1656 Ok(BatchPart::Hollow(HollowBatchPart {
1657 key: key.into_rust()?,
1658 meta: proto.metadata.into_rust()?,
1659 encoded_size_bytes: proto.encoded_size_bytes.into_rust()?,
1660 key_lower: proto.key_lower.into(),
1661 structured_key_lower: proto.structured_key_lower.into_rust()?,
1662 stats: proto.key_stats.into_rust()?,
1663 ts_rewrite,
1664 diffs_sum: proto.diffs_sum.map(i64::to_le_bytes),
1665 format: proto.format.map(|f| f.into_rust()).transpose()?,
1666 schema_id,
1667 deprecated_schema_id,
1668 }))
1669 }
1670 Some(proto_hollow_batch_part::Kind::Inline(x)) => {
1671 if proto.encoded_size_bytes != 0
1675 || !proto.key_lower.is_empty()
1676 || proto.key_stats.is_some()
1677 || proto.diffs_sum.is_some()
1678 {
1679 return Err(TryFromProtoError::InvalidPersistState(
1680 "inline ProtoHollowBatchPart has hollow-part fields set".into(),
1681 ));
1682 }
1683 let updates = LazyInlineBatchPart(x.into_rust()?);
1684 Ok(BatchPart::Inline {
1685 updates,
1686 ts_rewrite,
1687 schema_id,
1688 deprecated_schema_id,
1689 })
1690 }
1691 _ => Err(TryFromProtoError::unknown_enum_variant(
1692 "ProtoHollowBatchPart::kind",
1693 )),
1694 }
1695 }
1696}
1697
1698impl RustType<proto_hollow_batch_part::Format> for BatchColumnarFormat {
1699 fn into_proto(&self) -> proto_hollow_batch_part::Format {
1700 match self {
1701 BatchColumnarFormat::Row => proto_hollow_batch_part::Format::Row(()),
1702 BatchColumnarFormat::Both(version) => {
1703 proto_hollow_batch_part::Format::RowAndColumnar((*version).cast_into())
1704 }
1705 BatchColumnarFormat::Structured => proto_hollow_batch_part::Format::Structured(()),
1706 }
1707 }
1708
1709 fn from_proto(proto: proto_hollow_batch_part::Format) -> Result<Self, TryFromProtoError> {
1710 let format = match proto {
1711 proto_hollow_batch_part::Format::Row(_) => BatchColumnarFormat::Row,
1712 proto_hollow_batch_part::Format::RowAndColumnar(version) => {
1713 BatchColumnarFormat::Both(version.cast_into())
1714 }
1715 proto_hollow_batch_part::Format::Structured(_) => BatchColumnarFormat::Structured,
1716 };
1717 Ok(format)
1718 }
1719}
1720
1721#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1726pub struct LazyPartStats {
1727 key: LazyProto<ProtoStructStats>,
1728}
1729
1730impl Debug for LazyPartStats {
1731 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1732 f.debug_tuple("LazyPartStats")
1733 .field(&self.decode())
1734 .finish()
1735 }
1736}
1737
1738impl LazyPartStats {
1739 pub(crate) fn encode(x: &PartStats, map_proto: impl FnOnce(&mut ProtoStructStats)) -> Self {
1740 let PartStats { key } = x;
1741 let mut proto_stats = ProtoStructStats::from_rust(key);
1742 map_proto(&mut proto_stats);
1743 LazyPartStats {
1744 key: LazyProto::from(&proto_stats),
1745 }
1746 }
1747 pub fn decode(&self) -> PartStats {
1752 let key = self.key.decode().expect("valid proto");
1753 PartStats {
1754 key: key.into_rust().expect("valid stats"),
1755 }
1756 }
1757}
1758
1759impl RustType<Bytes> for LazyPartStats {
1760 fn into_proto(&self) -> Bytes {
1761 let LazyPartStats { key } = self;
1762 key.into_proto()
1763 }
1764
1765 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1766 Ok(LazyPartStats {
1767 key: proto.into_rust()?,
1768 })
1769 }
1770}
1771
1772#[cfg(test)]
1773pub(crate) fn any_some_lazy_part_stats() -> impl Strategy<Value = Option<LazyPartStats>> {
1774 proptest::prelude::any::<LazyPartStats>().prop_map(Some)
1775}
1776
1777#[allow(unused_parens)]
1778impl Arbitrary for LazyPartStats {
1779 type Parameters = ();
1780 type Strategy =
1781 proptest::strategy::Map<(<PartStats as Arbitrary>::Strategy), fn((PartStats)) -> Self>;
1782
1783 fn arbitrary_with(_: ()) -> Self::Strategy {
1784 Strategy::prop_map((proptest::prelude::any::<PartStats>()), |(x)| {
1785 LazyPartStats::encode(&x, |_| {})
1786 })
1787 }
1788}
1789
1790impl ProtoInlineBatchPart {
1791 pub(crate) fn into_rust<T: Timestamp + Codec64>(
1792 lgbytes: &ColumnarMetrics,
1793 proto: Self,
1794 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1795 let updates = proto
1796 .updates
1797 .ok_or_else(|| TryFromProtoError::missing_field("ProtoInlineBatchPart::updates"))?;
1798 let updates = BlobTraceUpdates::from_proto(lgbytes, updates)?;
1799
1800 Ok(BlobTraceBatchPart {
1801 desc: proto.desc.into_rust_if_some("ProtoInlineBatchPart::desc")?,
1802 index: proto.index.into_rust()?,
1803 updates,
1804 })
1805 }
1806}
1807
1808#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1810pub struct LazyInlineBatchPart(LazyProto<ProtoInlineBatchPart>);
1811
1812impl From<&ProtoInlineBatchPart> for LazyInlineBatchPart {
1813 fn from(value: &ProtoInlineBatchPart) -> Self {
1814 LazyInlineBatchPart(value.into())
1815 }
1816}
1817
1818impl Serialize for LazyInlineBatchPart {
1819 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1820 let proto = self.0.decode().expect("valid proto");
1823 let mut s = s.serialize_struct("InlineBatchPart", 3)?;
1824 let () = s.serialize_field("desc", &proto.desc)?;
1825 let () = s.serialize_field("index", &proto.index)?;
1826 let () = s.serialize_field("updates[len]", &proto.updates.map_or(0, |x| x.len))?;
1827 s.end()
1828 }
1829}
1830
1831impl LazyInlineBatchPart {
1832 pub(crate) fn encoded_size_bytes(&self) -> usize {
1833 self.0.buf.len()
1834 }
1835
1836 pub fn decode<T: Timestamp + Codec64>(
1842 &self,
1843 lgbytes: &ColumnarMetrics,
1844 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1845 let proto = self.0.decode().expect("valid proto");
1846 ProtoInlineBatchPart::into_rust(lgbytes, proto)
1847 }
1848}
1849
1850impl RustType<Bytes> for LazyInlineBatchPart {
1851 fn into_proto(&self) -> Bytes {
1852 self.0.into_proto()
1853 }
1854
1855 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1856 Ok(LazyInlineBatchPart(proto.into_rust()?))
1857 }
1858}
1859
1860impl RustType<ProtoHollowRollup> for HollowRollup {
1861 fn into_proto(&self) -> ProtoHollowRollup {
1862 ProtoHollowRollup {
1863 key: self.key.into_proto(),
1864 encoded_size_bytes: self.encoded_size_bytes.into_proto(),
1865 }
1866 }
1867
1868 fn from_proto(proto: ProtoHollowRollup) -> Result<Self, TryFromProtoError> {
1869 Ok(HollowRollup {
1870 key: proto.key.into_rust()?,
1871 encoded_size_bytes: proto.encoded_size_bytes.into_rust()?,
1872 })
1873 }
1874}
1875
1876impl RustType<ProtoActiveRollup> for ActiveRollup {
1877 fn into_proto(&self) -> ProtoActiveRollup {
1878 ProtoActiveRollup {
1879 start_ms: self.start_ms,
1880 seqno: self.seqno.into_proto(),
1881 }
1882 }
1883
1884 fn from_proto(proto: ProtoActiveRollup) -> Result<Self, TryFromProtoError> {
1885 Ok(ActiveRollup {
1886 start_ms: proto.start_ms,
1887 seqno: proto.seqno.into_rust()?,
1888 })
1889 }
1890}
1891
1892impl RustType<ProtoActiveGc> for ActiveGc {
1893 fn into_proto(&self) -> ProtoActiveGc {
1894 ProtoActiveGc {
1895 start_ms: self.start_ms,
1896 seqno: self.seqno.into_proto(),
1897 }
1898 }
1899
1900 fn from_proto(proto: ProtoActiveGc) -> Result<Self, TryFromProtoError> {
1901 Ok(ActiveGc {
1902 start_ms: proto.start_ms,
1903 seqno: proto.seqno.into_rust()?,
1904 })
1905 }
1906}
1907
1908impl<T: Timestamp + Codec64> RustType<ProtoU64Description> for Description<T> {
1909 fn into_proto(&self) -> ProtoU64Description {
1910 ProtoU64Description {
1911 lower: Some(self.lower().into_proto()),
1912 upper: Some(self.upper().into_proto()),
1913 since: Some(self.since().into_proto()),
1914 }
1915 }
1916
1917 fn from_proto(proto: ProtoU64Description) -> Result<Self, TryFromProtoError> {
1918 let lower: Antichain<T> = proto.lower.into_rust_if_some("lower")?;
1919 if lower.elements().is_empty() {
1922 return Err(TryFromProtoError::InvalidPersistState(
1923 "ProtoU64Description has an empty lower frontier".into(),
1924 ));
1925 }
1926 Ok(Description::new(
1927 lower,
1928 proto.upper.into_rust_if_some("upper")?,
1929 proto.since.into_rust_if_some("since")?,
1930 ))
1931 }
1932}
1933
1934impl<T: Timestamp + Codec64> RustType<ProtoU64Antichain> for Antichain<T> {
1935 fn into_proto(&self) -> ProtoU64Antichain {
1936 ProtoU64Antichain {
1937 elements: self
1938 .elements()
1939 .iter()
1940 .map(|x| i64::from_le_bytes(T::encode(x)))
1941 .collect(),
1942 }
1943 }
1944
1945 fn from_proto(proto: ProtoU64Antichain) -> Result<Self, TryFromProtoError> {
1946 let elements = proto
1947 .elements
1948 .iter()
1949 .map(|x| T::decode(x.to_le_bytes()))
1950 .collect::<Vec<_>>();
1951 Ok(Antichain::from(elements))
1952 }
1953}
1954
1955#[cfg(test)]
1956mod tests {
1957 use mz_ore::assert_none;
1958
1959 use bytes::Bytes;
1960 use mz_build_info::DUMMY_BUILD_INFO;
1961 use mz_dyncfg::ConfigUpdates;
1962 use mz_ore::assert_err;
1963 use mz_ore::cast::CastFrom;
1964 use mz_persist::location::SeqNo;
1965 use proptest::prelude::*;
1966
1967 use crate::ShardId;
1968 use crate::internal::paths::PartialRollupKey;
1969 use crate::internal::state::tests::any_state;
1970 use crate::internal::state::{BatchPart, HandleDebugState};
1971 use crate::internal::state_diff::StateDiff;
1972 use crate::tests::new_test_client_cache;
1973
1974 use super::*;
1975
1976 #[mz_ore::test]
1977 fn rollup_inline_batch_part_with_hollow_fields_is_error() {
1978 use mz_proto::ProtoType;
1982 use prost::Message;
1983 let bytes: &[u8] = &[
1984 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x22, 0x06, 0x5a, 0x00, 0x10, 0x02, 0x2a, 0x00,
1985 0x22, 0x00, 0x22, 0x00, 0x22, 0x00,
1986 ];
1987 let proto = crate::internal::state::ProtoRollup::decode(bytes)
1988 .expect("crash input decodes as a proto");
1989 let result: Result<Rollup<u64>, _> = proto.into_rust();
1990 assert_err!(result);
1991 }
1992
1993 #[mz_ore::test]
1994 fn rollup_batch_with_empty_lower_frontier_is_error() {
1995 use mz_proto::ProtoType;
1999 use prost::Message;
2000 let bytes: &[u8] = &[
2001 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x0a, 0x00, 0x12, 0x00, 0x1a, 0x00,
2002 0x12, 0x00, 0x32, 0x00, 0x22, 0x00,
2003 ];
2004 let proto = crate::internal::state::ProtoRollup::decode(bytes)
2005 .expect("crash input decodes as a proto");
2006 let result: Result<Rollup<u64>, _> = proto.into_rust();
2007 assert_err!(result);
2008 }
2009
2010 #[mz_ore::test]
2011 fn metadata_map() {
2012 const COUNT: MetadataKey<u64> = MetadataKey::new("count");
2013
2014 let mut map = MetadataMap::default();
2015 map.set(COUNT, 100);
2016 let mut map = MetadataMap::from_proto(map.into_proto()).unwrap();
2017 assert_eq!(map.get(COUNT), Some(100));
2018
2019 const ANTICHAIN: MetadataKey<Antichain<u64>, ProtoU64Antichain> =
2020 MetadataKey::new("antichain");
2021 assert_none!(map.get(ANTICHAIN));
2022
2023 map.set(ANTICHAIN, Antichain::from_elem(30));
2024 let map = MetadataMap::from_proto(map.into_proto()).unwrap();
2025 assert_eq!(map.get(COUNT), Some(100));
2026 assert_eq!(map.get(ANTICHAIN), Some(Antichain::from_elem(30)));
2027 }
2028
2029 #[mz_ore::test]
2030 fn applier_version_state() {
2031 let v1 = semver::Version::new(1, 0, 0);
2032 let v2 = semver::Version::new(2, 0, 0);
2033 let v3 = semver::Version::new(3, 0, 0);
2034
2035 let shard_id = ShardId::new();
2037 let state = TypedState::<(), (), u64, i64>::new(v2.clone(), shard_id, "".to_owned(), 0);
2038 let rollup =
2039 Rollup::from_untyped_state_without_diffs(state.clone_for_rollup().into()).into_proto();
2040 let mut buf = Vec::new();
2041 rollup.encode(&mut buf).expect("serializable");
2042 let bytes = Bytes::from(buf);
2043
2044 assert_eq!(
2046 UntypedState::<u64>::decode(&v2, bytes.clone())
2047 .check_codecs(&shard_id)
2048 .as_ref(),
2049 Ok(&state)
2050 );
2051 assert_eq!(
2052 UntypedState::<u64>::decode(&v3, bytes.clone())
2053 .check_codecs(&shard_id)
2054 .as_ref(),
2055 Ok(&state)
2056 );
2057
2058 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| UntypedState::<u64>::decode(&v1, bytes.clone()));
2063 assert_err!(v1_res);
2064 }
2065
2066 #[mz_ore::test]
2067 fn applier_version_state_diff() {
2068 let v1 = semver::Version::new(1, 0, 0);
2069 let v2 = semver::Version::new(2, 0, 0);
2070 let v3 = semver::Version::new(3, 0, 0);
2071
2072 let diff = StateDiff::<u64>::new(
2074 v2.clone(),
2075 SeqNo(0),
2076 SeqNo(1),
2077 2,
2078 PartialRollupKey("rollup".into()),
2079 );
2080 let mut buf = Vec::new();
2081 diff.encode(&mut buf);
2082 let bytes = Bytes::from(buf);
2083
2084 assert_eq!(StateDiff::decode(&v2, bytes.clone()), diff);
2086 assert_eq!(StateDiff::decode(&v3, bytes.clone()), diff);
2087
2088 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| StateDiff::<u64>::decode(&v1, bytes));
2093 assert_err!(v1_res);
2094 }
2095
2096 #[mz_ore::test]
2097 fn hollow_batch_migration_keys() {
2098 let x = HollowBatch::new_run(
2099 Description::new(
2100 Antichain::from_elem(1u64),
2101 Antichain::from_elem(2u64),
2102 Antichain::from_elem(3u64),
2103 ),
2104 vec![RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2105 key: PartialBatchKey("a".into()),
2106 meta: Default::default(),
2107 encoded_size_bytes: 5,
2108 key_lower: vec![],
2109 structured_key_lower: None,
2110 stats: None,
2111 ts_rewrite: None,
2112 diffs_sum: None,
2113 format: None,
2114 schema_id: None,
2115 deprecated_schema_id: None,
2116 }))],
2117 4,
2118 );
2119 let mut old = x.into_proto();
2120 old.deprecated_keys = vec!["b".into()];
2122 let mut expected = x;
2126 expected
2131 .parts
2132 .push(RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2133 key: PartialBatchKey("b".into()),
2134 meta: Default::default(),
2135 encoded_size_bytes: 0,
2136 key_lower: vec![],
2137 structured_key_lower: None,
2138 stats: None,
2139 ts_rewrite: None,
2140 diffs_sum: None,
2141 format: None,
2142 schema_id: None,
2143 deprecated_schema_id: None,
2144 })));
2145 assert_eq!(<HollowBatch<u64>>::from_proto(old).unwrap(), expected);
2146 }
2147
2148 #[mz_ore::test]
2149 fn reader_state_migration_lease_duration() {
2150 let x = LeasedReaderState {
2151 seqno: SeqNo(1),
2152 since: Antichain::from_elem(2u64),
2153 last_heartbeat_timestamp_ms: 3,
2154 debug: HandleDebugState {
2155 hostname: "host".to_owned(),
2156 purpose: "purpose".to_owned(),
2157 },
2158 lease_duration_ms: 0,
2160 };
2161 let old = x.into_proto();
2162 let mut expected = x;
2163 expected.lease_duration_ms =
2166 u64::try_from(READER_LEASE_DURATION.default().as_millis()).unwrap();
2167 assert_eq!(<LeasedReaderState<u64>>::from_proto(old).unwrap(), expected);
2168 }
2169
2170 #[mz_ore::test]
2171 fn writer_state_migration_most_recent_write() {
2172 let proto = ProtoWriterState {
2173 last_heartbeat_timestamp_ms: 1,
2174 lease_duration_ms: 2,
2175 most_recent_write_token: "".into(),
2178 most_recent_write_upper: None,
2179 debug: Some(ProtoHandleDebugState {
2180 hostname: "host".to_owned(),
2181 purpose: "purpose".to_owned(),
2182 }),
2183 };
2184 let expected = WriterState {
2185 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms,
2186 lease_duration_ms: proto.lease_duration_ms,
2187 most_recent_write_token: IdempotencyToken::SENTINEL,
2188 most_recent_write_upper: Antichain::from_elem(0),
2189 debug: HandleDebugState {
2190 hostname: "host".to_owned(),
2191 purpose: "purpose".to_owned(),
2192 },
2193 };
2194 assert_eq!(<WriterState<u64>>::from_proto(proto).unwrap(), expected);
2195 }
2196
2197 #[mz_ore::test]
2198 fn state_migration_rollups() {
2199 let r1 = HollowRollup {
2200 key: PartialRollupKey("foo".to_owned()),
2201 encoded_size_bytes: None,
2202 };
2203 let r2 = HollowRollup {
2204 key: PartialRollupKey("bar".to_owned()),
2205 encoded_size_bytes: Some(2),
2206 };
2207 let shard_id = ShardId::new();
2208 let mut state = TypedState::<(), (), u64, i64>::new(
2209 DUMMY_BUILD_INFO.semver_version(),
2210 shard_id,
2211 "host".to_owned(),
2212 0,
2213 );
2214 state.state.collections.rollups.insert(SeqNo(2), r2.clone());
2215 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2216
2217 proto.deprecated_rollups.insert(1, r1.key.0.clone());
2219
2220 let state: Rollup<u64> = proto.into_rust().unwrap();
2221 let state = state.state;
2222 let state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2223 let expected = vec![(SeqNo(1), r1), (SeqNo(2), r2)];
2224 assert_eq!(
2225 state
2226 .state
2227 .collections
2228 .rollups
2229 .into_iter()
2230 .collect::<Vec<_>>(),
2231 expected
2232 );
2233 }
2234
2235 #[mz_ore::test]
2242 fn rollup_proto_with_diffs_but_no_rollups_is_rejected() {
2243 let shard_id = ShardId::new();
2244 let mut state = TypedState::<(), (), u64, i64>::new(
2245 DUMMY_BUILD_INFO.semver_version(),
2246 shard_id,
2247 "host".to_owned(),
2248 0,
2249 );
2250 let seqno = state.state.seqno;
2253 state.state.collections.rollups.insert(
2254 seqno,
2255 HollowRollup {
2256 key: PartialRollupKey("foo".to_owned()),
2257 encoded_size_bytes: None,
2258 },
2259 );
2260 let mut proto = Rollup::from(state.into(), Vec::new()).into_proto();
2261
2262 proto.rollups.clear();
2264 proto.deprecated_rollups.clear();
2265
2266 let result: Result<Rollup<u64>, _> = proto.into_rust();
2267 assert!(
2268 result.is_err(),
2269 "a rollup proto with diffs but no rollups must error, not panic"
2270 );
2271 }
2272
2273 fn rollup_proto_with_trace(update_trace: impl FnOnce(&mut ProtoTrace)) -> ProtoRollup {
2275 let state = TypedState::<(), (), u64, i64>::new(
2276 DUMMY_BUILD_INFO.semver_version(),
2277 ShardId::new(),
2278 "host".to_owned(),
2279 0,
2280 );
2281 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2282 update_trace(proto.trace.as_mut().expect("fresh state has a trace"));
2283 proto
2284 }
2285
2286 fn u64_desc_proto(lower: u64, upper: u64, since: u64) -> ProtoU64Description {
2287 Description::new(
2288 Antichain::from_elem(lower),
2289 Antichain::from_elem(upper),
2290 Antichain::from_elem(since),
2291 )
2292 .into_proto()
2293 }
2294
2295 fn legacy_batch_proto(lower: u64, upper: u64, since: u64) -> ProtoHollowBatch {
2296 ProtoHollowBatch {
2297 desc: Some(u64_desc_proto(lower, upper, since)),
2298 ..Default::default()
2299 }
2300 }
2301
2302 fn rollup_decode_err(proto: ProtoRollup) -> String {
2303 let result: Result<Rollup<u64>, _> = proto.into_rust();
2304 match result {
2305 Ok(_) => panic!("crafted rollup proto must fail to decode"),
2306 Err(err) => err.to_string(),
2307 }
2308 }
2309
2310 #[mz_ore::test]
2317 fn rollup_proto_with_noncontiguous_legacy_batches_is_rejected() {
2318 let proto = rollup_proto_with_trace(|trace| {
2319 trace.legacy_batches.push(legacy_batch_proto(39, 40, 0));
2320 });
2321 let err = rollup_decode_err(proto);
2322 assert!(err.contains("legacy batch lower"), "{err}");
2323 }
2324
2325 #[mz_ore::test]
2329 fn rollup_proto_with_empty_range_legacy_batch_is_rejected() {
2330 let proto = rollup_proto_with_trace(|trace| {
2331 trace.legacy_batches.push(legacy_batch_proto(0, 0, 0));
2332 });
2333 let err = rollup_decode_err(proto);
2334 assert!(err.contains("empty time range"), "{err}");
2335 }
2336
2337 #[mz_ore::test]
2341 fn rollup_proto_with_batch_since_past_trace_since_is_rejected() {
2342 let proto = rollup_proto_with_trace(|trace| {
2343 trace.legacy_batches.push(legacy_batch_proto(0, 1, 5));
2344 });
2345 let err = rollup_decode_err(proto);
2346 assert!(err.contains("past the spine since"), "{err}");
2347 }
2348
2349 #[mz_ore::test]
2353 fn rollup_proto_with_absurd_batch_len_is_rejected() {
2354 let proto = rollup_proto_with_trace(|trace| {
2355 trace.legacy_batches.push(ProtoHollowBatch {
2356 desc: Some(u64_desc_proto(0, 1, 0)),
2357 len: u64::MAX,
2358 ..Default::default()
2359 });
2360 });
2361 let err = rollup_decode_err(proto);
2362 assert!(err.contains("maximum trace size"), "{err}");
2363 }
2364
2365 #[mz_ore::test]
2368 fn rollup_proto_with_absurd_spine_level_is_rejected() {
2369 let proto = rollup_proto_with_trace(|trace| {
2370 trace.spine_batches.push(ProtoIdSpineBatch {
2371 id: Some(SpineId(0, 1).into_proto()),
2372 batch: Some(ProtoSpineBatch {
2373 level: u64::MAX,
2374 desc: Some(u64_desc_proto(0, 1, 0)),
2375 parts: vec![],
2376 descs: vec![],
2377 }),
2378 });
2379 });
2380 let err = rollup_decode_err(proto);
2381 assert!(err.contains("exceeds the maximum"), "{err}");
2382 }
2383
2384 #[mz_ore::test]
2388 fn rollup_proto_with_partless_spine_batch_is_rejected() {
2389 let proto = rollup_proto_with_trace(|trace| {
2390 trace.spine_batches.push(ProtoIdSpineBatch {
2391 id: Some(SpineId(0, 1).into_proto()),
2392 batch: Some(ProtoSpineBatch {
2393 level: 0,
2394 desc: Some(u64_desc_proto(0, 1, 0)),
2395 parts: vec![],
2396 descs: vec![],
2397 }),
2398 });
2399 });
2400 let err = rollup_decode_err(proto);
2401 assert!(err.contains("do not tile"), "{err}");
2402 }
2403
2404 #[mz_ore::test]
2410 fn rollup_proto_with_noncontiguous_spine_parts_is_rejected() {
2411 let outer = SpineId(0, 3);
2412 let part_ids = [SpineId(0, 2), SpineId(1, 3)];
2413 let proto = rollup_proto_with_trace(|trace| {
2414 trace.spine_batches.push(ProtoIdSpineBatch {
2415 id: Some(outer.into_proto()),
2416 batch: Some(ProtoSpineBatch {
2417 level: 0,
2418 desc: Some(u64_desc_proto(0, 3, 0)),
2419 parts: part_ids.iter().map(|id| id.into_proto()).collect(),
2420 descs: vec![],
2421 }),
2422 });
2423 for id in part_ids {
2424 trace.hollow_batches.push(ProtoIdHollowBatch {
2425 id: Some(id.into_proto()),
2426 batch: Some(legacy_batch_proto(0, 1, 0)),
2427 });
2428 }
2429 });
2430 let err = rollup_decode_err(proto);
2431 assert!(err.contains("do not tile"), "{err}");
2432 }
2433
2434 #[mz_ore::test]
2438 fn rollup_proto_with_overfull_spine_level_is_rejected() {
2439 let proto = rollup_proto_with_trace(|trace| {
2440 for i in 0..3u64 {
2441 let id = SpineId(usize::cast_from(i), usize::cast_from(i + 1));
2442 trace.spine_batches.push(ProtoIdSpineBatch {
2443 id: Some(id.into_proto()),
2444 batch: Some(ProtoSpineBatch {
2445 level: 0,
2446 desc: Some(u64_desc_proto(i, i + 1, 0)),
2447 parts: vec![id.into_proto()],
2448 descs: vec![],
2449 }),
2450 });
2451 trace.hollow_batches.push(ProtoIdHollowBatch {
2452 id: Some(id.into_proto()),
2453 batch: Some(legacy_batch_proto(i, i + 1, 0)),
2454 });
2455 }
2456 });
2457 let err = rollup_decode_err(proto);
2458 assert!(err.contains("full layer"), "{err}");
2459 }
2460
2461 #[mz_persist_proc::test(tokio::test)]
2462 #[cfg_attr(miri, ignore)] async fn state_diff_migration_rollups(dyncfgs: ConfigUpdates) {
2464 let r1_rollup = HollowRollup {
2465 key: PartialRollupKey("foo".to_owned()),
2466 encoded_size_bytes: None,
2467 };
2468 let r1 = StateFieldDiff {
2469 key: SeqNo(1),
2470 val: StateFieldValDiff::Insert(r1_rollup.clone()),
2471 };
2472 let r2_rollup = HollowRollup {
2473 key: PartialRollupKey("bar".to_owned()),
2474 encoded_size_bytes: Some(2),
2475 };
2476 let r2 = StateFieldDiff {
2477 key: SeqNo(2),
2478 val: StateFieldValDiff::Insert(r2_rollup.clone()),
2479 };
2480 let r3_rollup = HollowRollup {
2481 key: PartialRollupKey("baz".to_owned()),
2482 encoded_size_bytes: None,
2483 };
2484 let r3 = StateFieldDiff {
2485 key: SeqNo(3),
2486 val: StateFieldValDiff::Delete(r3_rollup.clone()),
2487 };
2488 let mut diff = StateDiff::<u64>::new(
2489 DUMMY_BUILD_INFO.semver_version(),
2490 SeqNo(4),
2491 SeqNo(5),
2492 0,
2493 PartialRollupKey("ignored".to_owned()),
2494 );
2495 diff.rollups.push(r2.clone());
2496 diff.rollups.push(r3.clone());
2497 let mut diff_proto = diff.into_proto();
2498
2499 let field_diffs = std::mem::take(&mut diff_proto.field_diffs).unwrap();
2500 let mut field_diffs_writer = field_diffs.into_writer();
2501
2502 field_diffs_into_proto(
2504 ProtoStateField::DeprecatedRollups,
2505 &[StateFieldDiff {
2506 key: r1.key,
2507 val: StateFieldValDiff::Insert(r1_rollup.key.clone()),
2508 }],
2509 &mut field_diffs_writer,
2510 );
2511
2512 assert_none!(diff_proto.field_diffs);
2513 diff_proto.field_diffs = Some(field_diffs_writer.into_proto());
2514
2515 let diff = StateDiff::<u64>::from_proto(diff_proto.clone()).unwrap();
2516 assert_eq!(
2517 diff.rollups.into_iter().collect::<Vec<_>>(),
2518 vec![r2, r3, r1]
2519 );
2520
2521 let shard_id = ShardId::new();
2524 let mut state = TypedState::<(), (), u64, i64>::new(
2525 DUMMY_BUILD_INFO.semver_version(),
2526 shard_id,
2527 "host".to_owned(),
2528 0,
2529 );
2530 state.state.seqno = SeqNo(4);
2531 let mut rollup = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2532 rollup
2533 .deprecated_rollups
2534 .insert(3, r3_rollup.key.into_proto());
2535 let state: Rollup<u64> = rollup.into_rust().unwrap();
2536 let state = state.state;
2537 let mut state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2538 let cache = new_test_client_cache(&dyncfgs);
2539 let encoded_diff = VersionedData {
2540 seqno: SeqNo(5),
2541 data: diff_proto.encode_to_vec().into(),
2542 };
2543 state.apply_encoded_diffs(cache.cfg(), &cache.metrics, std::iter::once(&encoded_diff));
2544 assert_eq!(
2545 state
2546 .state
2547 .collections
2548 .rollups
2549 .into_iter()
2550 .collect::<Vec<_>>(),
2551 vec![(SeqNo(1), r1_rollup), (SeqNo(2), r2_rollup)]
2552 );
2553 }
2554
2555 #[mz_ore::test]
2556 #[cfg_attr(miri, ignore)] fn state_proto_roundtrip() {
2558 fn testcase<T: Timestamp + Lattice + Codec64>(state: State<T>) {
2559 let before = UntypedState {
2560 key_codec: <() as Codec>::codec_name(),
2561 val_codec: <() as Codec>::codec_name(),
2562 ts_codec: <T as Codec64>::codec_name(),
2563 diff_codec: <i64 as Codec64>::codec_name(),
2564 state,
2565 };
2566 let proto = Rollup::from_untyped_state_without_diffs(before.clone()).into_proto();
2567 let after: Rollup<T> = proto.into_rust().unwrap();
2568 let after = after.state;
2569 assert_eq!(before, after);
2570 }
2571
2572 proptest!(|(state in any_state::<u64>(0..3))| testcase(state));
2573 }
2574
2575 #[mz_ore::test]
2576 fn check_data_versions() {
2577 #[track_caller]
2578 fn testcase(code: &str, data: &str, expected: Result<(), ()>) {
2579 let code = Version::parse(code).unwrap();
2580 let data = Version::parse(data).unwrap();
2581 #[allow(clippy::disallowed_methods)]
2582 let actual = cfg::code_can_write_data(&code, &data)
2583 .then_some(())
2584 .ok_or(());
2585 assert_eq!(actual, expected, "data at {data} read by code {code}");
2586 }
2587
2588 testcase("0.160.0-dev", "0.160.0-dev", Ok(()));
2589 testcase("0.160.0-dev", "0.160.0", Err(()));
2590 testcase("0.160.0-dev", "0.161.0-dev", Err(()));
2593 testcase("0.160.0-dev", "0.161.0", Err(()));
2594 testcase("0.160.0-dev", "0.162.0-dev", Err(()));
2595 testcase("0.160.0-dev", "0.162.0", Err(()));
2596 testcase("0.160.0-dev", "0.163.0-dev", Err(()));
2597
2598 testcase("0.160.0", "0.158.0-dev", Ok(()));
2599 testcase("0.160.0", "0.158.0", Ok(()));
2600 testcase("0.160.0", "0.159.0-dev", Ok(()));
2601 testcase("0.160.0", "0.159.0", Ok(()));
2602 testcase("0.160.0", "0.160.0-dev", Ok(()));
2603 testcase("0.160.0", "0.160.0", Ok(()));
2604
2605 testcase("0.160.0", "0.161.0-dev", Err(()));
2606 testcase("0.160.0", "0.161.0", Err(()));
2607 testcase("0.160.0", "0.161.1", Err(()));
2608 testcase("0.160.0", "0.161.1000000", Err(()));
2609 testcase("0.160.0", "0.162.0-dev", Err(()));
2610 testcase("0.160.0", "0.162.0", Err(()));
2611 testcase("0.160.0", "0.163.0-dev", Err(()));
2612
2613 testcase("0.160.1", "0.159.0", Ok(()));
2614 testcase("0.160.1", "0.160.0", Ok(()));
2615 testcase("0.160.1", "0.161.0", Err(()));
2616 testcase("0.160.1", "0.161.1", Err(()));
2617 testcase("0.160.1", "0.161.100", Err(()));
2618 testcase("0.160.0", "0.160.1", Err(()));
2619
2620 testcase("0.160.1", "26.0.0", Err(()));
2621 testcase("26.0.0", "0.160.1", Ok(()));
2622 testcase("26.2.0", "0.160.1", Ok(()));
2623 testcase("26.200.200", "0.160.1", Ok(()));
2624
2625 testcase("27.0.0", "0.160.1", Err(()));
2626 testcase("27.0.0", "0.16000.1", Err(()));
2627 testcase("27.0.0", "26.0.1", Ok(()));
2628 testcase("27.1000.100", "26.0.1", Ok(()));
2629 testcase("28.0.0", "26.0.1", Err(()));
2630 testcase("28.0.0", "26.1000.1", Err(()));
2631 testcase("28.0.0", "27.0.0", Ok(()));
2632 }
2633}