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