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 let mut f = f.debug_tuple("LazyPartStats");
1733 match self.try_decode() {
1739 Ok(stats) => f.field(&stats).finish(),
1740 Err(err) => f.field(&format_args!("<undecodable: {err}>")).finish(),
1741 }
1742 }
1743}
1744
1745impl LazyPartStats {
1746 pub(crate) fn encode(x: &PartStats, map_proto: impl FnOnce(&mut ProtoStructStats)) -> Self {
1747 let PartStats { key } = x;
1748 let mut proto_stats = ProtoStructStats::from_rust(key);
1749 map_proto(&mut proto_stats);
1750 LazyPartStats {
1751 key: LazyProto::from(&proto_stats),
1752 }
1753 }
1754 pub fn decode(&self) -> PartStats {
1762 self.try_decode().expect("valid stats")
1763 }
1764
1765 pub fn try_decode(&self) -> Result<PartStats, TryFromProtoError> {
1771 let key = self
1772 .key
1773 .decode()
1774 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
1775 Ok(PartStats {
1776 key: key.into_rust()?,
1777 })
1778 }
1779}
1780
1781impl RustType<Bytes> for LazyPartStats {
1782 fn into_proto(&self) -> Bytes {
1783 let LazyPartStats { key } = self;
1784 key.into_proto()
1785 }
1786
1787 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1788 Ok(LazyPartStats {
1789 key: proto.into_rust()?,
1790 })
1791 }
1792}
1793
1794#[cfg(test)]
1795pub(crate) fn any_some_lazy_part_stats() -> impl Strategy<Value = Option<LazyPartStats>> {
1796 proptest::prelude::any::<LazyPartStats>().prop_map(Some)
1797}
1798
1799#[allow(unused_parens)]
1800impl Arbitrary for LazyPartStats {
1801 type Parameters = ();
1802 type Strategy =
1803 proptest::strategy::Map<(<PartStats as Arbitrary>::Strategy), fn((PartStats)) -> Self>;
1804
1805 fn arbitrary_with(_: ()) -> Self::Strategy {
1806 Strategy::prop_map((proptest::prelude::any::<PartStats>()), |(x)| {
1807 LazyPartStats::encode(&x, |_| {})
1808 })
1809 }
1810}
1811
1812impl ProtoInlineBatchPart {
1813 pub(crate) fn into_rust<T: Timestamp + Codec64>(
1814 lgbytes: &ColumnarMetrics,
1815 proto: Self,
1816 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1817 let updates = proto
1818 .updates
1819 .ok_or_else(|| TryFromProtoError::missing_field("ProtoInlineBatchPart::updates"))?;
1820 let updates = BlobTraceUpdates::from_proto(lgbytes, updates)?;
1821
1822 Ok(BlobTraceBatchPart {
1823 desc: proto.desc.into_rust_if_some("ProtoInlineBatchPart::desc")?,
1824 index: proto.index.into_rust()?,
1825 updates,
1826 })
1827 }
1828}
1829
1830#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1832pub struct LazyInlineBatchPart(LazyProto<ProtoInlineBatchPart>);
1833
1834impl From<&ProtoInlineBatchPart> for LazyInlineBatchPart {
1835 fn from(value: &ProtoInlineBatchPart) -> Self {
1836 LazyInlineBatchPart(value.into())
1837 }
1838}
1839
1840impl Serialize for LazyInlineBatchPart {
1841 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1842 let proto = self.0.decode().expect("valid proto");
1845 let mut s = s.serialize_struct("InlineBatchPart", 3)?;
1846 let () = s.serialize_field("desc", &proto.desc)?;
1847 let () = s.serialize_field("index", &proto.index)?;
1848 let () = s.serialize_field("updates[len]", &proto.updates.map_or(0, |x| x.len))?;
1849 s.end()
1850 }
1851}
1852
1853impl LazyInlineBatchPart {
1854 pub(crate) fn encoded_size_bytes(&self) -> usize {
1855 self.0.buf.len()
1856 }
1857
1858 pub fn decode<T: Timestamp + Codec64>(
1864 &self,
1865 lgbytes: &ColumnarMetrics,
1866 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1867 let proto = self.0.decode().expect("valid proto");
1868 ProtoInlineBatchPart::into_rust(lgbytes, proto)
1869 }
1870}
1871
1872impl RustType<Bytes> for LazyInlineBatchPart {
1873 fn into_proto(&self) -> Bytes {
1874 self.0.into_proto()
1875 }
1876
1877 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1878 Ok(LazyInlineBatchPart(proto.into_rust()?))
1879 }
1880}
1881
1882impl RustType<ProtoHollowRollup> for HollowRollup {
1883 fn into_proto(&self) -> ProtoHollowRollup {
1884 ProtoHollowRollup {
1885 key: self.key.into_proto(),
1886 encoded_size_bytes: self.encoded_size_bytes.into_proto(),
1887 }
1888 }
1889
1890 fn from_proto(proto: ProtoHollowRollup) -> Result<Self, TryFromProtoError> {
1891 Ok(HollowRollup {
1892 key: proto.key.into_rust()?,
1893 encoded_size_bytes: proto.encoded_size_bytes.into_rust()?,
1894 })
1895 }
1896}
1897
1898impl RustType<ProtoActiveRollup> for ActiveRollup {
1899 fn into_proto(&self) -> ProtoActiveRollup {
1900 ProtoActiveRollup {
1901 start_ms: self.start_ms,
1902 seqno: self.seqno.into_proto(),
1903 }
1904 }
1905
1906 fn from_proto(proto: ProtoActiveRollup) -> Result<Self, TryFromProtoError> {
1907 Ok(ActiveRollup {
1908 start_ms: proto.start_ms,
1909 seqno: proto.seqno.into_rust()?,
1910 })
1911 }
1912}
1913
1914impl RustType<ProtoActiveGc> for ActiveGc {
1915 fn into_proto(&self) -> ProtoActiveGc {
1916 ProtoActiveGc {
1917 start_ms: self.start_ms,
1918 seqno: self.seqno.into_proto(),
1919 }
1920 }
1921
1922 fn from_proto(proto: ProtoActiveGc) -> Result<Self, TryFromProtoError> {
1923 Ok(ActiveGc {
1924 start_ms: proto.start_ms,
1925 seqno: proto.seqno.into_rust()?,
1926 })
1927 }
1928}
1929
1930impl<T: Timestamp + Codec64> RustType<ProtoU64Description> for Description<T> {
1931 fn into_proto(&self) -> ProtoU64Description {
1932 ProtoU64Description {
1933 lower: Some(self.lower().into_proto()),
1934 upper: Some(self.upper().into_proto()),
1935 since: Some(self.since().into_proto()),
1936 }
1937 }
1938
1939 fn from_proto(proto: ProtoU64Description) -> Result<Self, TryFromProtoError> {
1940 let lower: Antichain<T> = proto.lower.into_rust_if_some("lower")?;
1941 if lower.elements().is_empty() {
1944 return Err(TryFromProtoError::InvalidPersistState(
1945 "ProtoU64Description has an empty lower frontier".into(),
1946 ));
1947 }
1948 Ok(Description::new(
1949 lower,
1950 proto.upper.into_rust_if_some("upper")?,
1951 proto.since.into_rust_if_some("since")?,
1952 ))
1953 }
1954}
1955
1956impl<T: Timestamp + Codec64> RustType<ProtoU64Antichain> for Antichain<T> {
1957 fn into_proto(&self) -> ProtoU64Antichain {
1958 ProtoU64Antichain {
1959 elements: self
1960 .elements()
1961 .iter()
1962 .map(|x| i64::from_le_bytes(T::encode(x)))
1963 .collect(),
1964 }
1965 }
1966
1967 fn from_proto(proto: ProtoU64Antichain) -> Result<Self, TryFromProtoError> {
1968 let elements = proto
1969 .elements
1970 .iter()
1971 .map(|x| T::decode(x.to_le_bytes()))
1972 .collect::<Vec<_>>();
1973 Ok(Antichain::from(elements))
1974 }
1975}
1976
1977#[cfg(test)]
1978mod tests {
1979 use mz_ore::assert_none;
1980
1981 use bytes::Bytes;
1982 use mz_build_info::DUMMY_BUILD_INFO;
1983 use mz_dyncfg::ConfigUpdates;
1984 use mz_ore::assert_err;
1985 use mz_ore::cast::CastFrom;
1986 use mz_persist::location::SeqNo;
1987 use proptest::prelude::*;
1988
1989 use crate::ShardId;
1990 use crate::internal::paths::PartialRollupKey;
1991 use crate::internal::state::tests::any_state;
1992 use crate::internal::state::{BatchPart, HandleDebugState};
1993 use crate::internal::state_diff::StateDiff;
1994 use crate::tests::new_test_client_cache;
1995
1996 use super::*;
1997
1998 #[mz_ore::test]
1999 fn rollup_inline_batch_part_with_hollow_fields_is_error() {
2000 use mz_proto::ProtoType;
2004 use prost::Message;
2005 let bytes: &[u8] = &[
2006 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x22, 0x06, 0x5a, 0x00, 0x10, 0x02, 0x2a, 0x00,
2007 0x22, 0x00, 0x22, 0x00, 0x22, 0x00,
2008 ];
2009 let proto = crate::internal::state::ProtoRollup::decode(bytes)
2010 .expect("crash input decodes as a proto");
2011 let result: Result<Rollup<u64>, _> = proto.into_rust();
2012 assert_err!(result);
2013 }
2014
2015 #[mz_ore::test]
2016 fn rollup_batch_with_empty_lower_frontier_is_error() {
2017 use mz_proto::ProtoType;
2021 use prost::Message;
2022 let bytes: &[u8] = &[
2023 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x0a, 0x00, 0x12, 0x00, 0x1a, 0x00,
2024 0x12, 0x00, 0x32, 0x00, 0x22, 0x00,
2025 ];
2026 let proto = crate::internal::state::ProtoRollup::decode(bytes)
2027 .expect("crash input decodes as a proto");
2028 let result: Result<Rollup<u64>, _> = proto.into_rust();
2029 assert_err!(result);
2030 }
2031
2032 #[mz_ore::test]
2033 fn metadata_map() {
2034 const COUNT: MetadataKey<u64> = MetadataKey::new("count");
2035
2036 let mut map = MetadataMap::default();
2037 map.set(COUNT, 100);
2038 let mut map = MetadataMap::from_proto(map.into_proto()).unwrap();
2039 assert_eq!(map.get(COUNT), Some(100));
2040
2041 const ANTICHAIN: MetadataKey<Antichain<u64>, ProtoU64Antichain> =
2042 MetadataKey::new("antichain");
2043 assert_none!(map.get(ANTICHAIN));
2044
2045 map.set(ANTICHAIN, Antichain::from_elem(30));
2046 let map = MetadataMap::from_proto(map.into_proto()).unwrap();
2047 assert_eq!(map.get(COUNT), Some(100));
2048 assert_eq!(map.get(ANTICHAIN), Some(Antichain::from_elem(30)));
2049 }
2050
2051 #[mz_ore::test]
2052 fn applier_version_state() {
2053 let v1 = semver::Version::new(1, 0, 0);
2054 let v2 = semver::Version::new(2, 0, 0);
2055 let v3 = semver::Version::new(3, 0, 0);
2056
2057 let shard_id = ShardId::new();
2059 let state = TypedState::<(), (), u64, i64>::new(v2.clone(), shard_id, "".to_owned(), 0);
2060 let rollup =
2061 Rollup::from_untyped_state_without_diffs(state.clone_for_rollup().into()).into_proto();
2062 let mut buf = Vec::new();
2063 rollup.encode(&mut buf).expect("serializable");
2064 let bytes = Bytes::from(buf);
2065
2066 assert_eq!(
2068 UntypedState::<u64>::decode(&v2, bytes.clone())
2069 .check_codecs(&shard_id)
2070 .as_ref(),
2071 Ok(&state)
2072 );
2073 assert_eq!(
2074 UntypedState::<u64>::decode(&v3, bytes.clone())
2075 .check_codecs(&shard_id)
2076 .as_ref(),
2077 Ok(&state)
2078 );
2079
2080 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| UntypedState::<u64>::decode(&v1, bytes.clone()));
2085 assert_err!(v1_res);
2086 }
2087
2088 #[mz_ore::test]
2089 fn applier_version_state_diff() {
2090 let v1 = semver::Version::new(1, 0, 0);
2091 let v2 = semver::Version::new(2, 0, 0);
2092 let v3 = semver::Version::new(3, 0, 0);
2093
2094 let diff = StateDiff::<u64>::new(
2096 v2.clone(),
2097 SeqNo(0),
2098 SeqNo(1),
2099 2,
2100 PartialRollupKey("rollup".into()),
2101 );
2102 let mut buf = Vec::new();
2103 diff.encode(&mut buf);
2104 let bytes = Bytes::from(buf);
2105
2106 assert_eq!(StateDiff::decode(&v2, bytes.clone()), diff);
2108 assert_eq!(StateDiff::decode(&v3, bytes.clone()), diff);
2109
2110 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| StateDiff::<u64>::decode(&v1, bytes));
2115 assert_err!(v1_res);
2116 }
2117
2118 #[mz_ore::test]
2119 fn hollow_batch_migration_keys() {
2120 let x = HollowBatch::new_run(
2121 Description::new(
2122 Antichain::from_elem(1u64),
2123 Antichain::from_elem(2u64),
2124 Antichain::from_elem(3u64),
2125 ),
2126 vec![RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2127 key: PartialBatchKey("a".into()),
2128 meta: Default::default(),
2129 encoded_size_bytes: 5,
2130 key_lower: vec![],
2131 structured_key_lower: None,
2132 stats: None,
2133 ts_rewrite: None,
2134 diffs_sum: None,
2135 format: None,
2136 schema_id: None,
2137 deprecated_schema_id: None,
2138 }))],
2139 4,
2140 );
2141 let mut old = x.into_proto();
2142 old.deprecated_keys = vec!["b".into()];
2144 let mut expected = x;
2148 expected
2153 .parts
2154 .push(RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2155 key: PartialBatchKey("b".into()),
2156 meta: Default::default(),
2157 encoded_size_bytes: 0,
2158 key_lower: vec![],
2159 structured_key_lower: None,
2160 stats: None,
2161 ts_rewrite: None,
2162 diffs_sum: None,
2163 format: None,
2164 schema_id: None,
2165 deprecated_schema_id: None,
2166 })));
2167 assert_eq!(<HollowBatch<u64>>::from_proto(old).unwrap(), expected);
2168 }
2169
2170 #[mz_ore::test]
2171 fn reader_state_migration_lease_duration() {
2172 let x = LeasedReaderState {
2173 seqno: SeqNo(1),
2174 since: Antichain::from_elem(2u64),
2175 last_heartbeat_timestamp_ms: 3,
2176 debug: HandleDebugState {
2177 hostname: "host".to_owned(),
2178 purpose: "purpose".to_owned(),
2179 },
2180 lease_duration_ms: 0,
2182 };
2183 let old = x.into_proto();
2184 let mut expected = x;
2185 expected.lease_duration_ms =
2188 u64::try_from(READER_LEASE_DURATION.default().as_millis()).unwrap();
2189 assert_eq!(<LeasedReaderState<u64>>::from_proto(old).unwrap(), expected);
2190 }
2191
2192 #[mz_ore::test]
2193 fn writer_state_migration_most_recent_write() {
2194 let proto = ProtoWriterState {
2195 last_heartbeat_timestamp_ms: 1,
2196 lease_duration_ms: 2,
2197 most_recent_write_token: "".into(),
2200 most_recent_write_upper: None,
2201 debug: Some(ProtoHandleDebugState {
2202 hostname: "host".to_owned(),
2203 purpose: "purpose".to_owned(),
2204 }),
2205 };
2206 let expected = WriterState {
2207 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms,
2208 lease_duration_ms: proto.lease_duration_ms,
2209 most_recent_write_token: IdempotencyToken::SENTINEL,
2210 most_recent_write_upper: Antichain::from_elem(0),
2211 debug: HandleDebugState {
2212 hostname: "host".to_owned(),
2213 purpose: "purpose".to_owned(),
2214 },
2215 };
2216 assert_eq!(<WriterState<u64>>::from_proto(proto).unwrap(), expected);
2217 }
2218
2219 #[mz_ore::test]
2220 fn state_migration_rollups() {
2221 let r1 = HollowRollup {
2222 key: PartialRollupKey("foo".to_owned()),
2223 encoded_size_bytes: None,
2224 };
2225 let r2 = HollowRollup {
2226 key: PartialRollupKey("bar".to_owned()),
2227 encoded_size_bytes: Some(2),
2228 };
2229 let shard_id = ShardId::new();
2230 let mut state = TypedState::<(), (), u64, i64>::new(
2231 DUMMY_BUILD_INFO.semver_version(),
2232 shard_id,
2233 "host".to_owned(),
2234 0,
2235 );
2236 state.state.collections.rollups.insert(SeqNo(2), r2.clone());
2237 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2238
2239 proto.deprecated_rollups.insert(1, r1.key.0.clone());
2241
2242 let state: Rollup<u64> = proto.into_rust().unwrap();
2243 let state = state.state;
2244 let state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2245 let expected = vec![(SeqNo(1), r1), (SeqNo(2), r2)];
2246 assert_eq!(
2247 state
2248 .state
2249 .collections
2250 .rollups
2251 .into_iter()
2252 .collect::<Vec<_>>(),
2253 expected
2254 );
2255 }
2256
2257 #[mz_ore::test]
2264 fn rollup_proto_with_diffs_but_no_rollups_is_rejected() {
2265 let shard_id = ShardId::new();
2266 let mut state = TypedState::<(), (), u64, i64>::new(
2267 DUMMY_BUILD_INFO.semver_version(),
2268 shard_id,
2269 "host".to_owned(),
2270 0,
2271 );
2272 let seqno = state.state.seqno;
2275 state.state.collections.rollups.insert(
2276 seqno,
2277 HollowRollup {
2278 key: PartialRollupKey("foo".to_owned()),
2279 encoded_size_bytes: None,
2280 },
2281 );
2282 let mut proto = Rollup::from(state.into(), Vec::new()).into_proto();
2283
2284 proto.rollups.clear();
2286 proto.deprecated_rollups.clear();
2287
2288 let result: Result<Rollup<u64>, _> = proto.into_rust();
2289 assert!(
2290 result.is_err(),
2291 "a rollup proto with diffs but no rollups must error, not panic"
2292 );
2293 }
2294
2295 fn rollup_proto_with_trace(update_trace: impl FnOnce(&mut ProtoTrace)) -> ProtoRollup {
2297 let state = TypedState::<(), (), u64, i64>::new(
2298 DUMMY_BUILD_INFO.semver_version(),
2299 ShardId::new(),
2300 "host".to_owned(),
2301 0,
2302 );
2303 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2304 update_trace(proto.trace.as_mut().expect("fresh state has a trace"));
2305 proto
2306 }
2307
2308 fn u64_desc_proto(lower: u64, upper: u64, since: u64) -> ProtoU64Description {
2309 Description::new(
2310 Antichain::from_elem(lower),
2311 Antichain::from_elem(upper),
2312 Antichain::from_elem(since),
2313 )
2314 .into_proto()
2315 }
2316
2317 fn legacy_batch_proto(lower: u64, upper: u64, since: u64) -> ProtoHollowBatch {
2318 ProtoHollowBatch {
2319 desc: Some(u64_desc_proto(lower, upper, since)),
2320 ..Default::default()
2321 }
2322 }
2323
2324 fn rollup_decode_err(proto: ProtoRollup) -> String {
2325 let result: Result<Rollup<u64>, _> = proto.into_rust();
2326 match result {
2327 Ok(_) => panic!("crafted rollup proto must fail to decode"),
2328 Err(err) => err.to_string(),
2329 }
2330 }
2331
2332 #[mz_ore::test]
2339 fn rollup_proto_with_noncontiguous_legacy_batches_is_rejected() {
2340 let proto = rollup_proto_with_trace(|trace| {
2341 trace.legacy_batches.push(legacy_batch_proto(39, 40, 0));
2342 });
2343 let err = rollup_decode_err(proto);
2344 assert!(err.contains("legacy batch lower"), "{err}");
2345 }
2346
2347 #[mz_ore::test]
2351 fn rollup_proto_with_empty_range_legacy_batch_is_rejected() {
2352 let proto = rollup_proto_with_trace(|trace| {
2353 trace.legacy_batches.push(legacy_batch_proto(0, 0, 0));
2354 });
2355 let err = rollup_decode_err(proto);
2356 assert!(err.contains("empty time range"), "{err}");
2357 }
2358
2359 #[mz_ore::test]
2363 fn rollup_proto_with_batch_since_past_trace_since_is_rejected() {
2364 let proto = rollup_proto_with_trace(|trace| {
2365 trace.legacy_batches.push(legacy_batch_proto(0, 1, 5));
2366 });
2367 let err = rollup_decode_err(proto);
2368 assert!(err.contains("past the spine since"), "{err}");
2369 }
2370
2371 #[mz_ore::test]
2375 fn rollup_proto_with_absurd_batch_len_is_rejected() {
2376 let proto = rollup_proto_with_trace(|trace| {
2377 trace.legacy_batches.push(ProtoHollowBatch {
2378 desc: Some(u64_desc_proto(0, 1, 0)),
2379 len: u64::MAX,
2380 ..Default::default()
2381 });
2382 });
2383 let err = rollup_decode_err(proto);
2384 assert!(err.contains("maximum trace size"), "{err}");
2385 }
2386
2387 #[mz_ore::test]
2390 fn rollup_proto_with_absurd_spine_level_is_rejected() {
2391 let proto = rollup_proto_with_trace(|trace| {
2392 trace.spine_batches.push(ProtoIdSpineBatch {
2393 id: Some(SpineId(0, 1).into_proto()),
2394 batch: Some(ProtoSpineBatch {
2395 level: u64::MAX,
2396 desc: Some(u64_desc_proto(0, 1, 0)),
2397 parts: vec![],
2398 descs: vec![],
2399 }),
2400 });
2401 });
2402 let err = rollup_decode_err(proto);
2403 assert!(err.contains("exceeds the maximum"), "{err}");
2404 }
2405
2406 #[mz_ore::test]
2410 fn rollup_proto_with_partless_spine_batch_is_rejected() {
2411 let proto = rollup_proto_with_trace(|trace| {
2412 trace.spine_batches.push(ProtoIdSpineBatch {
2413 id: Some(SpineId(0, 1).into_proto()),
2414 batch: Some(ProtoSpineBatch {
2415 level: 0,
2416 desc: Some(u64_desc_proto(0, 1, 0)),
2417 parts: vec![],
2418 descs: vec![],
2419 }),
2420 });
2421 });
2422 let err = rollup_decode_err(proto);
2423 assert!(err.contains("do not tile"), "{err}");
2424 }
2425
2426 #[mz_ore::test]
2432 fn rollup_proto_with_noncontiguous_spine_parts_is_rejected() {
2433 let outer = SpineId(0, 3);
2434 let part_ids = [SpineId(0, 2), SpineId(1, 3)];
2435 let proto = rollup_proto_with_trace(|trace| {
2436 trace.spine_batches.push(ProtoIdSpineBatch {
2437 id: Some(outer.into_proto()),
2438 batch: Some(ProtoSpineBatch {
2439 level: 0,
2440 desc: Some(u64_desc_proto(0, 3, 0)),
2441 parts: part_ids.iter().map(|id| id.into_proto()).collect(),
2442 descs: vec![],
2443 }),
2444 });
2445 for id in part_ids {
2446 trace.hollow_batches.push(ProtoIdHollowBatch {
2447 id: Some(id.into_proto()),
2448 batch: Some(legacy_batch_proto(0, 1, 0)),
2449 });
2450 }
2451 });
2452 let err = rollup_decode_err(proto);
2453 assert!(err.contains("do not tile"), "{err}");
2454 }
2455
2456 #[mz_ore::test]
2460 fn rollup_proto_with_overfull_spine_level_is_rejected() {
2461 let proto = rollup_proto_with_trace(|trace| {
2462 for i in 0..3u64 {
2463 let id = SpineId(usize::cast_from(i), usize::cast_from(i + 1));
2464 trace.spine_batches.push(ProtoIdSpineBatch {
2465 id: Some(id.into_proto()),
2466 batch: Some(ProtoSpineBatch {
2467 level: 0,
2468 desc: Some(u64_desc_proto(i, i + 1, 0)),
2469 parts: vec![id.into_proto()],
2470 descs: vec![],
2471 }),
2472 });
2473 trace.hollow_batches.push(ProtoIdHollowBatch {
2474 id: Some(id.into_proto()),
2475 batch: Some(legacy_batch_proto(i, i + 1, 0)),
2476 });
2477 }
2478 });
2479 let err = rollup_decode_err(proto);
2480 assert!(err.contains("full layer"), "{err}");
2481 }
2482
2483 #[mz_persist_proc::test(tokio::test)]
2484 #[cfg_attr(miri, ignore)] async fn state_diff_migration_rollups(dyncfgs: ConfigUpdates) {
2486 let r1_rollup = HollowRollup {
2487 key: PartialRollupKey("foo".to_owned()),
2488 encoded_size_bytes: None,
2489 };
2490 let r1 = StateFieldDiff {
2491 key: SeqNo(1),
2492 val: StateFieldValDiff::Insert(r1_rollup.clone()),
2493 };
2494 let r2_rollup = HollowRollup {
2495 key: PartialRollupKey("bar".to_owned()),
2496 encoded_size_bytes: Some(2),
2497 };
2498 let r2 = StateFieldDiff {
2499 key: SeqNo(2),
2500 val: StateFieldValDiff::Insert(r2_rollup.clone()),
2501 };
2502 let r3_rollup = HollowRollup {
2503 key: PartialRollupKey("baz".to_owned()),
2504 encoded_size_bytes: None,
2505 };
2506 let r3 = StateFieldDiff {
2507 key: SeqNo(3),
2508 val: StateFieldValDiff::Delete(r3_rollup.clone()),
2509 };
2510 let mut diff = StateDiff::<u64>::new(
2511 DUMMY_BUILD_INFO.semver_version(),
2512 SeqNo(4),
2513 SeqNo(5),
2514 0,
2515 PartialRollupKey("ignored".to_owned()),
2516 );
2517 diff.rollups.push(r2.clone());
2518 diff.rollups.push(r3.clone());
2519 let mut diff_proto = diff.into_proto();
2520
2521 let field_diffs = std::mem::take(&mut diff_proto.field_diffs).unwrap();
2522 let mut field_diffs_writer = field_diffs.into_writer();
2523
2524 field_diffs_into_proto(
2526 ProtoStateField::DeprecatedRollups,
2527 &[StateFieldDiff {
2528 key: r1.key,
2529 val: StateFieldValDiff::Insert(r1_rollup.key.clone()),
2530 }],
2531 &mut field_diffs_writer,
2532 );
2533
2534 assert_none!(diff_proto.field_diffs);
2535 diff_proto.field_diffs = Some(field_diffs_writer.into_proto());
2536
2537 let diff = StateDiff::<u64>::from_proto(diff_proto.clone()).unwrap();
2538 assert_eq!(
2539 diff.rollups.into_iter().collect::<Vec<_>>(),
2540 vec![r2, r3, r1]
2541 );
2542
2543 let shard_id = ShardId::new();
2546 let mut state = TypedState::<(), (), u64, i64>::new(
2547 DUMMY_BUILD_INFO.semver_version(),
2548 shard_id,
2549 "host".to_owned(),
2550 0,
2551 );
2552 state.state.seqno = SeqNo(4);
2553 let mut rollup = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2554 rollup
2555 .deprecated_rollups
2556 .insert(3, r3_rollup.key.into_proto());
2557 let state: Rollup<u64> = rollup.into_rust().unwrap();
2558 let state = state.state;
2559 let mut state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2560 let cache = new_test_client_cache(&dyncfgs);
2561 let encoded_diff = VersionedData {
2562 seqno: SeqNo(5),
2563 data: diff_proto.encode_to_vec().into(),
2564 };
2565 state.apply_encoded_diffs(cache.cfg(), &cache.metrics, std::iter::once(&encoded_diff));
2566 assert_eq!(
2567 state
2568 .state
2569 .collections
2570 .rollups
2571 .into_iter()
2572 .collect::<Vec<_>>(),
2573 vec![(SeqNo(1), r1_rollup), (SeqNo(2), r2_rollup)]
2574 );
2575 }
2576
2577 #[mz_ore::test]
2578 #[cfg_attr(miri, ignore)] fn state_proto_roundtrip() {
2580 fn testcase<T: Timestamp + Lattice + Codec64>(state: State<T>) {
2581 let before = UntypedState {
2582 key_codec: <() as Codec>::codec_name(),
2583 val_codec: <() as Codec>::codec_name(),
2584 ts_codec: <T as Codec64>::codec_name(),
2585 diff_codec: <i64 as Codec64>::codec_name(),
2586 state,
2587 };
2588 let proto = Rollup::from_untyped_state_without_diffs(before.clone()).into_proto();
2589 let after: Rollup<T> = proto.into_rust().unwrap();
2590 let after = after.state;
2591 assert_eq!(before, after);
2592 }
2593
2594 proptest!(|(state in any_state::<u64>(0..3))| testcase(state));
2595 }
2596
2597 #[mz_ore::test]
2598 fn check_data_versions() {
2599 #[track_caller]
2600 fn testcase(code: &str, data: &str, expected: Result<(), ()>) {
2601 let code = Version::parse(code).unwrap();
2602 let data = Version::parse(data).unwrap();
2603 #[allow(clippy::disallowed_methods)]
2604 let actual = cfg::code_can_write_data(&code, &data)
2605 .then_some(())
2606 .ok_or(());
2607 assert_eq!(actual, expected, "data at {data} read by code {code}");
2608 }
2609
2610 testcase("0.160.0-dev", "0.160.0-dev", Ok(()));
2611 testcase("0.160.0-dev", "0.160.0", Err(()));
2612 testcase("0.160.0-dev", "0.161.0-dev", Err(()));
2615 testcase("0.160.0-dev", "0.161.0", Err(()));
2616 testcase("0.160.0-dev", "0.162.0-dev", Err(()));
2617 testcase("0.160.0-dev", "0.162.0", Err(()));
2618 testcase("0.160.0-dev", "0.163.0-dev", Err(()));
2619
2620 testcase("0.160.0", "0.158.0-dev", Ok(()));
2621 testcase("0.160.0", "0.158.0", Ok(()));
2622 testcase("0.160.0", "0.159.0-dev", Ok(()));
2623 testcase("0.160.0", "0.159.0", Ok(()));
2624 testcase("0.160.0", "0.160.0-dev", Ok(()));
2625 testcase("0.160.0", "0.160.0", Ok(()));
2626
2627 testcase("0.160.0", "0.161.0-dev", Err(()));
2628 testcase("0.160.0", "0.161.0", Err(()));
2629 testcase("0.160.0", "0.161.1", Err(()));
2630 testcase("0.160.0", "0.161.1000000", Err(()));
2631 testcase("0.160.0", "0.162.0-dev", Err(()));
2632 testcase("0.160.0", "0.162.0", Err(()));
2633 testcase("0.160.0", "0.163.0-dev", Err(()));
2634
2635 testcase("0.160.1", "0.159.0", Ok(()));
2636 testcase("0.160.1", "0.160.0", Ok(()));
2637 testcase("0.160.1", "0.161.0", Err(()));
2638 testcase("0.160.1", "0.161.1", Err(()));
2639 testcase("0.160.1", "0.161.100", Err(()));
2640 testcase("0.160.0", "0.160.1", Err(()));
2641
2642 testcase("0.160.1", "26.0.0", Err(()));
2643 testcase("26.0.0", "0.160.1", Ok(()));
2644 testcase("26.2.0", "0.160.1", Ok(()));
2645 testcase("26.200.200", "0.160.1", Ok(()));
2646
2647 testcase("27.0.0", "0.160.1", Err(()));
2648 testcase("27.0.0", "0.16000.1", Err(()));
2649 testcase("27.0.0", "26.0.1", Ok(()));
2650 testcase("27.1000.100", "26.0.1", Ok(()));
2651 testcase("28.0.0", "26.0.1", Err(()));
2652 testcase("28.0.0", "26.1000.1", Err(()));
2653 testcase("28.0.0", "27.0.0", Ok(()));
2654 }
2655
2656 #[mz_ore::test]
2662 fn lazy_part_stats_debug_does_not_panic_on_garbage() {
2663 let stats = LazyPartStats::from_proto(Bytes::from_static(&[0x00, 0xff]))
2665 .expect("stats bytes are stored undecoded");
2666 assert_err!(stats.try_decode());
2667 assert!(format!("{stats:?}").contains("undecodable"));
2668 }
2669}