1use std::collections::HashSet;
16use std::fmt::{Display, Formatter};
17use std::mem::size_of;
18
19use anyhow::anyhow;
20use bytes::{BufMut, BytesMut};
21use itertools::Itertools;
22use risingwave_hummock_sdk::HummockRawObjectId;
23use risingwave_hummock_sdk::change_log::EpochNewChangeLog;
24use risingwave_hummock_sdk::version::HummockVersion;
25use risingwave_object_store::object::{MonitoredStreamingReader, ObjectDataStreamReader};
26use serde::Serialize;
27use serde::de::DeserializeOwned;
28use tracing::info;
29
30use crate::meta_snapshot::{MetaSnapshot, Metadata, SnapshotPayloadReader, SnapshotPayloadWriter};
31use crate::{BackupError, BackupResult};
32pub type MetaSnapshotV2 = MetaSnapshot<MetadataV2>;
33
34impl From<serde_json::Error> for BackupError {
35 fn from(value: serde_json::Error) -> Self {
36 BackupError::Other(value.into())
37 }
38}
39
40#[macro_export]
42macro_rules! for_all_metadata_models_v2 {
43 ($macro:ident) => {
44 $macro! {
45 {seaql_migrations, risingwave_meta_model::serde_seaql_migration},
46 {version_stats, risingwave_meta_model::hummock_version_stats},
47 {compaction_configs, risingwave_meta_model::compaction_config},
48 {clusters, risingwave_meta_model::cluster},
49 {fragment_relation, risingwave_meta_model::fragment_relation},
50 {catalog_versions, risingwave_meta_model::catalog_version},
51 {connections, risingwave_meta_model::connection},
52 {databases, risingwave_meta_model::database},
53 {fragments, risingwave_meta_model::fragment},
54 {functions, risingwave_meta_model::function},
55 {indexes, risingwave_meta_model::index},
56 {objects, risingwave_meta_model::object},
57 {object_dependencies, risingwave_meta_model::object_dependency},
58 {schemas, risingwave_meta_model::schema},
59 {sinks, risingwave_meta_model::sink},
60 {sources, risingwave_meta_model::source},
61 {streaming_jobs, risingwave_meta_model::streaming_job},
62 {subscriptions, risingwave_meta_model::subscription},
63 {system_parameters, risingwave_meta_model::system_parameter},
64 {tables, risingwave_meta_model::table},
65 {users, risingwave_meta_model::user},
66 {user_privileges, risingwave_meta_model::user_privilege},
67 {views, risingwave_meta_model::view},
68 {workers, risingwave_meta_model::worker},
69 {worker_properties, risingwave_meta_model::worker_property},
70 {hummock_sequences, risingwave_meta_model::hummock_sequence},
71 {session_parameters, risingwave_meta_model::session_parameter},
72 {secrets, risingwave_meta_model::secret},
73 {exactly_once_iceberg_sinks, risingwave_meta_model::exactly_once_iceberg_sink},
74 {iceberg_tables, risingwave_meta_model::iceberg_tables},
75 {iceberg_namespace_properties, risingwave_meta_model::iceberg_namespace_properties},
76 {user_default_privilege, risingwave_meta_model::user_default_privilege},
77 {fragment_splits, risingwave_meta_model::fragment_splits},
78 {pending_sink_state, risingwave_meta_model::pending_sink_state},
79 {refresh_jobs, risingwave_meta_model::refresh_job},
80 {cdc_table_snapshot_splits, risingwave_meta_model::cdc_table_snapshot_split},
81 {hummock_table_change_logs, risingwave_meta_model::hummock_table_change_log}
82 }
83 };
84}
85
86macro_rules! define_metadata_v2 {
87 ($({ $name:ident, $mod_path:ident::$mod_name:ident }),*) => {
88 #[derive(Default)]
89 pub struct MetadataV2 {
90 pub hummock_version: HummockVersion,
91 $(
92 pub $name: Vec<$mod_path::$mod_name::Model>,
93 )*
94 }
95 };
96}
97
98for_all_metadata_models_v2!(define_metadata_v2);
99
100macro_rules! define_encode_metadata_to_writer {
101 ($( {$name:ident, $mod_path:ident::$mod_name:ident} ),*) => {
102 async fn encode_metadata_to_writer(
103 metadata: &MetadataV2,
104 writer: &mut SnapshotPayloadWriter,
105 ) -> BackupResult<()> {
106 let mut _idx = 0;
107 $(
108 if _idx == 1 {
109 write_1(writer, &metadata.hummock_version.to_protobuf()).await?;
110 }
111 write_n(writer, &metadata.$name).await?;
112 _idx += 1;
113 )*
114 Ok(())
115 }
116 };
117}
118
119for_all_metadata_models_v2!(define_encode_metadata_to_writer);
120
121macro_rules! define_decode_metadata {
122 ($( {$name:ident, $mod_path:ident::$mod_name:ident} ),*) => {
123 async fn decode_metadata_from_reader(
124 metadata: &mut MetadataV2,
125 reader: &mut SnapshotPayloadReader,
126 ) -> BackupResult<()> {
127 let mut _idx = 0;
128 $(
129 if _idx == 1 {
130 metadata.hummock_version = HummockVersion::from_persisted_protobuf_owned(read_1(reader).await?);
131 }
132 metadata.$name = read_n(reader).await?;
133 _idx += 1;
134 )*
135 Ok(())
136 }
137 };
138}
139
140for_all_metadata_models_v2!(define_decode_metadata);
141
142const HUMMOCK_SEQUENCES_METADATA_SECTION_INDEX: usize = 26;
148
149pub async fn decode_hummock_sequences_from_stream(
150 stream: MonitoredStreamingReader,
151) -> BackupResult<Vec<risingwave_meta_model::hummock_sequence::Model>> {
152 let mut reader = SnapshotPayloadReader::new(ObjectDataStreamReader::new(stream.into_stream()));
153 let (format_version, _snapshot_id) = reader.read_snapshot_header().await?;
154 if format_version < 2 {
155 return Err(BackupError::Other(anyhow!(
156 "unsupported metadata snapshot format version for hummock sequence partial decoding: {}",
157 format_version
158 )));
159 }
160 for _ in 0..HUMMOCK_SEQUENCES_METADATA_SECTION_INDEX {
161 skip_metadata_list(&mut reader).await?;
162 }
163 let hummock_sequences = read_n(&mut reader).await?;
164 reader.finish_after_skipping_to_end().await?;
165 Ok(hummock_sequences)
166}
167
168impl Display for MetadataV2 {
169 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
170 writeln!(f, "clusters: {:#?}", self.clusters)?;
171 writeln!(
172 f,
173 "Hummock version: id {}, committed_epoch: {:?}",
174 self.hummock_version.id,
175 self.hummock_version.state_table_info.info(),
176 )?;
177 Ok(())
179 }
180}
181
182impl Metadata for MetadataV2 {
183 async fn encode_to_writer(&self, mut writer: SnapshotPayloadWriter) -> BackupResult<()> {
184 encode_metadata_to_writer(self, &mut writer).await?;
185 let snapshot_size_bytes = writer.finish().await?;
186 info!(snapshot_size_bytes, "encoded v2 meta snapshot backup file");
187 Ok(())
188 }
189
190 async fn decode_from_reader(mut reader: SnapshotPayloadReader) -> BackupResult<Self> {
191 let mut metadata = Self::default();
192 decode_metadata_from_reader(&mut metadata, &mut reader).await?;
193 reader.finish_after_skipping_to_end().await?;
194 Ok(metadata)
195 }
196
197 fn hummock_version_ref(&self) -> &HummockVersion {
198 &self.hummock_version
199 }
200
201 fn hummock_version(self) -> HummockVersion {
202 self.hummock_version
203 }
204
205 fn storage_url(&self) -> BackupResult<String> {
206 let storage_url_from_snapshot =
207 Itertools::exactly_one(self.system_parameters.iter().filter_map(|m| {
208 if m.name == "state_store" {
209 return Some(m.value.clone());
210 }
211 None
212 }))
213 .map_err(|_| BackupError::Other(anyhow!("expect state_store")))?;
214 storage_url_from_snapshot
215 .strip_prefix("hummock+")
216 .map(|s| s.to_owned())
217 .ok_or_else(|| {
218 BackupError::Other(anyhow!(
219 "invalid state_store from metadata snapshot: {}",
220 storage_url_from_snapshot
221 ))
222 })
223 }
224
225 fn storage_directory(&self) -> BackupResult<String> {
226 Itertools::exactly_one(self.system_parameters.iter().filter_map(|m| {
227 if m.name == "data_directory" {
228 return Some(m.value.clone());
229 }
230 None
231 }))
232 .map_err(|_| BackupError::Other(anyhow!("expect data_directory")))
233 }
234
235 fn table_change_log_object_ids(&self) -> HashSet<HummockRawObjectId> {
236 self.hummock_table_change_logs
237 .iter()
238 .flat_map(|m| {
239 let EpochNewChangeLog {
241 new_value,
242 old_value,
243 ..
244 } = to_table_change_log(m);
245 new_value
246 .into_iter()
247 .chain(old_value)
248 .map(|t| t.object_id.as_raw())
249 })
250 .collect()
251 }
252}
253
254fn to_table_change_log(
255 change_log: &risingwave_meta_model::hummock_table_change_log::Model,
256) -> EpochNewChangeLog {
257 EpochNewChangeLog {
258 new_value: change_log
259 .new_value_sst
260 .to_protobuf()
261 .into_iter()
262 .map(Into::into)
263 .collect(),
264 old_value: change_log
265 .old_value_sst
266 .to_protobuf()
267 .into_iter()
268 .map(Into::into)
269 .collect(),
270 non_checkpoint_epochs: change_log
271 .non_checkpoint_epochs
272 .0
273 .iter()
274 .map(|e| *e as _)
275 .collect(),
276 checkpoint_epoch: change_log.checkpoint_epoch as _,
277 }
278}
279
280async fn write_len(writer: &mut SnapshotPayloadWriter, len: usize) -> BackupResult<()> {
281 let mut buf = BytesMut::with_capacity(size_of::<u32>());
282 buf.put_u32_le(
283 len.try_into()
284 .unwrap_or_else(|_| panic!("cannot convert {} into u32", len)),
285 );
286 writer.write_snapshot_bytes(buf.freeze()).await
287}
288
289async fn write_n<T: Serialize>(writer: &mut SnapshotPayloadWriter, data: &[T]) -> BackupResult<()> {
290 write_len(writer, data.len()).await?;
291 for d in data {
292 write_with_len_prefix(writer, d).await?;
293 }
294 Ok(())
295}
296
297async fn write_1<T: Serialize>(writer: &mut SnapshotPayloadWriter, data: &T) -> BackupResult<()> {
298 write_n(writer, &[data]).await
299}
300
301async fn read_n<T>(reader: &mut SnapshotPayloadReader) -> BackupResult<Vec<T>>
302where
303 T: DeserializeOwned,
304{
305 let n = reader.read_u32_le().await? as usize;
306 let mut elements = Vec::with_capacity(n);
307 for _ in 0..n {
308 elements.push(read_with_len_prefix(reader).await?);
309 }
310 Ok(elements)
311}
312
313async fn read_1<T>(reader: &mut SnapshotPayloadReader) -> BackupResult<T>
314where
315 T: DeserializeOwned,
316{
317 let elements = read_n(reader).await?;
318 assert_eq!(elements.len(), 1);
319 Ok(elements.into_iter().next().unwrap())
320}
321
322async fn skip_metadata_list(reader: &mut SnapshotPayloadReader) -> BackupResult<()> {
323 let n = reader.read_u32_le().await? as usize;
324 for _ in 0..n {
325 skip_with_len_prefix(reader).await?;
326 }
327 Ok(())
328}
329
330async fn skip_with_len_prefix(reader: &mut SnapshotPayloadReader) -> BackupResult<()> {
331 let len = read_len_prefix(reader).await?;
332 reader.skip_exact(len).await
333}
334
335async fn read_len_prefix(reader: &mut SnapshotPayloadReader) -> BackupResult<usize> {
336 match reader.read_u32_le().await? {
337 0 => {
338 reader.read_u64_le().await?.try_into().map_err(|_| {
339 BackupError::Other(anyhow!("metadata JSON payload length exceeds usize"))
340 })
341 }
342 len => Ok(len as usize),
343 }
344}
345
346async fn write_with_len_prefix<T: Serialize>(
347 writer: &mut SnapshotPayloadWriter,
348 data: &T,
349) -> BackupResult<()> {
350 let b = serde_json::to_vec(data)?;
351 assert!(!b.is_empty());
353 let len_prefix_len = if u32::try_from(b.len()).is_ok() {
354 size_of::<u32>()
355 } else {
356 size_of::<u32>() + size_of::<u64>()
357 };
358 let mut buf = BytesMut::with_capacity(len_prefix_len + b.len());
359 if let Ok(len) = u32::try_from(b.len()) {
360 buf.put_u32_le(len);
361 } else {
362 buf.put_u32_le(0);
363 buf.put_u64_le(
364 b.len()
365 .try_into()
366 .unwrap_or_else(|_| panic!("cannot convert {} into u64", b.len())),
367 );
368 }
369 buf.put_slice(&b);
370 writer.write_snapshot_bytes(buf.freeze()).await
371}
372
373async fn read_with_len_prefix<T>(reader: &mut SnapshotPayloadReader) -> BackupResult<T>
374where
375 T: DeserializeOwned,
376{
377 let len = read_len_prefix(reader).await?;
378 let bytes = reader.read_exact(len).await?;
379 Ok(serde_json::from_slice(&bytes)?)
380}
381
382#[cfg(test)]
383mod tests {
384 use std::sync::Arc;
385
386 use bytes::{Buf, BufMut, Bytes};
387 use risingwave_common::config::ObjectStoreConfig;
388 use risingwave_hummock_sdk::HummockVersionId;
389 use risingwave_meta_model::hummock_sequence;
390 use risingwave_object_store::object::object_metrics::ObjectStoreMetrics;
391 use risingwave_object_store::object::{
392 InMemObjectStore, MonitoredObjectStore, ObjectStoreImpl,
393 };
394
395 use super::*;
396 use crate::meta_snapshot::read_u32_le;
397
398 fn test_store() -> ObjectStoreImpl {
399 ObjectStoreImpl::InMem(MonitoredObjectStore::new(
400 InMemObjectStore::for_test(),
401 Arc::new(ObjectStoreMetrics::unused()),
402 Arc::new(ObjectStoreConfig::default()),
403 ))
404 }
405
406 async fn upload_bytes(store: &ObjectStoreImpl, path: &str, bytes: Vec<u8>) {
407 let mut uploader = store.streaming_upload(path).await.unwrap();
408 uploader.write_bytes(Bytes::from(bytes)).await.unwrap();
409 uploader.finish().await.unwrap();
410 }
411
412 async fn read_bytes(store: &ObjectStoreImpl, path: &str) -> Vec<u8> {
413 store.read(path, ..).await.unwrap().to_vec()
414 }
415
416 async fn upload_snapshot(store: &ObjectStoreImpl, path: &str, snapshot: &MetaSnapshotV2) {
417 let uploader = store.streaming_upload(path).await.unwrap();
418 snapshot.encode_to_uploader(uploader).await.unwrap();
419 }
420
421 async fn snapshot_reader(store: &ObjectStoreImpl, path: &str) -> SnapshotPayloadReader {
422 SnapshotPayloadReader::new(ObjectDataStreamReader::new(
423 store.streaming_read(path, ..).await.unwrap().into_stream(),
424 ))
425 }
426
427 fn append_checksum(mut payload: Vec<u8>) -> Vec<u8> {
428 let checksum = crate::xxhash64_checksum(&payload);
429 payload.put_u64_le(checksum);
430 payload
431 }
432
433 fn read_u64_le(buf: &mut &[u8]) -> BackupResult<u64> {
434 if buf.remaining() < size_of::<u64>() {
435 return Err(BackupError::Other(anyhow!(
436 "metadata snapshot is truncated while reading u64"
437 )));
438 }
439 Ok(buf.get_u64_le())
440 }
441
442 fn read_len_prefix_from_slice(buf: &mut &[u8]) -> BackupResult<usize> {
443 match read_u32_le(buf)? {
444 0 => read_u64_le(buf)?.try_into().map_err(|_| {
445 BackupError::Other(anyhow!("metadata JSON payload length exceeds usize"))
446 }),
447 len => Ok(len as usize),
448 }
449 }
450
451 fn skip_with_len_prefix_from_slice(buf: &mut &[u8]) -> BackupResult<()> {
452 let len = read_len_prefix_from_slice(buf)?;
453 if buf.remaining() < len {
454 return Err(BackupError::Other(anyhow!(
455 "metadata JSON payload length {} exceeds remaining buffer {}",
456 len,
457 buf.remaining()
458 )));
459 }
460 buf.advance(len);
461 Ok(())
462 }
463
464 fn skip_metadata_list_from_slice(buf: &mut &[u8]) -> BackupResult<()> {
465 let n = read_u32_le(buf)? as usize;
466 for _ in 0..n {
467 skip_with_len_prefix_from_slice(buf)?;
468 }
469 Ok(())
470 }
471
472 fn put_with_len_prefix(buf: &mut Vec<u8>, data: &impl Serialize) {
473 let payload = serde_json::to_vec(data).unwrap();
474 if let Ok(len) = u32::try_from(payload.len()) {
475 buf.put_u32_le(len);
476 } else {
477 buf.put_u32_le(0);
478 buf.put_u64_le(payload.len() as u64);
479 }
480 buf.put_slice(&payload);
481 }
482
483 fn put_n(buf: &mut Vec<u8>, data: &[impl Serialize]) {
484 buf.put_u32_le(data.len() as u32);
485 for item in data {
486 put_with_len_prefix(buf, item);
487 }
488 }
489
490 async fn encoded_snapshot_with_invalid_trailing_metadata_after_hummock_sequences(
491 metadata: &MetadataV2,
492 ) -> Vec<u8> {
493 let store = test_store();
494 let snapshot = MetaSnapshotV2 {
495 format_version: 2,
496 id: 123,
497 metadata: MetadataV2 {
498 hummock_sequences: metadata.hummock_sequences.clone(),
499 ..Default::default()
500 },
501 };
502 upload_snapshot(&store, "snapshot", &snapshot).await;
503 let encoded = read_bytes(&store, "snapshot").await;
504 let payload = &encoded[..encoded.len() - size_of::<u64>()];
505 let mut metadata_buf = &payload[size_of::<u32>() + size_of::<u64>()..];
506
507 for _ in 0..=HUMMOCK_SEQUENCES_METADATA_SECTION_INDEX {
508 skip_metadata_list_from_slice(&mut metadata_buf).unwrap();
509 }
510
511 let mut partial_payload = payload[..payload.len() - metadata_buf.len()].to_vec();
512 partial_payload.put_u32_le(1);
513 partial_payload.put_u32_le(100);
514 partial_payload.put_slice(br#""short""#);
515 append_checksum(partial_payload)
516 }
517
518 async fn append_future_metadata_section(store: &ObjectStoreImpl, path: &str) -> Vec<u8> {
519 let mut encoded = read_bytes(store, path).await;
520 encoded.truncate(encoded.len() - size_of::<u64>());
521 put_n(&mut encoded, &["future section"]);
522 append_checksum(encoded)
523 }
524
525 #[tokio::test]
526 async fn test_len_prefix_legacy_u32_round_trip() {
527 let store = test_store();
528 let uploader = store.streaming_upload("len-prefix").await.unwrap();
529 let mut writer = SnapshotPayloadWriter::new(uploader);
530 write_with_len_prefix(&mut writer, &"hello").await.unwrap();
531 writer.finish().await.unwrap();
532
533 let encoded = read_bytes(&store, "len-prefix").await;
534 assert_ne!(u32::from_le_bytes(encoded[..4].try_into().unwrap()), 0);
535
536 let mut reader = snapshot_reader(&store, "len-prefix").await;
537 let decoded: String = read_with_len_prefix(&mut reader).await.unwrap();
538 reader.finish().await.unwrap();
539 assert_eq!(decoded, "hello");
540 }
541
542 #[tokio::test]
543 async fn test_len_prefix_extended_u64_decoding() {
544 let payload = serde_json::to_vec("hello").unwrap();
545 let mut bytes = vec![];
546 bytes.put_u32_le(0);
547 bytes.put_u64_le(payload.len() as u64);
548 bytes.put_slice(&payload);
549
550 let store = test_store();
551 upload_bytes(&store, "extended-len-prefix", append_checksum(bytes)).await;
552
553 let mut reader = snapshot_reader(&store, "extended-len-prefix").await;
554 let decoded: String = read_with_len_prefix(&mut reader).await.unwrap();
555 reader.finish().await.unwrap();
556 assert_eq!(decoded, "hello");
557 }
558
559 #[tokio::test]
560 async fn test_len_prefix_errors_on_missing_extended_u64() {
561 let store = test_store();
562 let mut bytes = vec![];
563 bytes.put_u32_le(0);
564 upload_bytes(&store, "missing-extended-len", bytes).await;
565
566 let mut reader = snapshot_reader(&store, "missing-extended-len").await;
567 assert!(read_with_len_prefix::<String>(&mut reader).await.is_err());
568 }
569
570 #[tokio::test]
571 async fn test_len_prefix_errors_on_payload_shorter_than_declared_len() {
572 let store = test_store();
573 let mut bytes = vec![];
574 bytes.put_u32_le(100);
575 bytes.put_slice(br#""hello""#);
576 upload_bytes(&store, "short-payload", bytes).await;
577
578 let mut reader = snapshot_reader(&store, "short-payload").await;
579 assert!(read_with_len_prefix::<String>(&mut reader).await.is_err());
580 }
581
582 #[tokio::test]
583 async fn test_snapshot_v2_encoding_decoding() {
584 let store = test_store();
585 let mut metadata = MetadataV2::default();
586 metadata.hummock_version.id = HummockVersionId::new(123);
587 let raw = MetaSnapshotV2 {
588 format_version: 2,
589 id: 123,
590 metadata,
591 };
592
593 upload_snapshot(&store, "snapshot-v2", &raw).await;
594 let decoded = MetaSnapshotV2::decode_from_stream(
595 store.streaming_read("snapshot-v2", ..).await.unwrap(),
596 )
597 .await
598 .unwrap();
599
600 assert_eq!(decoded.format_version, raw.format_version);
601 assert_eq!(decoded.id, raw.id);
602 assert_eq!(
603 decoded.metadata.hummock_version.id,
604 raw.metadata.hummock_version.id
605 );
606 }
607
608 #[tokio::test]
609 async fn test_partial_decode_hummock_sequences() {
610 let store = test_store();
611 let raw = MetaSnapshotV2 {
612 format_version: 2,
613 id: 123,
614 metadata: MetadataV2 {
615 hummock_sequences: vec![
616 hummock_sequence::Model {
617 name: "meta_backup".to_owned(),
618 seq: 42,
619 },
620 hummock_sequence::Model {
621 name: "sstable_object".to_owned(),
622 seq: 100,
623 },
624 ],
625 ..Default::default()
626 },
627 };
628
629 upload_snapshot(&store, "partial-decode", &raw).await;
630 let decoded = decode_hummock_sequences_from_stream(
631 store.streaming_read("partial-decode", ..).await.unwrap(),
632 )
633 .await
634 .unwrap();
635 assert_eq!(decoded, raw.metadata.hummock_sequences);
636 }
637
638 #[tokio::test]
639 async fn test_partial_decode_hummock_sequences_ignores_trailing_metadata() {
640 let metadata = MetadataV2 {
641 hummock_sequences: vec![hummock_sequence::Model {
642 name: "meta_backup".to_owned(),
643 seq: 42,
644 }],
645 ..Default::default()
646 };
647 let encoded =
648 encoded_snapshot_with_invalid_trailing_metadata_after_hummock_sequences(&metadata)
649 .await;
650 let store = test_store();
651 upload_bytes(&store, "invalid-trailing", encoded).await;
652
653 let decoded = decode_hummock_sequences_from_stream(
654 store.streaming_read("invalid-trailing", ..).await.unwrap(),
655 )
656 .await
657 .unwrap();
658 assert_eq!(decoded, metadata.hummock_sequences);
659
660 let full_decode_result = MetaSnapshotV2::decode_from_stream(
661 store.streaming_read("invalid-trailing", ..).await.unwrap(),
662 )
663 .await;
664 assert!(
665 full_decode_result.is_err(),
666 "full metadata decoding should not accept the intentionally invalid trailing section"
667 );
668 }
669
670 #[tokio::test]
671 async fn test_partial_decode_hummock_sequences_ignores_appended_future_sections() {
672 let store = test_store();
673 let raw = MetaSnapshotV2 {
674 format_version: 2,
675 id: 123,
676 metadata: MetadataV2 {
677 hummock_sequences: vec![hummock_sequence::Model {
678 name: "meta_backup".to_owned(),
679 seq: 42,
680 }],
681 ..Default::default()
682 },
683 };
684
685 upload_snapshot(&store, "future-section-source", &raw).await;
686 let encoded = append_future_metadata_section(&store, "future-section-source").await;
687 upload_bytes(&store, "future-section", encoded).await;
688
689 let decoded = decode_hummock_sequences_from_stream(
690 store.streaming_read("future-section", ..).await.unwrap(),
691 )
692 .await
693 .unwrap();
694 assert_eq!(decoded, raw.metadata.hummock_sequences);
695
696 let decoded = MetaSnapshotV2::decode_from_stream(
697 store.streaming_read("future-section", ..).await.unwrap(),
698 )
699 .await
700 .unwrap();
701 assert_eq!(decoded.format_version, raw.format_version);
702 assert_eq!(decoded.id, raw.id);
703 assert_eq!(
704 decoded.metadata.hummock_sequences,
705 raw.metadata.hummock_sequences
706 );
707 }
708}