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