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 finalized: false,
567 })
568 },
569 )?
570 }
571 ProtoStateField::LeasedReaders => {
572 field_diff_into_rust::<String, ProtoLeasedReaderState, _, _, _, _>(
573 diff,
574 &mut state_diff.leased_readers,
575 |k| k.into_rust(),
576 |v| v.into_rust(),
577 )?
578 }
579 ProtoStateField::CriticalReaders => {
580 field_diff_into_rust::<String, ProtoCriticalReaderState, _, _, _, _>(
581 diff,
582 &mut state_diff.critical_readers,
583 |k| k.into_rust(),
584 |v| v.into_rust(),
585 )?
586 }
587 ProtoStateField::Writers => {
588 field_diff_into_rust::<String, ProtoWriterState, _, _, _, _>(
589 diff,
590 &mut state_diff.writers,
591 |k| k.into_rust(),
592 |v| v.into_rust(),
593 )?
594 }
595 ProtoStateField::Schemas => {
596 field_diff_into_rust::<u64, ProtoEncodedSchemas, _, _, _, _>(
597 diff,
598 &mut state_diff.schemas,
599 |k| k.into_rust(),
600 |v| v.into_rust(),
601 )?
602 }
603 ProtoStateField::Since => {
604 field_diff_into_rust::<(), ProtoU64Antichain, _, _, _, _>(
605 diff,
606 &mut state_diff.since,
607 |()| Ok(()),
608 |v| v.into_rust(),
609 )?
610 }
611 ProtoStateField::LegacyBatches => {
612 field_diff_into_rust::<ProtoHollowBatch, (), _, _, _, _>(
613 diff,
614 &mut state_diff.legacy_batches,
615 |k| k.into_rust(),
616 |()| Ok(()),
617 )?
618 }
619 ProtoStateField::HollowBatches => {
620 field_diff_into_rust::<ProtoSpineId, ProtoHollowBatch, _, _, _, _>(
621 diff,
622 &mut state_diff.hollow_batches,
623 |k| k.into_rust(),
624 |v| v.into_rust(),
625 )?
626 }
627 ProtoStateField::SpineBatches => {
628 field_diff_into_rust::<ProtoSpineId, ProtoSpineBatch, _, _, _, _>(
629 diff,
630 &mut state_diff.spine_batches,
631 |k| k.into_rust(),
632 |v| v.into_rust(),
633 )?
634 }
635 ProtoStateField::SpineMerges => {
636 field_diff_into_rust::<ProtoSpineId, ProtoMerge, _, _, _, _>(
637 diff,
638 &mut state_diff.merges,
639 |k| k.into_rust(),
640 |v| v.into_rust(),
641 )?
642 }
643 }
644 }
645 }
646 Ok(state_diff)
647 }
648}
649
650fn field_diffs_into_proto<K, KP, V, VP>(
651 field: ProtoStateField,
652 diffs: &[StateFieldDiff<K, V>],
653 writer: &mut ProtoStateFieldDiffsWriter,
654) where
655 KP: prost::Message,
656 K: RustType<KP>,
657 VP: prost::Message,
658 V: RustType<VP>,
659{
660 for diff in diffs.iter() {
661 field_diff_into_proto(field, diff, writer);
662 }
663}
664
665fn field_diff_into_proto<K, KP, V, VP>(
666 field: ProtoStateField,
667 diff: &StateFieldDiff<K, V>,
668 writer: &mut ProtoStateFieldDiffsWriter,
669) where
670 KP: prost::Message,
671 K: RustType<KP>,
672 VP: prost::Message,
673 V: RustType<VP>,
674{
675 writer.push_field(field);
676 writer.encode_proto(&diff.key.into_proto());
677 match &diff.val {
678 StateFieldValDiff::Insert(to) => {
679 writer.push_diff_type(ProtoStateFieldDiffType::Insert);
680 writer.encode_proto(&to.into_proto());
681 }
682 StateFieldValDiff::Update(from, to) => {
683 writer.push_diff_type(ProtoStateFieldDiffType::Update);
684 writer.encode_proto(&from.into_proto());
685 writer.encode_proto(&to.into_proto());
686 }
687 StateFieldValDiff::Delete(from) => {
688 writer.push_diff_type(ProtoStateFieldDiffType::Delete);
689 writer.encode_proto(&from.into_proto());
690 }
691 };
692}
693
694fn field_diff_into_rust<KP, VP, K, V, KFn, VFn>(
695 proto: ProtoStateFieldDiff<'_>,
696 diffs: &mut Vec<StateFieldDiff<K, V>>,
697 k_fn: KFn,
698 v_fn: VFn,
699) -> Result<(), TryFromProtoError>
700where
701 KP: prost::Message + Default,
702 VP: prost::Message + Default,
703 KFn: Fn(KP) -> Result<K, TryFromProtoError>,
704 VFn: Fn(VP) -> Result<V, TryFromProtoError>,
705{
706 let val = match proto.diff_type {
707 ProtoStateFieldDiffType::Insert => {
708 let to = VP::decode(proto.to)
709 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
710 StateFieldValDiff::Insert(v_fn(to)?)
711 }
712 ProtoStateFieldDiffType::Update => {
713 let from = VP::decode(proto.from)
714 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
715 let to = VP::decode(proto.to)
716 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
717
718 StateFieldValDiff::Update(v_fn(from)?, v_fn(to)?)
719 }
720 ProtoStateFieldDiffType::Delete => {
721 let from = VP::decode(proto.from)
722 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
723 StateFieldValDiff::Delete(v_fn(from)?)
724 }
725 };
726 let key = KP::decode(proto.key)
727 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
728 diffs.push(StateFieldDiff {
729 key: k_fn(key)?,
730 val,
731 });
732 Ok(())
733}
734
735#[derive(Debug)]
738#[cfg_attr(any(test, debug_assertions), derive(Clone, PartialEq))]
739pub struct UntypedState<T> {
740 pub(crate) key_codec: String,
741 pub(crate) val_codec: String,
742 pub(crate) ts_codec: String,
743 pub(crate) diff_codec: String,
744
745 state: State<T>,
749}
750
751impl<T: Timestamp + Lattice + Codec64> UntypedState<T> {
752 pub fn seqno(&self) -> SeqNo {
753 self.state.seqno
754 }
755
756 pub fn rollups(&self) -> &BTreeMap<SeqNo, HollowRollup> {
757 &self.state.collections.rollups
758 }
759
760 pub fn latest_rollup(&self) -> (&SeqNo, &HollowRollup) {
761 self.state.latest_rollup()
762 }
763
764 pub fn apply_encoded_diffs<'a, I: IntoIterator<Item = &'a VersionedData>>(
765 &mut self,
766 cfg: &PersistConfig,
767 metrics: &Metrics,
768 diffs: I,
769 ) {
770 if T::codec_name() != self.ts_codec {
774 return;
775 }
776 self.state.apply_encoded_diffs(cfg, metrics, diffs);
777 }
778
779 pub fn check_codecs<K: Codec, V: Codec, D: Codec64>(
780 self,
781 shard_id: &ShardId,
782 ) -> Result<TypedState<K, V, T, D>, Box<CodecMismatch>> {
783 assert_eq!(shard_id, &self.state.shard_id);
786 if K::codec_name() != self.key_codec
787 || V::codec_name() != self.val_codec
788 || T::codec_name() != self.ts_codec
789 || D::codec_name() != self.diff_codec
790 {
791 return Err(Box::new(CodecMismatch {
792 requested: (
793 K::codec_name(),
794 V::codec_name(),
795 T::codec_name(),
796 D::codec_name(),
797 None,
798 ),
799 actual: (
800 self.key_codec,
801 self.val_codec,
802 self.ts_codec,
803 self.diff_codec,
804 None,
805 ),
806 }));
807 }
808 Ok(TypedState {
809 state: self.state,
810 _phantom: PhantomData,
811 })
812 }
813
814 pub(crate) fn check_ts_codec(self, shard_id: &ShardId) -> Result<State<T>, CodecMismatchT> {
815 assert_eq!(shard_id, &self.state.shard_id);
818 if T::codec_name() != self.ts_codec {
819 return Err(CodecMismatchT {
820 requested: T::codec_name(),
821 actual: self.ts_codec,
822 });
823 }
824 Ok(self.state)
825 }
826
827 pub fn decode(build_version: &Version, buf: impl Buf) -> Self {
828 let proto = ProtoRollup::decode(buf)
829 .expect("internal error: invalid encoded state");
834 let state = Rollup::from_proto(proto)
835 .expect("internal error: invalid encoded state")
836 .state;
837 assert_code_can_read_data(build_version, &state.state.collections.version);
838 state
839 }
840}
841
842impl<K, V, T, D> From<TypedState<K, V, T, D>> for UntypedState<T>
843where
844 K: Codec,
845 V: Codec,
846 T: Codec64,
847 D: Codec64,
848{
849 fn from(typed_state: TypedState<K, V, T, D>) -> Self {
850 UntypedState {
851 key_codec: K::codec_name(),
852 val_codec: V::codec_name(),
853 ts_codec: T::codec_name(),
854 diff_codec: D::codec_name(),
855 state: typed_state.state,
856 }
857 }
858}
859
860#[derive(Debug)]
867pub struct Rollup<T> {
868 pub(crate) state: UntypedState<T>,
869 pub(crate) diffs: Option<InlinedDiffs>,
870}
871
872impl<T: Timestamp + Lattice + Codec64> Rollup<T> {
873 pub(crate) fn from(state: UntypedState<T>, diffs: Vec<VersionedData>) -> Self {
877 let latest_rollup_seqno = *state.latest_rollup().0;
878 let mut verify_seqno = latest_rollup_seqno;
879 for diff in &diffs {
880 assert_eq!(verify_seqno.next(), diff.seqno);
881 verify_seqno = diff.seqno;
882 }
883 assert_eq!(verify_seqno, state.seqno());
884
885 let diffs = Some(InlinedDiffs::from(
886 latest_rollup_seqno.next(),
887 state.seqno().next(),
888 diffs,
889 ));
890
891 Self { state, diffs }
892 }
893
894 pub(crate) fn from_untyped_state_without_diffs(state: UntypedState<T>) -> Self {
895 Self { state, diffs: None }
896 }
897
898 pub(crate) fn from_state_without_diffs(
899 state: State<T>,
900 key_codec: String,
901 val_codec: String,
902 ts_codec: String,
903 diff_codec: String,
904 ) -> Self {
905 Self::from_untyped_state_without_diffs(UntypedState {
906 key_codec,
907 val_codec,
908 ts_codec,
909 diff_codec,
910 state,
911 })
912 }
913}
914
915#[derive(Debug)]
916pub(crate) struct InlinedDiffs {
917 pub(crate) lower: SeqNo,
918 pub(crate) upper: SeqNo,
919 pub(crate) diffs: Vec<VersionedData>,
920}
921
922impl InlinedDiffs {
923 pub(crate) fn description(&self) -> Description<SeqNo> {
924 Description::new(
925 Antichain::from_elem(self.lower),
926 Antichain::from_elem(self.upper),
927 Antichain::from_elem(SeqNo::minimum()),
928 )
929 }
930
931 fn from(lower: SeqNo, upper: SeqNo, diffs: Vec<VersionedData>) -> Self {
932 for diff in &diffs {
933 assert!(diff.seqno >= lower);
934 assert!(diff.seqno < upper);
935 }
936 Self {
937 lower,
938 upper,
939 diffs,
940 }
941 }
942}
943
944impl RustType<ProtoInlinedDiffs> for InlinedDiffs {
945 fn into_proto(&self) -> ProtoInlinedDiffs {
946 ProtoInlinedDiffs {
947 lower: self.lower.into_proto(),
948 upper: self.upper.into_proto(),
949 diffs: self.diffs.into_proto(),
950 }
951 }
952
953 fn from_proto(proto: ProtoInlinedDiffs) -> Result<Self, TryFromProtoError> {
954 Ok(Self {
955 lower: proto.lower.into_rust()?,
956 upper: proto.upper.into_rust()?,
957 diffs: proto.diffs.into_rust()?,
958 })
959 }
960}
961
962impl<T: Timestamp + Lattice + Codec64> RustType<ProtoRollup> for Rollup<T> {
963 fn into_proto(&self) -> ProtoRollup {
964 ProtoRollup {
965 applier_version: self.state.state.collections.version.to_string(),
966 shard_id: self.state.state.shard_id.into_proto(),
967 seqno: self.state.state.seqno.into_proto(),
968 walltime_ms: self.state.state.walltime_ms.into_proto(),
969 hostname: self.state.state.hostname.into_proto(),
970 key_codec: self.state.key_codec.into_proto(),
971 val_codec: self.state.val_codec.into_proto(),
972 ts_codec: T::codec_name(),
973 diff_codec: self.state.diff_codec.into_proto(),
974 last_gc_req: self.state.state.collections.last_gc_req.into_proto(),
975 active_rollup: self.state.state.collections.active_rollup.into_proto(),
976 active_gc: self.state.state.collections.active_gc.into_proto(),
977 rollups: self
978 .state
979 .state
980 .collections
981 .rollups
982 .iter()
983 .map(|(seqno, key)| (seqno.into_proto(), key.into_proto()))
984 .collect(),
985 deprecated_rollups: Default::default(),
986 leased_readers: self
987 .state
988 .state
989 .collections
990 .leased_readers
991 .iter()
992 .map(|(id, state)| (id.into_proto(), state.into_proto()))
993 .collect(),
994 critical_readers: self
995 .state
996 .state
997 .collections
998 .critical_readers
999 .iter()
1000 .map(|(id, state)| (id.into_proto(), state.into_proto()))
1001 .collect(),
1002 writers: self
1003 .state
1004 .state
1005 .collections
1006 .writers
1007 .iter()
1008 .map(|(id, state)| (id.into_proto(), state.into_proto()))
1009 .collect(),
1010 schemas: self
1011 .state
1012 .state
1013 .collections
1014 .schemas
1015 .iter()
1016 .map(|(id, schema)| (id.into_proto(), schema.into_proto()))
1017 .collect(),
1018 trace: Some(self.state.state.collections.trace.into_proto()),
1019 diffs: self.diffs.as_ref().map(|x| x.into_proto()),
1020 }
1021 }
1022
1023 fn from_proto(x: ProtoRollup) -> Result<Self, TryFromProtoError> {
1024 let applier_version = if x.applier_version.is_empty() {
1025 semver::Version::new(0, 0, 0)
1029 } else {
1030 semver::Version::parse(&x.applier_version).map_err(|err| {
1031 TryFromProtoError::InvalidSemverVersion(format!(
1032 "invalid applier_version {}: {}",
1033 x.applier_version, err
1034 ))
1035 })?
1036 };
1037
1038 let mut rollups = BTreeMap::new();
1039 for (seqno, rollup) in x.rollups {
1040 rollups.insert(seqno.into_rust()?, rollup.into_rust()?);
1041 }
1042 for (seqno, key) in x.deprecated_rollups {
1043 rollups.insert(
1044 seqno.into_rust()?,
1045 HollowRollup {
1046 key: key.into_rust()?,
1047 encoded_size_bytes: None,
1048 finalized: false,
1049 },
1050 );
1051 }
1052 let mut leased_readers = BTreeMap::new();
1053 for (id, state) in x.leased_readers {
1054 leased_readers.insert(id.into_rust()?, state.into_rust()?);
1055 }
1056 let mut critical_readers = BTreeMap::new();
1057 for (id, state) in x.critical_readers {
1058 critical_readers.insert(id.into_rust()?, state.into_rust()?);
1059 }
1060 let mut writers = BTreeMap::new();
1061 for (id, state) in x.writers {
1062 writers.insert(id.into_rust()?, state.into_rust()?);
1063 }
1064 let mut schemas = BTreeMap::new();
1065 for (id, x) in x.schemas {
1066 schemas.insert(id.into_rust()?, x.into_rust()?);
1067 }
1068 let active_rollup = x
1069 .active_rollup
1070 .map(|rollup| rollup.into_rust())
1071 .transpose()?;
1072 let active_gc = x.active_gc.map(|gc| gc.into_rust()).transpose()?;
1073 let collections = StateCollections {
1074 version: applier_version.clone(),
1075 rollups,
1076 active_rollup,
1077 active_gc,
1078 last_gc_req: x.last_gc_req.into_rust()?,
1079 leased_readers,
1080 critical_readers,
1081 writers,
1082 schemas,
1083 trace: x.trace.into_rust_if_some("trace")?,
1084 };
1085 let state = State {
1086 shard_id: x.shard_id.into_rust()?,
1087 seqno: x.seqno.into_rust()?,
1088 walltime_ms: x.walltime_ms,
1089 hostname: x.hostname,
1090 collections,
1091 };
1092
1093 let diffs: Option<InlinedDiffs> = x.diffs.map(|diffs| diffs.into_rust()).transpose()?;
1094 if let Some(diffs) = &diffs {
1095 if state.collections.rollups.is_empty() {
1099 return Err(TryFromProtoError::InvalidPersistState(
1100 "rollup state has diffs but no rollups".into(),
1101 ));
1102 }
1103 if diffs.lower != state.latest_rollup().0.next() {
1104 return Err(TryFromProtoError::InvalidPersistState(format!(
1105 "diffs lower ({}) should match latest rollup's successor: ({})",
1106 diffs.lower,
1107 state.latest_rollup().0.next()
1108 )));
1109 }
1110 if diffs.upper != state.seqno.next() {
1111 return Err(TryFromProtoError::InvalidPersistState(format!(
1112 "diffs upper ({}) should match state's successor: ({})",
1113 diffs.lower,
1114 state.seqno.next()
1115 )));
1116 }
1117 }
1118
1119 Ok(Rollup {
1120 state: UntypedState {
1121 state,
1122 key_codec: x.key_codec.into_rust()?,
1123 val_codec: x.val_codec.into_rust()?,
1124 ts_codec: x.ts_codec.into_rust()?,
1125 diff_codec: x.diff_codec.into_rust()?,
1126 },
1127 diffs,
1128 })
1129 }
1130}
1131
1132impl RustType<ProtoVersionedData> for VersionedData {
1133 fn into_proto(&self) -> ProtoVersionedData {
1134 ProtoVersionedData {
1135 seqno: self.seqno.into_proto(),
1136 data: Bytes::clone(&self.data),
1137 }
1138 }
1139
1140 fn from_proto(proto: ProtoVersionedData) -> Result<Self, TryFromProtoError> {
1141 Ok(Self {
1142 seqno: proto.seqno.into_rust()?,
1143 data: proto.data,
1144 })
1145 }
1146}
1147
1148impl RustType<ProtoSpineId> for SpineId {
1149 fn into_proto(&self) -> ProtoSpineId {
1150 ProtoSpineId {
1151 lo: self.0.into_proto(),
1152 hi: self.1.into_proto(),
1153 }
1154 }
1155
1156 fn from_proto(proto: ProtoSpineId) -> Result<Self, TryFromProtoError> {
1157 Ok(SpineId(proto.lo.into_rust()?, proto.hi.into_rust()?))
1158 }
1159}
1160
1161impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, Arc<HollowBatch<T>>> for ProtoIdHollowBatch {
1162 fn from_rust<'a>(entry: (&'a SpineId, &'a Arc<HollowBatch<T>>)) -> Self {
1163 let (id, batch) = entry;
1164 ProtoIdHollowBatch {
1165 id: Some(id.into_proto()),
1166 batch: Some(batch.into_proto()),
1167 }
1168 }
1169
1170 fn into_rust(self) -> Result<(SpineId, Arc<HollowBatch<T>>), TryFromProtoError> {
1171 let id = self.id.into_rust_if_some("ProtoIdHollowBatch::id")?;
1172 let batch = Arc::new(self.batch.into_rust_if_some("ProtoIdHollowBatch::batch")?);
1173 Ok((id, batch))
1174 }
1175}
1176
1177impl<T: Timestamp + Codec64> RustType<ProtoSpineBatch> for ThinSpineBatch<T> {
1178 fn into_proto(&self) -> ProtoSpineBatch {
1179 ProtoSpineBatch {
1180 desc: Some(self.desc.into_proto()),
1181 parts: self.parts.into_proto(),
1182 level: self.level.into_proto(),
1183 descs: self.descs.into_proto(),
1184 }
1185 }
1186
1187 fn from_proto(proto: ProtoSpineBatch) -> Result<Self, TryFromProtoError> {
1188 let level = proto.level.into_rust()?;
1189 let desc = proto.desc.into_rust_if_some("ProtoSpineBatch::desc")?;
1190 let parts = proto.parts.into_rust()?;
1191 let descs = proto.descs.into_rust()?;
1192 Ok(ThinSpineBatch {
1193 level,
1194 desc,
1195 parts,
1196 descs,
1197 })
1198 }
1199}
1200
1201impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, ThinSpineBatch<T>> for ProtoIdSpineBatch {
1202 fn from_rust<'a>(entry: (&'a SpineId, &'a ThinSpineBatch<T>)) -> Self {
1203 let (id, batch) = entry;
1204 ProtoIdSpineBatch {
1205 id: Some(id.into_proto()),
1206 batch: Some(batch.into_proto()),
1207 }
1208 }
1209
1210 fn into_rust(self) -> Result<(SpineId, ThinSpineBatch<T>), TryFromProtoError> {
1211 let id = self.id.into_rust_if_some("ProtoHollowBatch::id")?;
1212 let batch = self.batch.into_rust_if_some("ProtoHollowBatch::batch")?;
1213 Ok((id, batch))
1214 }
1215}
1216
1217impl RustType<ProtoCompaction> for ActiveCompaction {
1218 fn into_proto(&self) -> ProtoCompaction {
1219 ProtoCompaction {
1220 start_ms: self.start_ms,
1221 }
1222 }
1223
1224 fn from_proto(proto: ProtoCompaction) -> Result<Self, TryFromProtoError> {
1225 Ok(Self {
1226 start_ms: proto.start_ms,
1227 })
1228 }
1229}
1230
1231impl<T: Timestamp + Codec64> RustType<ProtoMerge> for ThinMerge<T> {
1232 fn into_proto(&self) -> ProtoMerge {
1233 ProtoMerge {
1234 since: Some(self.since.into_proto()),
1235 remaining_work: self.remaining_work.into_proto(),
1236 active_compaction: self.active_compaction.into_proto(),
1237 }
1238 }
1239
1240 fn from_proto(proto: ProtoMerge) -> Result<Self, TryFromProtoError> {
1241 let since = proto.since.into_rust_if_some("ProtoMerge::since")?;
1242 let remaining_work = proto.remaining_work.into_rust()?;
1243 let active_compaction = proto.active_compaction.into_rust()?;
1244 Ok(Self {
1245 since,
1246 remaining_work,
1247 active_compaction,
1248 })
1249 }
1250}
1251
1252impl<T: Timestamp + Codec64> ProtoMapEntry<SpineId, ThinMerge<T>> for ProtoIdMerge {
1253 fn from_rust<'a>((id, merge): (&'a SpineId, &'a ThinMerge<T>)) -> Self {
1254 ProtoIdMerge {
1255 id: Some(id.into_proto()),
1256 merge: Some(merge.into_proto()),
1257 }
1258 }
1259
1260 fn into_rust(self) -> Result<(SpineId, ThinMerge<T>), TryFromProtoError> {
1261 let id = self.id.into_rust_if_some("ProtoIdMerge::id")?;
1262 let merge = self.merge.into_rust_if_some("ProtoIdMerge::merge")?;
1263 Ok((id, merge))
1264 }
1265}
1266
1267impl<T: Timestamp + Codec64> RustType<ProtoTrace> for FlatTrace<T> {
1268 fn into_proto(&self) -> ProtoTrace {
1269 let since = self.since.into_proto();
1270 let legacy_batches = self
1271 .legacy_batches
1272 .iter()
1273 .map(|(b, _)| b.into_proto())
1274 .collect();
1275 let hollow_batches = self.hollow_batches.into_proto();
1276 let spine_batches = self.spine_batches.into_proto();
1277 let merges = self.merges.into_proto();
1278 ProtoTrace {
1279 since: Some(since),
1280 legacy_batches,
1281 hollow_batches,
1282 spine_batches,
1283 merges,
1284 }
1285 }
1286
1287 fn from_proto(proto: ProtoTrace) -> Result<Self, TryFromProtoError> {
1288 let since = proto.since.into_rust_if_some("ProtoTrace::since")?;
1289 let legacy_batches = proto
1290 .legacy_batches
1291 .into_iter()
1292 .map(|b| b.into_rust().map(|b| (b, ())))
1293 .collect::<Result<_, _>>()?;
1294 let hollow_batches = proto.hollow_batches.into_rust()?;
1295 let spine_batches = proto.spine_batches.into_rust()?;
1296 let merges = proto.merges.into_rust()?;
1297 Ok(FlatTrace {
1298 since,
1299 legacy_batches,
1300 hollow_batches,
1301 spine_batches,
1302 merges,
1303 })
1304 }
1305}
1306
1307impl<T: Timestamp + Lattice + Codec64> RustType<ProtoTrace> for Trace<T> {
1308 fn into_proto(&self) -> ProtoTrace {
1309 self.flatten().into_proto()
1310 }
1311
1312 fn from_proto(proto: ProtoTrace) -> Result<Self, TryFromProtoError> {
1313 Trace::unflatten(proto.into_rust()?).map_err(TryFromProtoError::InvalidPersistState)
1314 }
1315}
1316
1317impl<T: Timestamp + Codec64> RustType<ProtoLeasedReaderState> for LeasedReaderState<T> {
1318 fn into_proto(&self) -> ProtoLeasedReaderState {
1319 ProtoLeasedReaderState {
1320 seqno: self.seqno.into_proto(),
1321 since: Some(self.since.into_proto()),
1322 last_heartbeat_timestamp_ms: self.last_heartbeat_timestamp_ms.into_proto(),
1323 lease_duration_ms: self.lease_duration_ms.into_proto(),
1324 debug: Some(self.debug.into_proto()),
1325 }
1326 }
1327
1328 fn from_proto(proto: ProtoLeasedReaderState) -> Result<Self, TryFromProtoError> {
1329 let mut lease_duration_ms = proto.lease_duration_ms.into_rust()?;
1330 if lease_duration_ms == 0 {
1335 lease_duration_ms = u64::try_from(READER_LEASE_DURATION.default().as_millis())
1336 .expect("lease duration as millis should fit within u64");
1337 }
1338 let debug = proto.debug.unwrap_or_default().into_rust()?;
1341 Ok(LeasedReaderState {
1342 seqno: proto.seqno.into_rust()?,
1343 since: proto
1344 .since
1345 .into_rust_if_some("ProtoLeasedReaderState::since")?,
1346 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms.into_rust()?,
1347 lease_duration_ms,
1348 debug,
1349 })
1350 }
1351}
1352
1353impl<T: Timestamp + Codec64> RustType<ProtoCriticalReaderState> for CriticalReaderState<T> {
1354 fn into_proto(&self) -> ProtoCriticalReaderState {
1355 ProtoCriticalReaderState {
1356 since: Some(self.since.into_proto()),
1357 opaque: i64::from_le_bytes(self.opaque.1),
1358 opaque_codec: self.opaque.0.clone(),
1359 debug: Some(self.debug.into_proto()),
1360 }
1361 }
1362
1363 fn from_proto(proto: ProtoCriticalReaderState) -> Result<Self, TryFromProtoError> {
1364 let debug = proto.debug.unwrap_or_default().into_rust()?;
1367 Ok(CriticalReaderState {
1368 since: proto
1369 .since
1370 .into_rust_if_some("ProtoCriticalReaderState::since")?,
1371 opaque: Opaque(proto.opaque_codec, i64::to_le_bytes(proto.opaque)),
1372 debug,
1373 })
1374 }
1375}
1376
1377impl<T: Timestamp + Codec64> RustType<ProtoWriterState> for WriterState<T> {
1378 fn into_proto(&self) -> ProtoWriterState {
1379 ProtoWriterState {
1380 last_heartbeat_timestamp_ms: self.last_heartbeat_timestamp_ms.into_proto(),
1381 lease_duration_ms: self.lease_duration_ms.into_proto(),
1382 most_recent_write_token: self.most_recent_write_token.into_proto(),
1383 most_recent_write_upper: Some(self.most_recent_write_upper.into_proto()),
1384 debug: Some(self.debug.into_proto()),
1385 }
1386 }
1387
1388 fn from_proto(proto: ProtoWriterState) -> Result<Self, TryFromProtoError> {
1389 let most_recent_write_token = if proto.most_recent_write_token.is_empty() {
1395 IdempotencyToken::SENTINEL
1396 } else {
1397 proto.most_recent_write_token.into_rust()?
1398 };
1399 let most_recent_write_upper = match proto.most_recent_write_upper {
1400 Some(x) => x.into_rust()?,
1401 None => Antichain::from_elem(T::minimum()),
1402 };
1403 let debug = proto.debug.unwrap_or_default().into_rust()?;
1406 Ok(WriterState {
1407 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms.into_rust()?,
1408 lease_duration_ms: proto.lease_duration_ms.into_rust()?,
1409 most_recent_write_token,
1410 most_recent_write_upper,
1411 debug,
1412 })
1413 }
1414}
1415
1416impl RustType<ProtoHandleDebugState> for HandleDebugState {
1417 fn into_proto(&self) -> ProtoHandleDebugState {
1418 ProtoHandleDebugState {
1419 hostname: self.hostname.into_proto(),
1420 purpose: self.purpose.into_proto(),
1421 }
1422 }
1423
1424 fn from_proto(proto: ProtoHandleDebugState) -> Result<Self, TryFromProtoError> {
1425 Ok(HandleDebugState {
1426 hostname: proto.hostname,
1427 purpose: proto.purpose,
1428 })
1429 }
1430}
1431
1432impl<T: Timestamp + Codec64> RustType<ProtoHollowRun> for HollowRun<T> {
1433 fn into_proto(&self) -> ProtoHollowRun {
1434 ProtoHollowRun {
1435 parts: self.parts.into_proto(),
1436 }
1437 }
1438
1439 fn from_proto(proto: ProtoHollowRun) -> Result<Self, TryFromProtoError> {
1440 Ok(HollowRun {
1441 parts: proto.parts.into_rust()?,
1442 })
1443 }
1444}
1445
1446impl<T: Timestamp + Codec64> RustType<ProtoHollowBatch> for HollowBatch<T> {
1447 fn into_proto(&self) -> ProtoHollowBatch {
1448 let mut run_meta = self.run_meta.into_proto();
1449 let run_meta_default = RunMeta::default().into_proto();
1451 while run_meta.last() == Some(&run_meta_default) {
1452 run_meta.pop();
1453 }
1454 ProtoHollowBatch {
1455 desc: Some(self.desc.into_proto()),
1456 parts: self.parts.into_proto(),
1457 len: self.len.into_proto(),
1458 runs: self.run_splits.into_proto(),
1459 run_meta,
1460 deprecated_keys: vec![],
1461 }
1462 }
1463
1464 fn from_proto(proto: ProtoHollowBatch) -> Result<Self, TryFromProtoError> {
1465 let mut parts: Vec<RunPart<T>> = proto.parts.into_rust()?;
1466 parts.extend(proto.deprecated_keys.into_iter().map(|key| {
1469 RunPart::Single(BatchPart::Hollow(HollowBatchPart {
1470 key: PartialBatchKey(key),
1471 meta: Default::default(),
1472 encoded_size_bytes: 0,
1473 key_lower: vec![],
1474 structured_key_lower: None,
1475 stats: None,
1476 ts_rewrite: None,
1477 diffs_sum: None,
1478 format: None,
1479 schema_id: None,
1480 deprecated_schema_id: None,
1481 }))
1482 }));
1483 let run_splits: Vec<usize> = proto.runs.into_rust()?;
1485 let num_runs = if parts.is_empty() {
1486 0
1487 } else {
1488 run_splits.len() + 1
1489 };
1490 let mut run_meta: Vec<RunMeta> = proto.run_meta.into_rust()?;
1491 run_meta.resize(num_runs, RunMeta::default());
1492 Ok(HollowBatch {
1493 desc: proto.desc.into_rust_if_some("desc")?,
1494 parts,
1495 len: proto.len.into_rust()?,
1496 run_splits,
1497 run_meta,
1498 })
1499 }
1500}
1501
1502impl RustType<String> for RunId {
1503 fn into_proto(&self) -> String {
1504 self.to_string()
1505 }
1506
1507 fn from_proto(proto: String) -> Result<Self, TryFromProtoError> {
1508 RunId::from_str(&proto).map_err(|_| {
1509 TryFromProtoError::InvalidPersistState(format!("invalid RunId: {}", proto))
1510 })
1511 }
1512}
1513
1514impl RustType<ProtoRunMeta> for RunMeta {
1515 fn into_proto(&self) -> ProtoRunMeta {
1516 let order = match self.order {
1517 None => ProtoRunOrder::Unknown,
1518 Some(RunOrder::Unordered) => ProtoRunOrder::Unordered,
1519 Some(RunOrder::Codec) => ProtoRunOrder::Codec,
1520 Some(RunOrder::Structured) => ProtoRunOrder::Structured,
1521 };
1522 ProtoRunMeta {
1523 order: order.into(),
1524 schema_id: self.schema.into_proto(),
1525 deprecated_schema_id: self.deprecated_schema.into_proto(),
1526 id: self.id.into_proto(),
1527 len: self.len.into_proto(),
1528 meta: self.meta.into_proto(),
1529 }
1530 }
1531
1532 fn from_proto(proto: ProtoRunMeta) -> Result<Self, TryFromProtoError> {
1533 let order = match ProtoRunOrder::try_from(proto.order)? {
1534 ProtoRunOrder::Unknown => None,
1535 ProtoRunOrder::Unordered => Some(RunOrder::Unordered),
1536 ProtoRunOrder::Codec => Some(RunOrder::Codec),
1537 ProtoRunOrder::Structured => Some(RunOrder::Structured),
1538 };
1539 Ok(Self {
1540 order,
1541 schema: proto.schema_id.into_rust()?,
1542 deprecated_schema: proto.deprecated_schema_id.into_rust()?,
1543 id: proto.id.into_rust()?,
1544 len: proto.len.into_rust()?,
1545 meta: proto.meta.into_rust()?,
1546 })
1547 }
1548}
1549
1550impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for RunPart<T> {
1551 fn into_proto(&self) -> ProtoHollowBatchPart {
1552 match self {
1553 RunPart::Single(part) => part.into_proto(),
1554 RunPart::Many(runs) => runs.into_proto(),
1555 }
1556 }
1557
1558 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1559 let run_part = if let Some(proto_hollow_batch_part::Kind::RunRef(_)) = proto.kind {
1560 RunPart::Many(proto.into_rust()?)
1561 } else {
1562 RunPart::Single(proto.into_rust()?)
1563 };
1564 Ok(run_part)
1565 }
1566}
1567
1568impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for HollowRunRef<T> {
1569 fn into_proto(&self) -> ProtoHollowBatchPart {
1570 let part = ProtoHollowBatchPart {
1571 kind: Some(proto_hollow_batch_part::Kind::RunRef(ProtoHollowRunRef {
1572 key: self.key.into_proto(),
1573 max_part_bytes: self.max_part_bytes.into_proto(),
1574 })),
1575 encoded_size_bytes: self.hollow_bytes.into_proto(),
1576 key_lower: Bytes::copy_from_slice(&self.key_lower),
1577 diffs_sum: self.diffs_sum.map(i64::from_le_bytes),
1578 key_stats: None,
1579 ts_rewrite: None,
1580 format: None,
1581 schema_id: None,
1582 structured_key_lower: self.structured_key_lower.into_proto(),
1583 deprecated_schema_id: None,
1584 metadata: BTreeMap::default(),
1585 };
1586 part
1587 }
1588
1589 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1590 let run_proto = match proto.kind {
1591 Some(proto_hollow_batch_part::Kind::RunRef(proto_ref)) => proto_ref,
1592 _ => Err(TryFromProtoError::UnknownEnumVariant(
1593 "ProtoHollowBatchPart::kind".to_string(),
1594 ))?,
1595 };
1596 Ok(Self {
1597 key: run_proto.key.into_rust()?,
1598 hollow_bytes: proto.encoded_size_bytes.into_rust()?,
1599 max_part_bytes: run_proto.max_part_bytes.into_rust()?,
1600 key_lower: proto.key_lower.to_vec(),
1601 structured_key_lower: proto.structured_key_lower.into_rust()?,
1602 diffs_sum: proto.diffs_sum.as_ref().map(|x| i64::to_le_bytes(*x)),
1603 _phantom_data: Default::default(),
1604 })
1605 }
1606}
1607
1608impl<T: Timestamp + Codec64> RustType<ProtoHollowBatchPart> for BatchPart<T> {
1609 fn into_proto(&self) -> ProtoHollowBatchPart {
1610 match self {
1611 BatchPart::Hollow(x) => ProtoHollowBatchPart {
1612 kind: Some(proto_hollow_batch_part::Kind::Key(x.key.into_proto())),
1613 encoded_size_bytes: x.encoded_size_bytes.into_proto(),
1614 key_lower: Bytes::copy_from_slice(&x.key_lower),
1615 structured_key_lower: x.structured_key_lower.as_ref().map(|lazy| lazy.buf.clone()),
1616 key_stats: x.stats.into_proto(),
1617 ts_rewrite: x.ts_rewrite.as_ref().map(|x| x.into_proto()),
1618 diffs_sum: x.diffs_sum.as_ref().map(|x| i64::from_le_bytes(*x)),
1619 format: x.format.map(|f| f.into_proto()),
1620 schema_id: x.schema_id.into_proto(),
1621 deprecated_schema_id: x.deprecated_schema_id.into_proto(),
1622 metadata: BTreeMap::default(),
1623 },
1624 BatchPart::Inline {
1625 updates,
1626 ts_rewrite,
1627 schema_id,
1628 deprecated_schema_id,
1629 } => ProtoHollowBatchPart {
1630 kind: Some(proto_hollow_batch_part::Kind::Inline(updates.into_proto())),
1631 encoded_size_bytes: 0,
1632 key_lower: Bytes::new(),
1633 structured_key_lower: None,
1634 key_stats: None,
1635 ts_rewrite: ts_rewrite.as_ref().map(|x| x.into_proto()),
1636 diffs_sum: None,
1637 format: None,
1638 schema_id: schema_id.into_proto(),
1639 deprecated_schema_id: deprecated_schema_id.into_proto(),
1640 metadata: BTreeMap::default(),
1641 },
1642 }
1643 }
1644
1645 fn from_proto(proto: ProtoHollowBatchPart) -> Result<Self, TryFromProtoError> {
1646 let ts_rewrite = match proto.ts_rewrite {
1647 Some(ts_rewrite) => Some(ts_rewrite.into_rust()?),
1648 None => None,
1649 };
1650 let schema_id = proto.schema_id.into_rust()?;
1651 let deprecated_schema_id = proto.deprecated_schema_id.into_rust()?;
1652 match proto.kind {
1653 Some(proto_hollow_batch_part::Kind::Key(key)) => {
1654 Ok(BatchPart::Hollow(HollowBatchPart {
1655 key: key.into_rust()?,
1656 meta: proto.metadata.into_rust()?,
1657 encoded_size_bytes: proto.encoded_size_bytes.into_rust()?,
1658 key_lower: proto.key_lower.into(),
1659 structured_key_lower: proto.structured_key_lower.into_rust()?,
1660 stats: proto.key_stats.into_rust()?,
1661 ts_rewrite,
1662 diffs_sum: proto.diffs_sum.map(i64::to_le_bytes),
1663 format: proto.format.map(|f| f.into_rust()).transpose()?,
1664 schema_id,
1665 deprecated_schema_id,
1666 }))
1667 }
1668 Some(proto_hollow_batch_part::Kind::Inline(x)) => {
1669 if proto.encoded_size_bytes != 0
1673 || !proto.key_lower.is_empty()
1674 || proto.key_stats.is_some()
1675 || proto.diffs_sum.is_some()
1676 {
1677 return Err(TryFromProtoError::InvalidPersistState(
1678 "inline ProtoHollowBatchPart has hollow-part fields set".into(),
1679 ));
1680 }
1681 let updates = LazyInlineBatchPart(x.into_rust()?);
1682 Ok(BatchPart::Inline {
1683 updates,
1684 ts_rewrite,
1685 schema_id,
1686 deprecated_schema_id,
1687 })
1688 }
1689 _ => Err(TryFromProtoError::unknown_enum_variant(
1690 "ProtoHollowBatchPart::kind",
1691 )),
1692 }
1693 }
1694}
1695
1696impl RustType<proto_hollow_batch_part::Format> for BatchColumnarFormat {
1697 fn into_proto(&self) -> proto_hollow_batch_part::Format {
1698 match self {
1699 BatchColumnarFormat::Row => proto_hollow_batch_part::Format::Row(()),
1700 BatchColumnarFormat::Both(version) => {
1701 proto_hollow_batch_part::Format::RowAndColumnar((*version).cast_into())
1702 }
1703 BatchColumnarFormat::Structured => proto_hollow_batch_part::Format::Structured(()),
1704 }
1705 }
1706
1707 fn from_proto(proto: proto_hollow_batch_part::Format) -> Result<Self, TryFromProtoError> {
1708 let format = match proto {
1709 proto_hollow_batch_part::Format::Row(_) => BatchColumnarFormat::Row,
1710 proto_hollow_batch_part::Format::RowAndColumnar(version) => {
1711 BatchColumnarFormat::Both(version.cast_into())
1712 }
1713 proto_hollow_batch_part::Format::Structured(_) => BatchColumnarFormat::Structured,
1714 };
1715 Ok(format)
1716 }
1717}
1718
1719#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1724pub struct LazyPartStats {
1725 key: LazyProto<ProtoStructStats>,
1726}
1727
1728impl Debug for LazyPartStats {
1729 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1730 let mut f = f.debug_tuple("LazyPartStats");
1731 match self.try_decode() {
1737 Ok(stats) => f.field(&stats).finish(),
1738 Err(err) => f.field(&format_args!("<undecodable: {err}>")).finish(),
1739 }
1740 }
1741}
1742
1743impl LazyPartStats {
1744 pub(crate) fn encode(x: &PartStats, map_proto: impl FnOnce(&mut ProtoStructStats)) -> Self {
1745 let PartStats { key } = x;
1746 let mut proto_stats = ProtoStructStats::from_rust(key);
1747 map_proto(&mut proto_stats);
1748 LazyPartStats {
1749 key: LazyProto::from(&proto_stats),
1750 }
1751 }
1752 pub fn try_decode(&self) -> Result<PartStats, TryFromProtoError> {
1763 let key = self
1764 .key
1765 .decode()
1766 .map_err(|err| TryFromProtoError::InvalidPersistState(err.to_string()))?;
1767 Ok(PartStats {
1768 key: key.into_rust()?,
1769 })
1770 }
1771}
1772
1773impl RustType<Bytes> for LazyPartStats {
1774 fn into_proto(&self) -> Bytes {
1775 let LazyPartStats { key } = self;
1776 key.into_proto()
1777 }
1778
1779 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1780 Ok(LazyPartStats {
1781 key: proto.into_rust()?,
1782 })
1783 }
1784}
1785
1786#[cfg(test)]
1787pub(crate) fn any_some_lazy_part_stats() -> impl Strategy<Value = Option<LazyPartStats>> {
1788 proptest::prelude::any::<LazyPartStats>().prop_map(Some)
1789}
1790
1791#[allow(unused_parens)]
1792impl Arbitrary for LazyPartStats {
1793 type Parameters = ();
1794 type Strategy =
1795 proptest::strategy::Map<(<PartStats as Arbitrary>::Strategy), fn((PartStats)) -> Self>;
1796
1797 fn arbitrary_with(_: ()) -> Self::Strategy {
1798 Strategy::prop_map((proptest::prelude::any::<PartStats>()), |(x)| {
1799 LazyPartStats::encode(&x, |_| {})
1800 })
1801 }
1802}
1803
1804impl ProtoInlineBatchPart {
1805 pub(crate) fn into_rust<T: Timestamp + Codec64>(
1806 lgbytes: &ColumnarMetrics,
1807 proto: Self,
1808 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1809 let updates = proto
1810 .updates
1811 .ok_or_else(|| TryFromProtoError::missing_field("ProtoInlineBatchPart::updates"))?;
1812 let updates = BlobTraceUpdates::from_proto(lgbytes, updates)?;
1813
1814 Ok(BlobTraceBatchPart {
1815 desc: proto.desc.into_rust_if_some("ProtoInlineBatchPart::desc")?,
1816 index: proto.index.into_rust()?,
1817 updates,
1818 })
1819 }
1820}
1821
1822#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1824pub struct LazyInlineBatchPart(LazyProto<ProtoInlineBatchPart>);
1825
1826impl From<&ProtoInlineBatchPart> for LazyInlineBatchPart {
1827 fn from(value: &ProtoInlineBatchPart) -> Self {
1828 LazyInlineBatchPart(value.into())
1829 }
1830}
1831
1832impl Serialize for LazyInlineBatchPart {
1833 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1834 let proto = self.0.decode().expect("valid proto");
1837 let mut s = s.serialize_struct("InlineBatchPart", 3)?;
1838 let () = s.serialize_field("desc", &proto.desc)?;
1839 let () = s.serialize_field("index", &proto.index)?;
1840 let () = s.serialize_field("updates[len]", &proto.updates.map_or(0, |x| x.len))?;
1841 s.end()
1842 }
1843}
1844
1845impl LazyInlineBatchPart {
1846 pub(crate) fn encoded_size_bytes(&self) -> usize {
1847 self.0.buf.len()
1848 }
1849
1850 pub fn decode<T: Timestamp + Codec64>(
1856 &self,
1857 lgbytes: &ColumnarMetrics,
1858 ) -> Result<BlobTraceBatchPart<T>, TryFromProtoError> {
1859 let proto = self.0.decode().expect("valid proto");
1860 ProtoInlineBatchPart::into_rust(lgbytes, proto)
1861 }
1862}
1863
1864impl RustType<Bytes> for LazyInlineBatchPart {
1865 fn into_proto(&self) -> Bytes {
1866 self.0.into_proto()
1867 }
1868
1869 fn from_proto(proto: Bytes) -> Result<Self, TryFromProtoError> {
1870 Ok(LazyInlineBatchPart(proto.into_rust()?))
1871 }
1872}
1873
1874impl RustType<ProtoHollowRollup> for HollowRollup {
1875 fn into_proto(&self) -> ProtoHollowRollup {
1876 ProtoHollowRollup {
1877 key: self.key.into_proto(),
1878 encoded_size_bytes: self.encoded_size_bytes.into_proto(),
1879 finalized: self.finalized,
1880 }
1881 }
1882
1883 fn from_proto(proto: ProtoHollowRollup) -> Result<Self, TryFromProtoError> {
1884 Ok(HollowRollup {
1885 key: proto.key.into_rust()?,
1886 encoded_size_bytes: proto.encoded_size_bytes.into_rust()?,
1887 finalized: proto.finalized,
1888 })
1889 }
1890}
1891
1892impl RustType<ProtoActiveRollup> for ActiveRollup {
1893 fn into_proto(&self) -> ProtoActiveRollup {
1894 ProtoActiveRollup {
1895 start_ms: self.start_ms,
1896 seqno: self.seqno.into_proto(),
1897 }
1898 }
1899
1900 fn from_proto(proto: ProtoActiveRollup) -> Result<Self, TryFromProtoError> {
1901 Ok(ActiveRollup {
1902 start_ms: proto.start_ms,
1903 seqno: proto.seqno.into_rust()?,
1904 })
1905 }
1906}
1907
1908impl RustType<ProtoActiveGc> for ActiveGc {
1909 fn into_proto(&self) -> ProtoActiveGc {
1910 ProtoActiveGc {
1911 start_ms: self.start_ms,
1912 seqno: self.seqno.into_proto(),
1913 }
1914 }
1915
1916 fn from_proto(proto: ProtoActiveGc) -> Result<Self, TryFromProtoError> {
1917 Ok(ActiveGc {
1918 start_ms: proto.start_ms,
1919 seqno: proto.seqno.into_rust()?,
1920 })
1921 }
1922}
1923
1924impl<T: Timestamp + Codec64> RustType<ProtoU64Description> for Description<T> {
1925 fn into_proto(&self) -> ProtoU64Description {
1926 ProtoU64Description {
1927 lower: Some(self.lower().into_proto()),
1928 upper: Some(self.upper().into_proto()),
1929 since: Some(self.since().into_proto()),
1930 }
1931 }
1932
1933 fn from_proto(proto: ProtoU64Description) -> Result<Self, TryFromProtoError> {
1934 let lower: Antichain<T> = proto.lower.into_rust_if_some("lower")?;
1935 if lower.elements().is_empty() {
1938 return Err(TryFromProtoError::InvalidPersistState(
1939 "ProtoU64Description has an empty lower frontier".into(),
1940 ));
1941 }
1942 Ok(Description::new(
1943 lower,
1944 proto.upper.into_rust_if_some("upper")?,
1945 proto.since.into_rust_if_some("since")?,
1946 ))
1947 }
1948}
1949
1950impl<T: Timestamp + Codec64> RustType<ProtoU64Antichain> for Antichain<T> {
1951 fn into_proto(&self) -> ProtoU64Antichain {
1952 ProtoU64Antichain {
1953 elements: self
1954 .elements()
1955 .iter()
1956 .map(|x| i64::from_le_bytes(T::encode(x)))
1957 .collect(),
1958 }
1959 }
1960
1961 fn from_proto(proto: ProtoU64Antichain) -> Result<Self, TryFromProtoError> {
1962 let elements = proto
1963 .elements
1964 .iter()
1965 .map(|x| T::decode(x.to_le_bytes()))
1966 .collect::<Vec<_>>();
1967 Ok(Antichain::from(elements))
1968 }
1969}
1970
1971#[cfg(test)]
1972mod tests {
1973 use mz_ore::assert_none;
1974 use mz_persist_types::stats::{ProtoDynStats, ProtoStructStats};
1975
1976 use bytes::Bytes;
1977 use mz_build_info::DUMMY_BUILD_INFO;
1978 use mz_dyncfg::ConfigUpdates;
1979 use mz_ore::assert_err;
1980 use mz_ore::cast::CastFrom;
1981 use mz_persist::location::SeqNo;
1982 use proptest::prelude::*;
1983
1984 use crate::ShardId;
1985 use crate::internal::paths::PartialRollupKey;
1986 use crate::internal::state::tests::any_state;
1987 use crate::internal::state::{BatchPart, HandleDebugState};
1988 use crate::internal::state_diff::StateDiff;
1989 use crate::tests::new_test_client_cache;
1990
1991 use super::*;
1992
1993 #[mz_ore::test]
1994 fn rollup_inline_batch_part_with_hollow_fields_is_error() {
1995 use mz_proto::ProtoType;
1999 use prost::Message;
2000 let bytes: &[u8] = &[
2001 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x22, 0x06, 0x5a, 0x00, 0x10, 0x02, 0x2a, 0x00,
2002 0x22, 0x00, 0x22, 0x00, 0x22, 0x00,
2003 ];
2004 let proto = crate::internal::state::ProtoRollup::decode(bytes)
2005 .expect("crash input decodes as a proto");
2006 let result: Result<Rollup<u64>, _> = proto.into_rust();
2007 assert_err!(result);
2008 }
2009
2010 #[mz_ore::test]
2011 fn rollup_batch_with_empty_lower_frontier_is_error() {
2012 use mz_proto::ProtoType;
2016 use prost::Message;
2017 let bytes: &[u8] = &[
2018 0x3a, 0x12, 0x0a, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x0a, 0x00, 0x12, 0x00, 0x1a, 0x00,
2019 0x12, 0x00, 0x32, 0x00, 0x22, 0x00,
2020 ];
2021 let proto = crate::internal::state::ProtoRollup::decode(bytes)
2022 .expect("crash input decodes as a proto");
2023 let result: Result<Rollup<u64>, _> = proto.into_rust();
2024 assert_err!(result);
2025 }
2026
2027 #[mz_ore::test]
2028 fn metadata_map() {
2029 const COUNT: MetadataKey<u64> = MetadataKey::new("count");
2030
2031 let mut map = MetadataMap::default();
2032 map.set(COUNT, 100);
2033 let mut map = MetadataMap::from_proto(map.into_proto()).unwrap();
2034 assert_eq!(map.get(COUNT), Some(100));
2035
2036 const ANTICHAIN: MetadataKey<Antichain<u64>, ProtoU64Antichain> =
2037 MetadataKey::new("antichain");
2038 assert_none!(map.get(ANTICHAIN));
2039
2040 map.set(ANTICHAIN, Antichain::from_elem(30));
2041 let map = MetadataMap::from_proto(map.into_proto()).unwrap();
2042 assert_eq!(map.get(COUNT), Some(100));
2043 assert_eq!(map.get(ANTICHAIN), Some(Antichain::from_elem(30)));
2044 }
2045
2046 #[mz_ore::test]
2047 fn applier_version_state() {
2048 let v1 = semver::Version::new(1, 0, 0);
2049 let v2 = semver::Version::new(2, 0, 0);
2050 let v3 = semver::Version::new(3, 0, 0);
2051
2052 let shard_id = ShardId::new();
2054 let state = TypedState::<(), (), u64, i64>::new(v2.clone(), shard_id, "".to_owned(), 0);
2055 let rollup =
2056 Rollup::from_untyped_state_without_diffs(state.clone_for_rollup().into()).into_proto();
2057 let mut buf = Vec::new();
2058 rollup.encode(&mut buf).expect("serializable");
2059 let bytes = Bytes::from(buf);
2060
2061 assert_eq!(
2063 UntypedState::<u64>::decode(&v2, bytes.clone())
2064 .check_codecs(&shard_id)
2065 .as_ref(),
2066 Ok(&state)
2067 );
2068 assert_eq!(
2069 UntypedState::<u64>::decode(&v3, bytes.clone())
2070 .check_codecs(&shard_id)
2071 .as_ref(),
2072 Ok(&state)
2073 );
2074
2075 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| UntypedState::<u64>::decode(&v1, bytes.clone()));
2080 assert_err!(v1_res);
2081 }
2082
2083 #[mz_ore::test]
2084 fn applier_version_state_diff() {
2085 let v1 = semver::Version::new(1, 0, 0);
2086 let v2 = semver::Version::new(2, 0, 0);
2087 let v3 = semver::Version::new(3, 0, 0);
2088
2089 let diff = StateDiff::<u64>::new(
2091 v2.clone(),
2092 SeqNo(0),
2093 SeqNo(1),
2094 2,
2095 PartialRollupKey("rollup".into()),
2096 );
2097 let mut buf = Vec::new();
2098 diff.encode(&mut buf);
2099 let bytes = Bytes::from(buf);
2100
2101 assert_eq!(StateDiff::decode(&v2, bytes.clone()), diff);
2103 assert_eq!(StateDiff::decode(&v3, bytes.clone()), diff);
2104
2105 #[allow(clippy::disallowed_methods)] let v1_res = std::panic::catch_unwind(|| StateDiff::<u64>::decode(&v1, bytes));
2110 assert_err!(v1_res);
2111 }
2112
2113 #[mz_ore::test]
2114 fn hollow_batch_migration_keys() {
2115 let x = HollowBatch::new_run(
2116 Description::new(
2117 Antichain::from_elem(1u64),
2118 Antichain::from_elem(2u64),
2119 Antichain::from_elem(3u64),
2120 ),
2121 vec![RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2122 key: PartialBatchKey("a".into()),
2123 meta: Default::default(),
2124 encoded_size_bytes: 5,
2125 key_lower: vec![],
2126 structured_key_lower: None,
2127 stats: None,
2128 ts_rewrite: None,
2129 diffs_sum: None,
2130 format: None,
2131 schema_id: None,
2132 deprecated_schema_id: None,
2133 }))],
2134 4,
2135 );
2136 let mut old = x.into_proto();
2137 old.deprecated_keys = vec!["b".into()];
2139 let mut expected = x;
2143 expected
2148 .parts
2149 .push(RunPart::Single(BatchPart::Hollow(HollowBatchPart {
2150 key: PartialBatchKey("b".into()),
2151 meta: Default::default(),
2152 encoded_size_bytes: 0,
2153 key_lower: vec![],
2154 structured_key_lower: None,
2155 stats: None,
2156 ts_rewrite: None,
2157 diffs_sum: None,
2158 format: None,
2159 schema_id: None,
2160 deprecated_schema_id: None,
2161 })));
2162 assert_eq!(<HollowBatch<u64>>::from_proto(old).unwrap(), expected);
2163 }
2164
2165 #[mz_ore::test]
2166 fn reader_state_migration_lease_duration() {
2167 let x = LeasedReaderState {
2168 seqno: SeqNo(1),
2169 since: Antichain::from_elem(2u64),
2170 last_heartbeat_timestamp_ms: 3,
2171 debug: HandleDebugState {
2172 hostname: "host".to_owned(),
2173 purpose: "purpose".to_owned(),
2174 },
2175 lease_duration_ms: 0,
2177 };
2178 let old = x.into_proto();
2179 let mut expected = x;
2180 expected.lease_duration_ms =
2183 u64::try_from(READER_LEASE_DURATION.default().as_millis()).unwrap();
2184 assert_eq!(<LeasedReaderState<u64>>::from_proto(old).unwrap(), expected);
2185 }
2186
2187 #[mz_ore::test]
2188 fn writer_state_migration_most_recent_write() {
2189 let proto = ProtoWriterState {
2190 last_heartbeat_timestamp_ms: 1,
2191 lease_duration_ms: 2,
2192 most_recent_write_token: "".into(),
2195 most_recent_write_upper: None,
2196 debug: Some(ProtoHandleDebugState {
2197 hostname: "host".to_owned(),
2198 purpose: "purpose".to_owned(),
2199 }),
2200 };
2201 let expected = WriterState {
2202 last_heartbeat_timestamp_ms: proto.last_heartbeat_timestamp_ms,
2203 lease_duration_ms: proto.lease_duration_ms,
2204 most_recent_write_token: IdempotencyToken::SENTINEL,
2205 most_recent_write_upper: Antichain::from_elem(0),
2206 debug: HandleDebugState {
2207 hostname: "host".to_owned(),
2208 purpose: "purpose".to_owned(),
2209 },
2210 };
2211 assert_eq!(<WriterState<u64>>::from_proto(proto).unwrap(), expected);
2212 }
2213
2214 #[mz_ore::test]
2215 fn state_migration_rollups() {
2216 let r1 = HollowRollup {
2217 key: PartialRollupKey("foo".to_owned()),
2218 encoded_size_bytes: None,
2219 finalized: false,
2220 };
2221 let r2 = HollowRollup {
2222 key: PartialRollupKey("bar".to_owned()),
2223 encoded_size_bytes: Some(2),
2224 finalized: false,
2225 };
2226 let shard_id = ShardId::new();
2227 let mut state = TypedState::<(), (), u64, i64>::new(
2228 DUMMY_BUILD_INFO.semver_version(),
2229 shard_id,
2230 "host".to_owned(),
2231 0,
2232 );
2233 state.state.collections.rollups.insert(SeqNo(2), r2.clone());
2234 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2235
2236 proto.deprecated_rollups.insert(1, r1.key.0.clone());
2238
2239 let state: Rollup<u64> = proto.into_rust().unwrap();
2240 let state = state.state;
2241 let state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2242 let expected = vec![(SeqNo(1), r1), (SeqNo(2), r2)];
2243 assert_eq!(
2244 state
2245 .state
2246 .collections
2247 .rollups
2248 .into_iter()
2249 .collect::<Vec<_>>(),
2250 expected
2251 );
2252 }
2253
2254 #[mz_ore::test]
2261 fn rollup_proto_with_diffs_but_no_rollups_is_rejected() {
2262 let shard_id = ShardId::new();
2263 let mut state = TypedState::<(), (), u64, i64>::new(
2264 DUMMY_BUILD_INFO.semver_version(),
2265 shard_id,
2266 "host".to_owned(),
2267 0,
2268 );
2269 let seqno = state.state.seqno;
2272 state.state.collections.rollups.insert(
2273 seqno,
2274 HollowRollup {
2275 key: PartialRollupKey("foo".to_owned()),
2276 encoded_size_bytes: None,
2277 finalized: false,
2278 },
2279 );
2280 let mut proto = Rollup::from(state.into(), Vec::new()).into_proto();
2281
2282 proto.rollups.clear();
2284 proto.deprecated_rollups.clear();
2285
2286 let result: Result<Rollup<u64>, _> = proto.into_rust();
2287 assert!(
2288 result.is_err(),
2289 "a rollup proto with diffs but no rollups must error, not panic"
2290 );
2291 }
2292
2293 fn rollup_proto_with_trace(update_trace: impl FnOnce(&mut ProtoTrace)) -> ProtoRollup {
2295 let state = TypedState::<(), (), u64, i64>::new(
2296 DUMMY_BUILD_INFO.semver_version(),
2297 ShardId::new(),
2298 "host".to_owned(),
2299 0,
2300 );
2301 let mut proto = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2302 update_trace(proto.trace.as_mut().expect("fresh state has a trace"));
2303 proto
2304 }
2305
2306 fn u64_desc_proto(lower: u64, upper: u64, since: u64) -> ProtoU64Description {
2307 Description::new(
2308 Antichain::from_elem(lower),
2309 Antichain::from_elem(upper),
2310 Antichain::from_elem(since),
2311 )
2312 .into_proto()
2313 }
2314
2315 fn legacy_batch_proto(lower: u64, upper: u64, since: u64) -> ProtoHollowBatch {
2316 ProtoHollowBatch {
2317 desc: Some(u64_desc_proto(lower, upper, since)),
2318 ..Default::default()
2319 }
2320 }
2321
2322 fn rollup_decode_err(proto: ProtoRollup) -> String {
2323 let result: Result<Rollup<u64>, _> = proto.into_rust();
2324 match result {
2325 Ok(_) => panic!("crafted rollup proto must fail to decode"),
2326 Err(err) => err.to_string(),
2327 }
2328 }
2329
2330 #[mz_ore::test]
2337 fn rollup_proto_with_noncontiguous_legacy_batches_is_rejected() {
2338 let proto = rollup_proto_with_trace(|trace| {
2339 trace.legacy_batches.push(legacy_batch_proto(39, 40, 0));
2340 });
2341 let err = rollup_decode_err(proto);
2342 assert!(err.contains("legacy batch lower"), "{err}");
2343 }
2344
2345 #[mz_ore::test]
2349 fn rollup_proto_with_empty_range_legacy_batch_is_rejected() {
2350 let proto = rollup_proto_with_trace(|trace| {
2351 trace.legacy_batches.push(legacy_batch_proto(0, 0, 0));
2352 });
2353 let err = rollup_decode_err(proto);
2354 assert!(err.contains("empty time range"), "{err}");
2355 }
2356
2357 #[mz_ore::test]
2361 fn rollup_proto_with_batch_since_past_trace_since_is_rejected() {
2362 let proto = rollup_proto_with_trace(|trace| {
2363 trace.legacy_batches.push(legacy_batch_proto(0, 1, 5));
2364 });
2365 let err = rollup_decode_err(proto);
2366 assert!(err.contains("past the spine since"), "{err}");
2367 }
2368
2369 #[mz_ore::test]
2373 fn rollup_proto_with_absurd_batch_len_is_rejected() {
2374 let proto = rollup_proto_with_trace(|trace| {
2375 trace.legacy_batches.push(ProtoHollowBatch {
2376 desc: Some(u64_desc_proto(0, 1, 0)),
2377 len: u64::MAX,
2378 ..Default::default()
2379 });
2380 });
2381 let err = rollup_decode_err(proto);
2382 assert!(err.contains("maximum trace size"), "{err}");
2383 }
2384
2385 #[mz_ore::test]
2388 fn rollup_proto_with_absurd_spine_level_is_rejected() {
2389 let proto = rollup_proto_with_trace(|trace| {
2390 trace.spine_batches.push(ProtoIdSpineBatch {
2391 id: Some(SpineId(0, 1).into_proto()),
2392 batch: Some(ProtoSpineBatch {
2393 level: u64::MAX,
2394 desc: Some(u64_desc_proto(0, 1, 0)),
2395 parts: vec![],
2396 descs: vec![],
2397 }),
2398 });
2399 });
2400 let err = rollup_decode_err(proto);
2401 assert!(err.contains("exceeds the maximum"), "{err}");
2402 }
2403
2404 #[mz_ore::test]
2408 fn rollup_proto_with_partless_spine_batch_is_rejected() {
2409 let proto = rollup_proto_with_trace(|trace| {
2410 trace.spine_batches.push(ProtoIdSpineBatch {
2411 id: Some(SpineId(0, 1).into_proto()),
2412 batch: Some(ProtoSpineBatch {
2413 level: 0,
2414 desc: Some(u64_desc_proto(0, 1, 0)),
2415 parts: vec![],
2416 descs: vec![],
2417 }),
2418 });
2419 });
2420 let err = rollup_decode_err(proto);
2421 assert!(err.contains("do not tile"), "{err}");
2422 }
2423
2424 #[mz_ore::test]
2430 fn rollup_proto_with_noncontiguous_spine_parts_is_rejected() {
2431 let outer = SpineId(0, 3);
2432 let part_ids = [SpineId(0, 2), SpineId(1, 3)];
2433 let proto = rollup_proto_with_trace(|trace| {
2434 trace.spine_batches.push(ProtoIdSpineBatch {
2435 id: Some(outer.into_proto()),
2436 batch: Some(ProtoSpineBatch {
2437 level: 0,
2438 desc: Some(u64_desc_proto(0, 3, 0)),
2439 parts: part_ids.iter().map(|id| id.into_proto()).collect(),
2440 descs: vec![],
2441 }),
2442 });
2443 for id in part_ids {
2444 trace.hollow_batches.push(ProtoIdHollowBatch {
2445 id: Some(id.into_proto()),
2446 batch: Some(legacy_batch_proto(0, 1, 0)),
2447 });
2448 }
2449 });
2450 let err = rollup_decode_err(proto);
2451 assert!(err.contains("do not tile"), "{err}");
2452 }
2453
2454 #[mz_ore::test]
2458 fn rollup_proto_with_overfull_spine_level_is_rejected() {
2459 let proto = rollup_proto_with_trace(|trace| {
2460 for i in 0..3u64 {
2461 let id = SpineId(usize::cast_from(i), usize::cast_from(i + 1));
2462 trace.spine_batches.push(ProtoIdSpineBatch {
2463 id: Some(id.into_proto()),
2464 batch: Some(ProtoSpineBatch {
2465 level: 0,
2466 desc: Some(u64_desc_proto(i, i + 1, 0)),
2467 parts: vec![id.into_proto()],
2468 descs: vec![],
2469 }),
2470 });
2471 trace.hollow_batches.push(ProtoIdHollowBatch {
2472 id: Some(id.into_proto()),
2473 batch: Some(legacy_batch_proto(i, i + 1, 0)),
2474 });
2475 }
2476 });
2477 let err = rollup_decode_err(proto);
2478 assert!(err.contains("full layer"), "{err}");
2479 }
2480
2481 #[mz_persist_proc::test(tokio::test)]
2482 #[cfg_attr(miri, ignore)] async fn state_diff_migration_rollups(dyncfgs: ConfigUpdates) {
2484 let r1_rollup = HollowRollup {
2485 key: PartialRollupKey("foo".to_owned()),
2486 encoded_size_bytes: None,
2487 finalized: false,
2488 };
2489 let r1 = StateFieldDiff {
2490 key: SeqNo(1),
2491 val: StateFieldValDiff::Insert(r1_rollup.clone()),
2492 };
2493 let r2_rollup = HollowRollup {
2494 key: PartialRollupKey("bar".to_owned()),
2495 encoded_size_bytes: Some(2),
2496 finalized: false,
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 finalized: false,
2506 };
2507 let r3 = StateFieldDiff {
2508 key: SeqNo(3),
2509 val: StateFieldValDiff::Delete(r3_rollup.clone()),
2510 };
2511 let mut diff = StateDiff::<u64>::new(
2512 DUMMY_BUILD_INFO.semver_version(),
2513 SeqNo(4),
2514 SeqNo(5),
2515 0,
2516 PartialRollupKey("ignored".to_owned()),
2517 );
2518 diff.rollups.push(r2.clone());
2519 diff.rollups.push(r3.clone());
2520 let mut diff_proto = diff.into_proto();
2521
2522 let field_diffs = std::mem::take(&mut diff_proto.field_diffs).unwrap();
2523 let mut field_diffs_writer = field_diffs.into_writer();
2524
2525 field_diffs_into_proto(
2527 ProtoStateField::DeprecatedRollups,
2528 &[StateFieldDiff {
2529 key: r1.key,
2530 val: StateFieldValDiff::Insert(r1_rollup.key.clone()),
2531 }],
2532 &mut field_diffs_writer,
2533 );
2534
2535 assert_none!(diff_proto.field_diffs);
2536 diff_proto.field_diffs = Some(field_diffs_writer.into_proto());
2537
2538 let diff = StateDiff::<u64>::from_proto(diff_proto.clone()).unwrap();
2539 assert_eq!(
2540 diff.rollups.into_iter().collect::<Vec<_>>(),
2541 vec![r2, r3, r1]
2542 );
2543
2544 let shard_id = ShardId::new();
2547 let mut state = TypedState::<(), (), u64, i64>::new(
2548 DUMMY_BUILD_INFO.semver_version(),
2549 shard_id,
2550 "host".to_owned(),
2551 0,
2552 );
2553 state.state.seqno = SeqNo(4);
2554 let mut rollup = Rollup::from_untyped_state_without_diffs(state.into()).into_proto();
2555 rollup
2556 .deprecated_rollups
2557 .insert(3, r3_rollup.key.into_proto());
2558 let state: Rollup<u64> = rollup.into_rust().unwrap();
2559 let state = state.state;
2560 let mut state = state.check_codecs::<(), (), i64>(&shard_id).unwrap();
2561 let cache = new_test_client_cache(&dyncfgs);
2562 let encoded_diff = VersionedData {
2563 seqno: SeqNo(5),
2564 data: diff_proto.encode_to_vec().into(),
2565 };
2566 state.apply_encoded_diffs(cache.cfg(), &cache.metrics, std::iter::once(&encoded_diff));
2567 assert_eq!(
2568 state
2569 .state
2570 .collections
2571 .rollups
2572 .into_iter()
2573 .collect::<Vec<_>>(),
2574 vec![(SeqNo(1), r1_rollup), (SeqNo(2), r2_rollup)]
2575 );
2576 }
2577
2578 #[mz_ore::test]
2579 #[cfg_attr(miri, ignore)] fn state_proto_roundtrip() {
2581 fn testcase<T: Timestamp + Lattice + Codec64>(state: State<T>) {
2582 let before = UntypedState {
2583 key_codec: <() as Codec>::codec_name(),
2584 val_codec: <() as Codec>::codec_name(),
2585 ts_codec: <T as Codec64>::codec_name(),
2586 diff_codec: <i64 as Codec64>::codec_name(),
2587 state,
2588 };
2589 let proto = Rollup::from_untyped_state_without_diffs(before.clone()).into_proto();
2590 let after: Rollup<T> = proto.into_rust().unwrap();
2591 let after = after.state;
2592 assert_eq!(before, after);
2593 }
2594
2595 proptest!(|(state in any_state::<u64>(0..3))| testcase(state));
2596 }
2597
2598 #[mz_ore::test]
2599 fn check_data_versions() {
2600 #[track_caller]
2601 fn testcase(code: &str, data: &str, expected: Result<(), ()>) {
2602 let code = Version::parse(code).unwrap();
2603 let data = Version::parse(data).unwrap();
2604 #[allow(clippy::disallowed_methods)]
2605 let actual = cfg::code_can_write_data(&code, &data)
2606 .then_some(())
2607 .ok_or(());
2608 assert_eq!(actual, expected, "data at {data} read by code {code}");
2609 }
2610
2611 testcase("0.160.0-dev", "0.160.0-dev", Ok(()));
2612 testcase("0.160.0-dev", "0.160.0", Err(()));
2613 testcase("0.160.0-dev", "0.161.0-dev", Err(()));
2616 testcase("0.160.0-dev", "0.161.0", Err(()));
2617 testcase("0.160.0-dev", "0.162.0-dev", Err(()));
2618 testcase("0.160.0-dev", "0.162.0", Err(()));
2619 testcase("0.160.0-dev", "0.163.0-dev", Err(()));
2620
2621 testcase("0.160.0", "0.158.0-dev", Ok(()));
2622 testcase("0.160.0", "0.158.0", Ok(()));
2623 testcase("0.160.0", "0.159.0-dev", Ok(()));
2624 testcase("0.160.0", "0.159.0", Ok(()));
2625 testcase("0.160.0", "0.160.0-dev", Ok(()));
2626 testcase("0.160.0", "0.160.0", Ok(()));
2627
2628 testcase("0.160.0", "0.161.0-dev", Err(()));
2629 testcase("0.160.0", "0.161.0", Err(()));
2630 testcase("0.160.0", "0.161.1", Err(()));
2631 testcase("0.160.0", "0.161.1000000", Err(()));
2632 testcase("0.160.0", "0.162.0-dev", Err(()));
2633 testcase("0.160.0", "0.162.0", Err(()));
2634 testcase("0.160.0", "0.163.0-dev", Err(()));
2635
2636 testcase("0.160.1", "0.159.0", Ok(()));
2637 testcase("0.160.1", "0.160.0", Ok(()));
2638 testcase("0.160.1", "0.161.0", Err(()));
2639 testcase("0.160.1", "0.161.1", Err(()));
2640 testcase("0.160.1", "0.161.100", Err(()));
2641 testcase("0.160.0", "0.160.1", Err(()));
2642
2643 testcase("0.160.1", "26.0.0", Err(()));
2644 testcase("26.0.0", "0.160.1", Ok(()));
2645 testcase("26.2.0", "0.160.1", Ok(()));
2646 testcase("26.200.200", "0.160.1", Ok(()));
2647
2648 testcase("27.0.0", "0.160.1", Err(()));
2649 testcase("27.0.0", "0.16000.1", Err(()));
2650 testcase("27.0.0", "26.0.1", Ok(()));
2651 testcase("27.1000.100", "26.0.1", Ok(()));
2652 testcase("28.0.0", "26.0.1", Err(()));
2653 testcase("28.0.0", "26.1000.1", Err(()));
2654 testcase("28.0.0", "27.0.0", Ok(()));
2655 }
2656
2657 #[mz_ore::test]
2663 fn lazy_part_stats_debug_does_not_panic_on_garbage() {
2664 let stats = LazyPartStats::from_proto(Bytes::from_static(&[0x00, 0xff]))
2666 .expect("stats bytes are stored undecoded");
2667 assert_err!(stats.try_decode());
2668 assert!(format!("{stats:?}").contains("undecodable"));
2669 }
2670
2671 fn version_skewed_part_stats() -> LazyPartStats {
2675 let mut proto = ProtoStructStats::default();
2676 proto.cols.insert("c".into(), ProtoDynStats::default());
2677 let bytes = prost::Message::encode_to_vec(&proto);
2678 LazyPartStats::from_proto(Bytes::from(bytes)).expect("stats bytes are stored undecoded")
2679 }
2680
2681 #[mz_ore::test]
2688 fn part_stats_try_decode_fails_open_on_unknown_variant() {
2689 assert_err!(version_skewed_part_stats().try_decode());
2690 }
2691}