1use std::collections::{HashMap, HashSet};
16use std::fmt::{Display, Formatter};
17use std::time::Duration;
18
19use base64::Engine;
20use common_error::ext::{ErrorExt, RetryHint};
21use common_error::status_code::StatusCode;
22use serde::{Deserialize, Deserializer, Serialize, Serializer};
23use snafu::ResultExt as _;
24use store_api::region_engine::SyncRegionFromRequest;
25use store_api::region_request::{RegionFlushReason, RegionRequirements};
26use store_api::storage::{FileId, FileRef, FileRefsManifest, GcReport, RegionId, RegionNumber};
27use strum::Display;
28use table::metadata::TableId;
29use table::table_name::TableName;
30
31use crate::error::{DecodePackedFileRefsSnafu, InvalidPackedFileRefsSnafu};
32use crate::flow_name::FlowName;
33use crate::key::schema_name::SchemaName;
34use crate::key::{FlowId, FlowPartitionId};
35use crate::peer::Peer;
36use crate::wal_provider::{RegionWalOptions, region_wal_options_serde};
37use crate::{DatanodeId, FlownodeId};
38
39#[derive(Debug, Serialize, PartialEq, Eq, Clone)]
41pub struct InstructionError {
42 #[serde(
44 serialize_with = "StatusCode::serialize_as_u32",
45 deserialize_with = "StatusCode::deserialize_from_u32"
46 )]
47 pub code: StatusCode,
48 pub message: String,
50 #[serde(
52 serialize_with = "InstructionError::serialize_retry_hint",
53 deserialize_with = "RetryHint::deserialize_from_str"
54 )]
55 pub retry_hint: RetryHint,
56}
57
58impl<'de> Deserialize<'de> for InstructionError {
59 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
60 where
61 D: Deserializer<'de>,
62 {
63 #[derive(Deserialize)]
64 #[serde(untagged)]
65 enum Compat {
66 Structured {
67 #[serde(deserialize_with = "StatusCode::deserialize_from_u32")]
68 code: StatusCode,
69 message: String,
70 #[serde(deserialize_with = "RetryHint::deserialize_from_str")]
71 retry_hint: RetryHint,
72 },
73 Legacy(String),
74 }
75
76 match Compat::deserialize(deserializer)? {
77 Compat::Structured {
78 code,
79 message,
80 retry_hint,
81 } => Ok(Self {
82 code,
83 message,
84 retry_hint,
85 }),
86 Compat::Legacy(message) => Ok(Self::legacy_internal_retryable(message)),
87 }
88 }
89}
90
91impl InstructionError {
92 pub fn new(code: StatusCode, message: impl Into<String>, retry_hint: RetryHint) -> Self {
93 Self {
94 code,
95 message: message.into(),
96 retry_hint,
97 }
98 }
99
100 pub fn legacy_internal_retryable(message: impl Into<String>) -> Self {
101 Self::new(StatusCode::Internal, message, RetryHint::Retryable)
102 }
103
104 pub fn from_error<E: ErrorExt>(error: &E) -> Self {
105 Self {
106 code: error.status_code(),
107 message: error.output_msg(),
108 retry_hint: error.retry_hint(),
109 }
110 }
111
112 fn serialize_retry_hint<S>(hint: &RetryHint, serializer: S) -> Result<S::Ok, S::Error>
114 where
115 S: Serializer,
116 {
117 let hint = match hint {
118 RetryHint::RetryAfterRouteRefresh => RetryHint::Retryable,
119 hint => *hint,
120 };
121 RetryHint::serialize_as_str(&hint, serializer)
122 }
123}
124
125impl Display for InstructionError {
126 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "InstructionError(code={}, retry_hint={}, message={})",
130 self.code as u32,
131 self.retry_hint.as_str(),
132 self.message
133 )
134 }
135}
136
137pub type InstructionResult<T> = std::result::Result<T, InstructionError>;
138
139#[derive(Eq, Hash, PartialEq, Clone, Debug, Serialize, Deserialize)]
140pub struct RegionIdent {
141 pub datanode_id: DatanodeId,
142 pub table_id: TableId,
143 pub region_number: RegionNumber,
144 pub engine: String,
145}
146
147impl RegionIdent {
148 pub fn get_region_id(&self) -> RegionId {
149 RegionId::new(self.table_id, self.region_number)
150 }
151}
152
153impl Display for RegionIdent {
154 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155 write!(
156 f,
157 "RegionIdent(datanode_id='{}', table_id={}, region_number={}, engine = {})",
158 self.datanode_id, self.table_id, self.region_number, self.engine
159 )
160 }
161}
162
163#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
165pub struct DowngradeRegionReply {
166 #[serde(default)]
169 pub region_id: RegionId,
170 pub last_entry_id: Option<u64>,
172 pub metadata_last_entry_id: Option<u64>,
174 pub exists: bool,
176 pub error: Option<InstructionError>,
178}
179
180impl Display for DowngradeRegionReply {
181 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
182 write!(
183 f,
184 "(last_entry_id={:?}, exists={}, error={:?})",
185 self.last_entry_id, self.exists, self.error
186 )
187 }
188}
189
190#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
191pub struct SimpleReply {
192 pub result: bool,
193 pub error: Option<InstructionError>,
194}
195
196#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
198pub struct FlushRegionReply {
199 pub results: Vec<(RegionId, InstructionResult<()>)>,
203 pub overall_success: bool,
205}
206
207impl FlushRegionReply {
208 pub fn success_single(region_id: RegionId) -> Self {
210 Self {
211 results: vec![(region_id, Ok(()))],
212 overall_success: true,
213 }
214 }
215
216 pub fn error_single(region_id: RegionId, error: InstructionError) -> Self {
218 Self {
219 results: vec![(region_id, Err(error))],
220 overall_success: false,
221 }
222 }
223
224 pub fn from_results(results: Vec<(RegionId, InstructionResult<()>)>) -> Self {
226 let overall_success = results.iter().all(|(_, result)| result.is_ok());
227 Self {
228 results,
229 overall_success,
230 }
231 }
232
233 pub fn to_simple_reply(&self) -> SimpleReply {
235 if self.overall_success {
236 SimpleReply {
237 result: true,
238 error: None,
239 }
240 } else {
241 let errors: Vec<String> = self
242 .results
243 .iter()
244 .filter_map(|(region_id, result)| {
245 result
246 .as_ref()
247 .err()
248 .map(|err| format!("{}: {}", region_id, err))
249 })
250 .collect();
251 SimpleReply {
252 result: false,
253 error: Some(InstructionError::legacy_internal_retryable(
254 errors.join("; "),
255 )),
256 }
257 }
258 }
259}
260
261impl Display for SimpleReply {
262 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
263 write!(f, "(result={}, error={:?})", self.result, self.error)
264 }
265}
266
267impl Display for FlushRegionReply {
268 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
269 let results_str = self
270 .results
271 .iter()
272 .map(|(region_id, result)| match result {
273 Ok(()) => format!("{}:OK", region_id),
274 Err(err) => format!("{}:ERR({})", region_id, err),
275 })
276 .collect::<Vec<_>>()
277 .join(", ");
278 write!(
279 f,
280 "(overall_success={}, results=[{}])",
281 self.overall_success, results_str
282 )
283 }
284}
285
286impl Display for OpenRegion {
287 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
288 write!(
289 f,
290 "OpenRegion(region_ident={}, region_storage_path={}, reason={:?})",
291 self.region_ident, self.region_storage_path, self.reason
292 )
293 }
294}
295
296#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
298pub enum OpenRegionReason {
299 RegionMigration,
301 RegionFailover,
303 #[cfg(feature = "enterprise")]
305 RegionFollower,
306}
307
308#[serde_with::serde_as]
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
310pub struct OpenRegion {
311 pub region_ident: RegionIdent,
312 pub region_storage_path: String,
313 pub region_options: HashMap<String, String>,
314 #[serde(default)]
315 #[serde(with = "region_wal_options_serde")]
316 pub region_wal_options: RegionWalOptions,
317 #[serde(default)]
318 pub skip_wal_replay: bool,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub reason: Option<OpenRegionReason>,
321 #[serde(default)]
322 pub requirements: RegionRequirements,
323}
324
325impl OpenRegion {
326 pub fn new(
327 region_ident: RegionIdent,
328 path: &str,
329 region_options: HashMap<String, String>,
330 region_wal_options: RegionWalOptions,
331 skip_wal_replay: bool,
332 reason: Option<OpenRegionReason>,
333 requirements: RegionRequirements,
334 ) -> Self {
335 Self {
336 region_ident,
337 region_storage_path: path.to_string(),
338 region_options,
339 region_wal_options,
340 skip_wal_replay,
341 reason,
342 requirements,
343 }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
349pub struct DowngradeRegion {
350 pub region_id: RegionId,
352 #[serde(default)]
356 pub flush_timeout: Option<Duration>,
357}
358
359impl Display for DowngradeRegion {
360 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
361 write!(
362 f,
363 "DowngradeRegion(region_id={}, flush_timeout={:?})",
364 self.region_id, self.flush_timeout,
365 )
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
371pub struct UpgradeRegion {
372 pub region_id: RegionId,
374 pub last_entry_id: Option<u64>,
376 pub metadata_last_entry_id: Option<u64>,
378 #[serde(with = "humantime_serde")]
383 pub replay_timeout: Duration,
384 #[serde(default)]
386 pub location_id: Option<u64>,
387 #[serde(default, skip_serializing_if = "Option::is_none")]
388 pub replay_entry_id: Option<u64>,
389 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub metadata_replay_entry_id: Option<u64>,
391}
392
393impl UpgradeRegion {
394 pub fn with_replay_entry_id(mut self, replay_entry_id: Option<u64>) -> Self {
396 self.replay_entry_id = replay_entry_id;
397 self
398 }
399
400 pub fn with_metadata_replay_entry_id(mut self, metadata_replay_entry_id: Option<u64>) -> Self {
402 self.metadata_replay_entry_id = metadata_replay_entry_id;
403 self
404 }
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
408pub enum CacheIdent {
410 FlowId(FlowId),
411 FlowNodeAddressChange(u64),
413 FlowName(FlowName),
414 TableId(TableId),
415 TableName(TableName),
416 SchemaName(SchemaName),
417 CreateFlow(CreateFlow),
418 DropFlow(DropFlow),
419 User(UserCacheIdent),
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
424pub struct UserCacheIdent {
425 pub catalog: String,
426 pub username: String,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
430pub struct CreateFlow {
431 pub flow_id: FlowId,
433 pub source_table_ids: Vec<TableId>,
434 pub partition_to_peer_mapping: Vec<(FlowPartitionId, Peer)>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
439pub struct DropFlow {
440 pub flow_id: FlowId,
441 pub source_table_ids: Vec<TableId>,
442 pub flow_part2node_id: Vec<(FlowPartitionId, FlownodeId)>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
448pub enum FlushStrategy {
449 #[default]
451 Sync,
452 Async,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
458pub enum FlushErrorStrategy {
459 #[default]
461 FailFast,
462 TryAll,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
469pub struct FlushRegions {
470 pub region_ids: Vec<RegionId>,
472 #[serde(default)]
474 pub strategy: FlushStrategy,
475 #[serde(default)]
477 pub error_strategy: FlushErrorStrategy,
478 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub reason: Option<RegionFlushReason>,
481}
482
483impl Display for FlushRegions {
484 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
485 write!(
486 f,
487 "FlushRegions(region_ids={:?}, strategy={:?}, error_strategy={:?}, reason={:?})",
488 self.region_ids, self.strategy, self.error_strategy, self.reason
489 )
490 }
491}
492
493impl FlushRegions {
494 pub fn sync_single(region_id: RegionId) -> Self {
496 Self {
497 region_ids: vec![region_id],
498 strategy: FlushStrategy::Sync,
499 error_strategy: FlushErrorStrategy::FailFast,
500 reason: None,
501 }
502 }
503
504 pub fn async_batch(region_ids: Vec<RegionId>) -> Self {
506 Self {
507 region_ids,
508 strategy: FlushStrategy::Async,
509 error_strategy: FlushErrorStrategy::TryAll,
510 reason: None,
511 }
512 }
513
514 pub fn sync_batch(region_ids: Vec<RegionId>, error_strategy: FlushErrorStrategy) -> Self {
516 Self {
517 region_ids,
518 strategy: FlushStrategy::Sync,
519 error_strategy,
520 reason: None,
521 }
522 }
523
524 pub fn with_reason(mut self, reason: RegionFlushReason) -> Self {
525 self.reason = Some(reason);
526 self
527 }
528
529 pub fn is_single_region(&self) -> bool {
531 self.region_ids.len() == 1
532 }
533
534 pub fn single_region_id(&self) -> Option<RegionId> {
536 if self.is_single_region() {
537 self.region_ids.first().copied()
538 } else {
539 None
540 }
541 }
542
543 pub fn is_hint(&self) -> bool {
545 matches!(self.strategy, FlushStrategy::Async)
546 }
547
548 pub fn is_sync(&self) -> bool {
550 matches!(self.strategy, FlushStrategy::Sync)
551 }
552}
553
554impl From<RegionId> for FlushRegions {
555 fn from(region_id: RegionId) -> Self {
556 Self::sync_single(region_id)
557 }
558}
559
560#[derive(Debug, Deserialize)]
561#[serde(untagged)]
562enum SingleOrMultiple<T> {
563 Single(T),
564 Multiple(Vec<T>),
565}
566
567fn single_or_multiple_from<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
568where
569 D: Deserializer<'de>,
570 T: Deserialize<'de>,
571{
572 let helper = SingleOrMultiple::<T>::deserialize(deserializer)?;
573 Ok(match helper {
574 SingleOrMultiple::Single(x) => vec![x],
575 SingleOrMultiple::Multiple(xs) => xs,
576 })
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
581pub struct GetFileRefs {
582 pub query_regions: Vec<RegionId>,
584 pub related_regions: HashMap<RegionId, HashSet<RegionId>>,
589}
590
591impl Display for GetFileRefs {
592 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
593 write!(f, "GetFileRefs(region_ids={:?})", self.query_regions)
594 }
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
599pub struct GetPackedFileRefs {
600 pub query_regions: Vec<RegionId>,
602 pub related_regions: HashMap<RegionId, HashSet<RegionId>>,
607}
608
609impl Display for GetPackedFileRefs {
610 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
611 write!(f, "GetPackedFileRefs(region_ids={:?})", self.query_regions)
612 }
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
617pub struct GcRegions {
618 pub regions: Vec<RegionId>,
620 pub file_refs_manifest: FileRefsManifest,
622 pub full_file_listing: bool,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
628pub struct PackedGcRegions {
629 pub regions: Vec<RegionId>,
631 pub packed_file_refs_manifest: PackedFileRefsManifest,
633 pub full_file_listing: bool,
635}
636
637impl Display for PackedGcRegions {
638 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
639 let file_refs_count = self.packed_file_refs_manifest.file_refs.len();
640 write!(
641 f,
642 "PackedGcRegions(regions={:?}, file_refs_count={}, full_file_listing={})",
643 self.regions, file_refs_count, self.full_file_listing
644 )
645 }
646}
647
648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
650pub struct PackedFileRefsManifest {
651 pub file_refs: HashMap<RegionId, PackedRegionFileRefs>,
653 #[serde(default)]
654 pub manifest_version: HashMap<RegionId, u64>,
655 #[serde(default)]
656 pub cross_region_refs: HashMap<RegionId, HashSet<RegionId>>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
668pub struct PackedRegionFileRefs {
669 pub files: String,
671 pub indexed: String,
673}
674
675impl PackedFileRefsManifest {
676 pub fn from_manifest(manifest: &FileRefsManifest) -> Self {
684 Self {
685 file_refs: manifest
686 .file_refs
687 .iter()
688 .map(|(region, refs)| (*region, PackedRegionFileRefs::from_refs(refs)))
689 .collect(),
690 manifest_version: manifest.manifest_version.clone(),
691 cross_region_refs: manifest.cross_region_refs.clone(),
692 }
693 }
694
695 pub fn into_manifest(self) -> crate::error::Result<FileRefsManifest> {
700 let mut file_refs = HashMap::new();
701 for (region, encoded) in self.file_refs {
702 file_refs.insert(region, encoded.into_refs(region)?);
703 }
704 Ok(FileRefsManifest {
705 file_refs,
706 manifest_version: self.manifest_version,
707 cross_region_refs: self.cross_region_refs,
708 })
709 }
710}
711
712impl PackedRegionFileRefs {
713 pub fn from_refs(refs: &HashSet<FileRef>) -> Self {
715 let engine = base64::engine::general_purpose::STANDARD;
716 let mut files = Vec::new();
717 let mut indexed = Vec::new();
718 for file_ref in refs {
719 match file_ref.index_version {
720 None => files.extend_from_slice(file_ref.file_id.as_bytes()),
721 Some(version) => {
722 indexed.extend_from_slice(file_ref.file_id.as_bytes());
723 indexed.extend_from_slice(&version.to_be_bytes());
724 }
725 }
726 }
727 Self {
728 files: engine.encode(files),
729 indexed: engine.encode(indexed),
730 }
731 }
732
733 fn into_refs(self, region: RegionId) -> crate::error::Result<HashSet<FileRef>> {
735 let engine = base64::engine::general_purpose::STANDARD;
736 let files = engine
737 .decode(self.files)
738 .context(DecodePackedFileRefsSnafu)?;
739 let indexed = engine
740 .decode(self.indexed)
741 .context(DecodePackedFileRefsSnafu)?;
742 if files.len() % 16 != 0 || indexed.len() % 24 != 0 {
743 return InvalidPackedFileRefsSnafu.fail();
744 }
745
746 let mut refs = HashSet::new();
747 for bytes in files.as_chunks::<16>().0 {
748 let mut id = [0; 16];
749 id.copy_from_slice(bytes);
750 refs.insert(FileRef::new(region, FileId::from_bytes(id), None));
751 }
752 for bytes in indexed.as_chunks::<24>().0 {
753 let mut id = [0; 16];
754 id.copy_from_slice(&bytes[..16]);
755 let mut version = [0; 8];
756 version.copy_from_slice(&bytes[16..]);
757 refs.insert(FileRef::new(
758 region,
759 FileId::from_bytes(id),
760 Some(u64::from_be_bytes(version)),
761 ));
762 }
763 Ok(refs)
764 }
765}
766
767impl Display for GcRegions {
768 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
769 write!(
770 f,
771 "GcRegion(regions={:?}, file_refs_count={}, full_file_listing={})",
772 self.regions,
773 self.file_refs_manifest.file_refs.len(),
774 self.full_file_listing
775 )
776 }
777}
778
779#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
781pub struct GetFileRefsReply {
782 pub file_refs_manifest: FileRefsManifest,
784 pub success: bool,
786 pub error: Option<InstructionError>,
788}
789
790impl Display for GetFileRefsReply {
791 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
792 write!(
793 f,
794 "GetFileRefsReply(success={}, file_refs_count={}, error={:?})",
795 self.success,
796 self.file_refs_manifest.file_refs.len(),
797 self.error
798 )
799 }
800}
801
802#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
804pub struct GetPackedFileRefsReply {
805 pub packed_file_refs_manifest: PackedFileRefsManifest,
806 pub success: bool,
807 pub error: Option<InstructionError>,
808}
809
810impl Display for GetPackedFileRefsReply {
811 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
812 write!(
813 f,
814 "GetPackedFileRefsReply(success={}, file_refs_count={}, error={:?})",
815 self.success,
816 self.packed_file_refs_manifest.file_refs.len(),
817 self.error
818 )
819 }
820}
821
822#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
824pub struct GcRegionsReply {
825 pub result: InstructionResult<GcReport>,
826}
827
828impl Display for GcRegionsReply {
829 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
830 write!(
831 f,
832 "GcReply(result={})",
833 match &self.result {
834 Ok(report) => format!(
835 "GcReport(deleted_files_count={}, need_retry_regions_count={})",
836 report.deleted_files.len(),
837 report.need_retry_regions.len()
838 ),
839 Err(err) => format!("Err({})", err),
840 }
841 )
842 }
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
846pub struct EnterStagingRegion {
847 pub region_id: RegionId,
848 #[serde(
849 alias = "partition_expr",
850 deserialize_with = "deserialize_enter_staging_partition_directive",
851 serialize_with = "serialize_enter_staging_partition_directive"
852 )]
853 pub partition_directive: StagingPartitionDirective,
854}
855
856impl Display for EnterStagingRegion {
857 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
858 write!(
859 f,
860 "EnterStagingRegion(region_id={}, partition_directive={})",
861 self.region_id, self.partition_directive
862 )
863 }
864}
865
866#[derive(Debug, Clone, PartialEq, Eq)]
867pub enum StagingPartitionDirective {
868 UpdatePartitionExpr(String),
869 RejectAllWrites,
870}
871
872impl StagingPartitionDirective {
873 pub fn as_partition_expr(&self) -> Option<&str> {
875 match self {
876 Self::UpdatePartitionExpr(expr) => Some(expr),
877 Self::RejectAllWrites => None,
878 }
879 }
880}
881
882impl Display for StagingPartitionDirective {
883 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
884 match self {
885 Self::UpdatePartitionExpr(expr) => write!(f, "UpdatePartitionExpr({})", expr),
886 Self::RejectAllWrites => write!(f, "RejectAllWrites"),
887 }
888 }
889}
890
891fn serialize_enter_staging_partition_directive<S>(
892 rule: &StagingPartitionDirective,
893 serializer: S,
894) -> std::result::Result<S::Ok, S::Error>
895where
896 S: Serializer,
897{
898 match rule {
899 StagingPartitionDirective::UpdatePartitionExpr(expr) => serializer.serialize_str(expr),
900 StagingPartitionDirective::RejectAllWrites => {
901 #[derive(Serialize)]
902 struct RejectAllWritesSer<'a> {
903 r#type: &'a str,
904 }
905
906 RejectAllWritesSer {
907 r#type: "reject_all_writes",
908 }
909 .serialize(serializer)
910 }
911 }
912}
913
914fn deserialize_enter_staging_partition_directive<'de, D>(
915 deserializer: D,
916) -> std::result::Result<StagingPartitionDirective, D::Error>
917where
918 D: Deserializer<'de>,
919{
920 #[derive(Deserialize)]
921 #[serde(untagged)]
922 enum Compat {
923 Legacy(String),
924 TypeTagged { r#type: String },
925 }
926
927 match Compat::deserialize(deserializer)? {
928 Compat::Legacy(expr) => Ok(StagingPartitionDirective::UpdatePartitionExpr(expr)),
929 Compat::TypeTagged { r#type } if r#type == "reject_all_writes" => {
930 Ok(StagingPartitionDirective::RejectAllWrites)
931 }
932 Compat::TypeTagged { r#type } => Err(serde::de::Error::custom(format!(
933 "Unknown enter staging partition directive type: {}",
934 r#type
935 ))),
936 }
937}
938
939#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
941pub struct SyncRegion {
942 pub region_id: RegionId,
944 pub request: SyncRegionFromRequest,
946}
947
948impl Display for SyncRegion {
949 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
950 write!(
951 f,
952 "SyncRegion(region_id={}, request={:?})",
953 self.region_id, self.request
954 )
955 }
956}
957
958#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
959pub struct RemapManifest {
960 pub region_id: RegionId,
961 pub input_regions: Vec<RegionId>,
963 pub region_mapping: HashMap<RegionId, Vec<RegionId>>,
965 pub new_partition_exprs: HashMap<RegionId, String>,
967}
968
969impl Display for RemapManifest {
970 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
971 write!(
972 f,
973 "RemapManifest(region_id={}, input_regions={:?}, region_mapping={:?}, new_partition_exprs={:?})",
974 self.region_id, self.input_regions, self.region_mapping, self.new_partition_exprs
975 )
976 }
977}
978
979#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
980pub struct ApplyStagingManifest {
981 pub region_id: RegionId,
983 pub partition_expr: String,
985 pub central_region_id: RegionId,
987 pub manifest_path: String,
989}
990
991impl Display for ApplyStagingManifest {
992 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
993 write!(
994 f,
995 "ApplyStagingManifest(region_id={}, partition_expr={}, central_region_id={}, manifest_path={})",
996 self.region_id, self.partition_expr, self.central_region_id, self.manifest_path
997 )
998 }
999}
1000
1001#[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq)]
1002pub enum Instruction {
1003 #[serde(deserialize_with = "single_or_multiple_from", alias = "OpenRegion")]
1005 OpenRegions(Vec<OpenRegion>),
1006 #[serde(deserialize_with = "single_or_multiple_from", alias = "CloseRegion")]
1008 CloseRegions(Vec<RegionIdent>),
1009 #[serde(deserialize_with = "single_or_multiple_from", alias = "UpgradeRegion")]
1011 UpgradeRegions(Vec<UpgradeRegion>),
1012 #[serde(
1013 deserialize_with = "single_or_multiple_from",
1014 alias = "DowngradeRegion"
1015 )]
1016 DowngradeRegions(Vec<DowngradeRegion>),
1018 InvalidateCaches(Vec<CacheIdent>),
1020 FlushRegions(FlushRegions),
1022 GetFileRefs(GetFileRefs),
1024 GetPackedFileRefs(GetPackedFileRefs),
1026 GcRegions(GcRegions),
1028 PackedGcRegions(PackedGcRegions),
1030 Suspend,
1032 EnterStagingRegions(Vec<EnterStagingRegion>),
1034 SyncRegions(Vec<SyncRegion>),
1036 RemapManifest(RemapManifest),
1038
1039 ApplyStagingManifests(Vec<ApplyStagingManifest>),
1041}
1042
1043impl Instruction {
1044 pub fn into_open_regions(self) -> Option<Vec<OpenRegion>> {
1046 match self {
1047 Self::OpenRegions(open_regions) => Some(open_regions),
1048 _ => None,
1049 }
1050 }
1051
1052 pub fn into_close_regions(self) -> Option<Vec<RegionIdent>> {
1054 match self {
1055 Self::CloseRegions(close_regions) => Some(close_regions),
1056 _ => None,
1057 }
1058 }
1059
1060 pub fn into_flush_regions(self) -> Option<FlushRegions> {
1062 match self {
1063 Self::FlushRegions(flush_regions) => Some(flush_regions),
1064 _ => None,
1065 }
1066 }
1067
1068 pub fn into_downgrade_regions(self) -> Option<Vec<DowngradeRegion>> {
1070 match self {
1071 Self::DowngradeRegions(downgrade_region) => Some(downgrade_region),
1072 _ => None,
1073 }
1074 }
1075
1076 pub fn into_upgrade_regions(self) -> Option<Vec<UpgradeRegion>> {
1078 match self {
1079 Self::UpgradeRegions(upgrade_region) => Some(upgrade_region),
1080 _ => None,
1081 }
1082 }
1083
1084 pub fn into_get_file_refs(self) -> Option<GetFileRefs> {
1085 match self {
1086 Self::GetFileRefs(get_file_refs) => Some(get_file_refs),
1087 _ => None,
1088 }
1089 }
1090
1091 pub fn into_gc_regions(self) -> Option<GcRegions> {
1092 match self {
1093 Self::GcRegions(gc_regions) => Some(gc_regions),
1094 _ => None,
1095 }
1096 }
1097
1098 pub fn into_enter_staging_regions(self) -> Option<Vec<EnterStagingRegion>> {
1099 match self {
1100 Self::EnterStagingRegions(enter_staging) => Some(enter_staging),
1101 _ => None,
1102 }
1103 }
1104
1105 pub fn into_sync_regions(self) -> Option<Vec<SyncRegion>> {
1106 match self {
1107 Self::SyncRegions(sync_regions) => Some(sync_regions),
1108 _ => None,
1109 }
1110 }
1111}
1112
1113#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1115pub struct UpgradeRegionReply {
1116 #[serde(default)]
1119 pub region_id: RegionId,
1120 pub ready: bool,
1122 pub exists: bool,
1124 pub error: Option<InstructionError>,
1126}
1127
1128impl Display for UpgradeRegionReply {
1129 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1130 write!(
1131 f,
1132 "(ready={}, exists={}, error={:?})",
1133 self.ready, self.exists, self.error
1134 )
1135 }
1136}
1137
1138#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1139pub struct DowngradeRegionsReply {
1140 pub replies: Vec<DowngradeRegionReply>,
1141}
1142
1143impl DowngradeRegionsReply {
1144 pub fn new(replies: Vec<DowngradeRegionReply>) -> Self {
1145 Self { replies }
1146 }
1147
1148 pub fn single(reply: DowngradeRegionReply) -> Self {
1149 Self::new(vec![reply])
1150 }
1151}
1152
1153#[derive(Deserialize)]
1154#[serde(untagged)]
1155enum DowngradeRegionsCompat {
1156 Single(DowngradeRegionReply),
1157 Multiple(DowngradeRegionsReply),
1158}
1159
1160fn downgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<DowngradeRegionsReply, D::Error>
1161where
1162 D: Deserializer<'de>,
1163{
1164 let helper = DowngradeRegionsCompat::deserialize(deserializer)?;
1165 Ok(match helper {
1166 DowngradeRegionsCompat::Single(x) => DowngradeRegionsReply::new(vec![x]),
1167 DowngradeRegionsCompat::Multiple(reply) => reply,
1168 })
1169}
1170
1171#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1172pub struct UpgradeRegionsReply {
1173 pub replies: Vec<UpgradeRegionReply>,
1174}
1175
1176impl UpgradeRegionsReply {
1177 pub fn new(replies: Vec<UpgradeRegionReply>) -> Self {
1178 Self { replies }
1179 }
1180
1181 pub fn single(reply: UpgradeRegionReply) -> Self {
1182 Self::new(vec![reply])
1183 }
1184}
1185
1186#[derive(Deserialize)]
1187#[serde(untagged)]
1188enum UpgradeRegionsCompat {
1189 Single(UpgradeRegionReply),
1190 Multiple(UpgradeRegionsReply),
1191}
1192
1193fn upgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<UpgradeRegionsReply, D::Error>
1194where
1195 D: Deserializer<'de>,
1196{
1197 let helper = UpgradeRegionsCompat::deserialize(deserializer)?;
1198 Ok(match helper {
1199 UpgradeRegionsCompat::Single(x) => UpgradeRegionsReply::new(vec![x]),
1200 UpgradeRegionsCompat::Multiple(reply) => reply,
1201 })
1202}
1203
1204#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1205pub struct EnterStagingRegionReply {
1206 pub region_id: RegionId,
1207 pub ready: bool,
1209 pub exists: bool,
1211 pub error: Option<InstructionError>,
1213}
1214
1215#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1216pub struct EnterStagingRegionsReply {
1217 pub replies: Vec<EnterStagingRegionReply>,
1218}
1219
1220impl EnterStagingRegionsReply {
1221 pub fn new(replies: Vec<EnterStagingRegionReply>) -> Self {
1222 Self { replies }
1223 }
1224}
1225
1226#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1228pub struct SyncRegionReply {
1229 pub region_id: RegionId,
1231 pub ready: bool,
1233 pub exists: bool,
1235 pub error: Option<InstructionError>,
1237}
1238
1239#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1241pub struct SyncRegionsReply {
1242 pub replies: Vec<SyncRegionReply>,
1243}
1244
1245impl SyncRegionsReply {
1246 pub fn new(replies: Vec<SyncRegionReply>) -> Self {
1247 Self { replies }
1248 }
1249}
1250
1251#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1252pub struct RemapManifestReply {
1253 pub exists: bool,
1255 pub manifest_paths: HashMap<RegionId, String>,
1257 pub error: Option<InstructionError>,
1259}
1260
1261impl Display for RemapManifestReply {
1262 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1263 write!(
1264 f,
1265 "RemapManifestReply(manifest_paths={:?}, error={:?})",
1266 self.manifest_paths, self.error
1267 )
1268 }
1269}
1270
1271#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1272pub struct ApplyStagingManifestsReply {
1273 pub replies: Vec<ApplyStagingManifestReply>,
1274}
1275
1276impl ApplyStagingManifestsReply {
1277 pub fn new(replies: Vec<ApplyStagingManifestReply>) -> Self {
1278 Self { replies }
1279 }
1280}
1281
1282#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1283pub struct ApplyStagingManifestReply {
1284 pub region_id: RegionId,
1285 pub ready: bool,
1287 pub exists: bool,
1289 pub error: Option<InstructionError>,
1291}
1292
1293#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1294#[serde(tag = "type", rename_all = "snake_case")]
1295pub enum InstructionReply {
1296 #[serde(alias = "open_region")]
1297 OpenRegions(SimpleReply),
1298 #[serde(alias = "close_region")]
1299 CloseRegions(SimpleReply),
1300 #[serde(
1301 deserialize_with = "upgrade_regions_compat_from",
1302 alias = "upgrade_region"
1303 )]
1304 UpgradeRegions(UpgradeRegionsReply),
1305 #[serde(
1306 alias = "downgrade_region",
1307 deserialize_with = "downgrade_regions_compat_from"
1308 )]
1309 DowngradeRegions(DowngradeRegionsReply),
1310 FlushRegions(FlushRegionReply),
1311 GetFileRefs(GetFileRefsReply),
1312 GetPackedFileRefs(GetPackedFileRefsReply),
1313 GcRegions(GcRegionsReply),
1314 EnterStagingRegions(EnterStagingRegionsReply),
1315 SyncRegions(SyncRegionsReply),
1316 RemapManifest(RemapManifestReply),
1317 ApplyStagingManifests(ApplyStagingManifestsReply),
1318}
1319
1320impl Display for InstructionReply {
1321 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1322 match self {
1323 Self::OpenRegions(reply) => write!(f, "InstructionReply::OpenRegions({})", reply),
1324 Self::CloseRegions(reply) => write!(f, "InstructionReply::CloseRegions({})", reply),
1325 Self::UpgradeRegions(reply) => {
1326 write!(f, "InstructionReply::UpgradeRegions({:?})", reply.replies)
1327 }
1328 Self::DowngradeRegions(reply) => {
1329 write!(f, "InstructionReply::DowngradeRegions({:?})", reply.replies)
1330 }
1331 Self::FlushRegions(reply) => write!(f, "InstructionReply::FlushRegions({})", reply),
1332 Self::GetFileRefs(reply) => write!(f, "InstructionReply::GetFileRefs({})", reply),
1333 Self::GetPackedFileRefs(reply) => {
1334 write!(f, "InstructionReply::GetPackedFileRefs({})", reply)
1335 }
1336 Self::GcRegions(reply) => write!(f, "InstructionReply::GcRegion({})", reply),
1337 Self::EnterStagingRegions(reply) => {
1338 write!(
1339 f,
1340 "InstructionReply::EnterStagingRegions({:?})",
1341 reply.replies
1342 )
1343 }
1344 Self::SyncRegions(reply) => {
1345 write!(f, "InstructionReply::SyncRegions({:?})", reply.replies)
1346 }
1347 Self::RemapManifest(reply) => write!(f, "InstructionReply::RemapManifest({})", reply),
1348 Self::ApplyStagingManifests(reply) => write!(
1349 f,
1350 "InstructionReply::ApplyStagingManifests({:?})",
1351 reply.replies
1352 ),
1353 }
1354 }
1355}
1356
1357#[cfg(any(test, feature = "testing"))]
1358impl InstructionReply {
1359 pub fn expect_close_regions_reply(self) -> SimpleReply {
1360 match self {
1361 Self::CloseRegions(reply) => reply,
1362 _ => panic!("Expected CloseRegions reply"),
1363 }
1364 }
1365
1366 pub fn expect_open_regions_reply(self) -> SimpleReply {
1367 match self {
1368 Self::OpenRegions(reply) => reply,
1369 _ => panic!("Expected OpenRegions reply"),
1370 }
1371 }
1372
1373 pub fn expect_upgrade_regions_reply(self) -> Vec<UpgradeRegionReply> {
1374 match self {
1375 Self::UpgradeRegions(reply) => reply.replies,
1376 _ => panic!("Expected UpgradeRegion reply"),
1377 }
1378 }
1379
1380 pub fn expect_downgrade_regions_reply(self) -> Vec<DowngradeRegionReply> {
1381 match self {
1382 Self::DowngradeRegions(reply) => reply.replies,
1383 _ => panic!("Expected DowngradeRegion reply"),
1384 }
1385 }
1386
1387 pub fn expect_flush_regions_reply(self) -> FlushRegionReply {
1388 match self {
1389 Self::FlushRegions(reply) => reply,
1390 _ => panic!("Expected FlushRegions reply"),
1391 }
1392 }
1393
1394 pub fn expect_enter_staging_regions_reply(self) -> Vec<EnterStagingRegionReply> {
1395 match self {
1396 Self::EnterStagingRegions(reply) => reply.replies,
1397 _ => panic!("Expected EnterStagingRegion reply"),
1398 }
1399 }
1400
1401 pub fn expect_sync_regions_reply(self) -> Vec<SyncRegionReply> {
1402 match self {
1403 Self::SyncRegions(reply) => reply.replies,
1404 _ => panic!("Expected SyncRegion reply"),
1405 }
1406 }
1407
1408 pub fn expect_remap_manifest_reply(self) -> RemapManifestReply {
1409 match self {
1410 Self::RemapManifest(reply) => reply,
1411 _ => panic!("Expected RemapManifest reply"),
1412 }
1413 }
1414
1415 pub fn expect_apply_staging_manifests_reply(self) -> Vec<ApplyStagingManifestReply> {
1416 match self {
1417 Self::ApplyStagingManifests(reply) => reply.replies,
1418 _ => panic!("Expected ApplyStagingManifest reply"),
1419 }
1420 }
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use std::collections::HashSet;
1426 use std::error::Error as _;
1427
1428 use common_error::mock::MockError;
1429 use common_wal::options::WalOptions;
1430 use store_api::storage::{FileId, FileRef};
1431
1432 use super::*;
1433
1434 #[test]
1435 fn test_instruction_error_serde() {
1436 let error = InstructionError::new(
1437 StatusCode::RegionNotFound,
1438 "region not found",
1439 RetryHint::Retryable,
1440 );
1441
1442 let serialized = serde_json::to_string(&error).unwrap();
1443 assert_eq!(
1444 r#"{"code":4005,"message":"region not found","retry_hint":"retryable"}"#,
1445 serialized
1446 );
1447
1448 let deserialized: InstructionError = serde_json::from_str(&serialized).unwrap();
1449 assert_eq!(error, deserialized);
1450
1451 assert!(
1452 serde_json::from_str::<InstructionError>(
1453 r#"{"code":999999,"message":"unknown","retry_hint":"retryable"}"#
1454 )
1455 .is_err()
1456 );
1457 assert!(
1458 serde_json::from_str::<InstructionError>(
1459 r#"{"code":4005,"message":"unknown","retry_hint":"unknown"}"#
1460 )
1461 .is_err()
1462 );
1463 }
1464
1465 #[test]
1466 fn test_instruction_error_route_refresh_compatibility() {
1467 let error = InstructionError::new(
1468 StatusCode::RegionNotFound,
1469 "region not found",
1470 RetryHint::RetryAfterRouteRefresh,
1471 );
1472 let serialized = serde_json::to_string(&error).unwrap();
1473 assert_eq!(
1474 r#"{"code":4005,"message":"region not found","retry_hint":"retryable"}"#,
1475 serialized
1476 );
1477 let decoded: InstructionError = serde_json::from_str(&serialized).unwrap();
1478 assert_eq!(decoded.code, error.code);
1479 assert_eq!(decoded.message, error.message);
1480 assert_eq!(decoded.retry_hint, RetryHint::Retryable);
1481 assert_eq!(error.retry_hint, RetryHint::RetryAfterRouteRefresh);
1482 assert_eq!(error.retry_hint.as_str(), "retry_after_route_refresh");
1483 }
1484
1485 #[test]
1486 fn test_instruction_error_from_error() {
1487 let error = MockError::new(StatusCode::RegionNotFound);
1488
1489 let instruction_error = InstructionError::from_error(&error);
1490
1491 assert_eq!(StatusCode::RegionNotFound, instruction_error.code);
1492 assert_eq!("RegionNotFound", instruction_error.message);
1493 assert_eq!(RetryHint::NonRetryable, instruction_error.retry_hint);
1494 }
1495
1496 #[test]
1497 fn test_instruction_result_serde() {
1498 let success: InstructionResult<bool> = Ok(true);
1499 let serialized = serde_json::to_string(&success).unwrap();
1500 assert_eq!(r#"{"Ok":true}"#, serialized);
1501 let deserialized: InstructionResult<bool> = serde_json::from_str(&serialized).unwrap();
1502 assert_eq!(success, deserialized);
1503
1504 let failure: InstructionResult<bool> = Err(InstructionError::new(
1505 StatusCode::RegionBusy,
1506 "region busy",
1507 RetryHint::Retryable,
1508 ));
1509 let serialized = serde_json::to_string(&failure).unwrap();
1510 assert_eq!(
1511 r#"{"Err":{"code":4009,"message":"region busy","retry_hint":"retryable"}}"#,
1512 serialized
1513 );
1514 let deserialized: InstructionResult<bool> = serde_json::from_str(&serialized).unwrap();
1515 assert_eq!(failure, deserialized);
1516 }
1517
1518 #[test]
1519 fn test_serialize_instruction() {
1520 let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1521 RegionIdent {
1522 datanode_id: 2,
1523 table_id: 1024,
1524 region_number: 1,
1525 engine: "mito2".to_string(),
1526 },
1527 "test/foo",
1528 HashMap::new(),
1529 HashMap::new(),
1530 false,
1531 None,
1532 RegionRequirements::empty(),
1533 )]);
1534
1535 let serialized = serde_json::to_string(&open_region).unwrap();
1536 assert_eq!(
1537 r#"{"OpenRegions":[{"region_ident":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"},"region_storage_path":"test/foo","region_options":{},"region_wal_options":{},"skip_wal_replay":false,"requirements":{"object_storage":false}}]}"#,
1538 serialized
1539 );
1540
1541 let close_region = Instruction::CloseRegions(vec![RegionIdent {
1542 datanode_id: 2,
1543 table_id: 1024,
1544 region_number: 1,
1545 engine: "mito2".to_string(),
1546 }]);
1547
1548 let serialized = serde_json::to_string(&close_region).unwrap();
1549 assert_eq!(
1550 r#"{"CloseRegions":[{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}]}"#,
1551 serialized
1552 );
1553
1554 let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1555 region_id: RegionId::new(1024, 1),
1556 last_entry_id: None,
1557 metadata_last_entry_id: None,
1558 replay_timeout: Duration::from_millis(1000),
1559 location_id: None,
1560 replay_entry_id: None,
1561 metadata_replay_entry_id: None,
1562 }]);
1563
1564 let serialized = serde_json::to_string(&upgrade_region).unwrap();
1565 assert_eq!(
1566 r#"{"UpgradeRegions":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"replay_timeout":"1s","location_id":null}]}"#,
1567 serialized
1568 );
1569 }
1570
1571 #[test]
1572 fn test_serialize_instruction_reply() {
1573 let downgrade_region_reply = InstructionReply::DowngradeRegions(
1574 DowngradeRegionsReply::single(DowngradeRegionReply {
1575 region_id: RegionId::new(1024, 1),
1576 last_entry_id: None,
1577 metadata_last_entry_id: None,
1578 exists: true,
1579 error: None,
1580 }),
1581 );
1582
1583 let serialized = serde_json::to_string(&downgrade_region_reply).unwrap();
1584 assert_eq!(
1585 r#"{"type":"downgrade_regions","replies":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"exists":true,"error":null}]}"#,
1586 serialized
1587 );
1588
1589 let upgrade_region_reply =
1590 InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1591 region_id: RegionId::new(1024, 1),
1592 ready: true,
1593 exists: true,
1594 error: None,
1595 }));
1596 let serialized = serde_json::to_string(&upgrade_region_reply).unwrap();
1597 assert_eq!(
1598 r#"{"type":"upgrade_regions","replies":[{"region_id":4398046511105,"ready":true,"exists":true,"error":null}]}"#,
1599 serialized
1600 );
1601 }
1602
1603 #[test]
1604 fn test_deserialize_instruction() {
1605 let open_region_instruction = r#"{"OpenRegion":{"region_ident":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"},"region_storage_path":"test/foo","region_options":{},"region_wal_options":{},"skip_wal_replay":false}}"#;
1607 let open_region_instruction: Instruction =
1608 serde_json::from_str(open_region_instruction).unwrap();
1609 let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1610 RegionIdent {
1611 datanode_id: 2,
1612 table_id: 1024,
1613 region_number: 1,
1614 engine: "mito2".to_string(),
1615 },
1616 "test/foo",
1617 HashMap::new(),
1618 HashMap::new(),
1619 false,
1620 None,
1621 RegionRequirements::empty(),
1622 )]);
1623 assert_eq!(open_region_instruction, open_region);
1624
1625 let close_region_instruction = r#"{"CloseRegion":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}}"#;
1627 let close_region_instruction: Instruction =
1628 serde_json::from_str(close_region_instruction).unwrap();
1629 let close_region = Instruction::CloseRegions(vec![RegionIdent {
1630 datanode_id: 2,
1631 table_id: 1024,
1632 region_number: 1,
1633 engine: "mito2".to_string(),
1634 }]);
1635 assert_eq!(close_region_instruction, close_region);
1636
1637 let downgrade_region_instruction = r#"{"DowngradeRegions":{"region_id":4398046511105,"flush_timeout":{"secs":1,"nanos":0}}}"#;
1639 let downgrade_region_instruction: Instruction =
1640 serde_json::from_str(downgrade_region_instruction).unwrap();
1641 let downgrade_region = Instruction::DowngradeRegions(vec![DowngradeRegion {
1642 region_id: RegionId::new(1024, 1),
1643 flush_timeout: Some(Duration::from_millis(1000)),
1644 }]);
1645 assert_eq!(downgrade_region_instruction, downgrade_region);
1646
1647 let upgrade_region_instruction = r#"{"UpgradeRegion":{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"replay_timeout":"1s","location_id":null,"replay_entry_id":null,"metadata_replay_entry_id":null}}"#;
1649 let upgrade_region_instruction: Instruction =
1650 serde_json::from_str(upgrade_region_instruction).unwrap();
1651 let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1652 region_id: RegionId::new(1024, 1),
1653 last_entry_id: None,
1654 metadata_last_entry_id: None,
1655 replay_timeout: Duration::from_millis(1000),
1656 location_id: None,
1657 replay_entry_id: None,
1658 metadata_replay_entry_id: None,
1659 }]);
1660 assert_eq!(upgrade_region_instruction, upgrade_region);
1661 }
1662
1663 #[test]
1664 fn test_deserialize_instruction_reply() {
1665 let close_region_instruction_reply =
1667 r#"{"result":true,"error":null,"type":"close_region"}"#;
1668 let close_region_instruction_reply: InstructionReply =
1669 serde_json::from_str(close_region_instruction_reply).unwrap();
1670 let close_region_reply = InstructionReply::CloseRegions(SimpleReply {
1671 result: true,
1672 error: None,
1673 });
1674 assert_eq!(close_region_instruction_reply, close_region_reply);
1675
1676 let open_region_instruction_reply = r#"{"result":true,"error":null,"type":"open_region"}"#;
1678 let open_region_instruction_reply: InstructionReply =
1679 serde_json::from_str(open_region_instruction_reply).unwrap();
1680 let open_region_reply = InstructionReply::OpenRegions(SimpleReply {
1681 result: true,
1682 error: None,
1683 });
1684 assert_eq!(open_region_instruction_reply, open_region_reply);
1685
1686 let downgrade_region_instruction_reply = r#"{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"exists":true,"error":null,"type":"downgrade_region"}"#;
1688 let downgrade_region_instruction_reply: InstructionReply =
1689 serde_json::from_str(downgrade_region_instruction_reply).unwrap();
1690 let downgrade_region_reply = InstructionReply::DowngradeRegions(
1691 DowngradeRegionsReply::single(DowngradeRegionReply {
1692 region_id: RegionId::new(1024, 1),
1693 last_entry_id: None,
1694 metadata_last_entry_id: None,
1695 exists: true,
1696 error: None,
1697 }),
1698 );
1699 assert_eq!(downgrade_region_instruction_reply, downgrade_region_reply);
1700
1701 let upgrade_region_instruction_reply = r#"{"region_id":4398046511105,"ready":true,"exists":true,"error":null,"type":"upgrade_region"}"#;
1703 let upgrade_region_instruction_reply: InstructionReply =
1704 serde_json::from_str(upgrade_region_instruction_reply).unwrap();
1705 let upgrade_region_reply =
1706 InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1707 region_id: RegionId::new(1024, 1),
1708 ready: true,
1709 exists: true,
1710 error: None,
1711 }));
1712 assert_eq!(upgrade_region_instruction_reply, upgrade_region_reply);
1713 }
1714
1715 #[test]
1716 fn test_enter_staging_partition_rule_compatibility() {
1717 let legacy = r#"{"region_id":4398046511105,"partition_expr":"{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"}"#;
1718 let enter: EnterStagingRegion = serde_json::from_str(legacy).unwrap();
1719 assert_eq!(enter.region_id, RegionId::new(1024, 1));
1720 assert_eq!(
1721 enter.partition_directive,
1722 StagingPartitionDirective::UpdatePartitionExpr(
1723 "{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"
1724 .to_string()
1725 )
1726 );
1727
1728 let serialized = serde_json::to_string(&enter).unwrap();
1729 assert!(serialized.contains("\"partition_directive\":\""));
1730 assert!(!serialized.contains("partition_expr"));
1731
1732 let reject = r#"{"region_id":4398046511105,"partition_expr":{"type":"reject_all_writes"}}"#;
1733 let enter: EnterStagingRegion = serde_json::from_str(reject).unwrap();
1734 assert_eq!(
1735 enter.partition_directive,
1736 StagingPartitionDirective::RejectAllWrites
1737 );
1738 }
1739
1740 #[derive(Debug, Clone, Serialize, Deserialize)]
1741 struct LegacyOpenRegion {
1742 region_ident: RegionIdent,
1743 region_storage_path: String,
1744 region_options: HashMap<String, String>,
1745 }
1746
1747 #[test]
1748 fn test_compatible_serialize_open_region() {
1749 let region_ident = RegionIdent {
1750 datanode_id: 2,
1751 table_id: 1024,
1752 region_number: 1,
1753 engine: "mito2".to_string(),
1754 };
1755 let region_storage_path = "test/foo".to_string();
1756 let region_options = HashMap::from([
1757 ("a".to_string(), "aa".to_string()),
1758 ("b".to_string(), "bb".to_string()),
1759 ]);
1760
1761 let legacy_open_region = LegacyOpenRegion {
1763 region_ident: region_ident.clone(),
1764 region_storage_path: region_storage_path.clone(),
1765 region_options: region_options.clone(),
1766 };
1767 let serialized = serde_json::to_string(&legacy_open_region).unwrap();
1768
1769 let deserialized = serde_json::from_str(&serialized).unwrap();
1771 let expected = OpenRegion {
1772 region_ident,
1773 region_storage_path,
1774 region_options,
1775 region_wal_options: HashMap::new(),
1776 skip_wal_replay: false,
1777 reason: None,
1778 requirements: RegionRequirements::empty(),
1779 };
1780 assert_eq!(expected, deserialized);
1781 }
1782
1783 #[test]
1784 fn test_deserialize_open_region_with_legacy_region_wal_options() {
1785 let open_region = r#"{"region_ident":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"},"region_storage_path":"test/foo","region_options":{},"region_wal_options":{"1":"{\"wal.provider\":\"raft_engine\"}"},"skip_wal_replay":false}"#;
1786
1787 let open_region: OpenRegion = serde_json::from_str(open_region).unwrap();
1788
1789 assert_eq!(
1790 open_region.region_wal_options,
1791 HashMap::from([(1, WalOptions::RaftEngine)])
1792 );
1793 }
1794
1795 #[test]
1796 fn test_serialize_open_region_with_reason_and_requirements() {
1797 let open_region = OpenRegion::new(
1798 RegionIdent {
1799 datanode_id: 2,
1800 table_id: 1024,
1801 region_number: 1,
1802 engine: "mito2".to_string(),
1803 },
1804 "test/foo",
1805 HashMap::new(),
1806 HashMap::new(),
1807 false,
1808 Some(OpenRegionReason::RegionMigration),
1809 RegionRequirements::object_storage(),
1810 );
1811
1812 let serialized = serde_json::to_string(&open_region).unwrap();
1813 assert!(serialized.contains(r#""reason":"RegionMigration""#));
1814 assert!(serialized.contains(r#""object_storage":true"#));
1815
1816 let deserialized: OpenRegion = serde_json::from_str(&serialized).unwrap();
1817 assert_eq!(Some(OpenRegionReason::RegionMigration), deserialized.reason);
1818 assert_eq!(
1819 RegionRequirements::object_storage(),
1820 deserialized.requirements
1821 );
1822 }
1823
1824 #[test]
1825 fn test_flush_regions_creation() {
1826 let region_id = RegionId::new(1024, 1);
1827
1828 let single_sync = FlushRegions::sync_single(region_id);
1830 assert_eq!(single_sync.region_ids, vec![region_id]);
1831 assert_eq!(single_sync.strategy, FlushStrategy::Sync);
1832 assert!(!single_sync.is_hint());
1833 assert!(single_sync.is_sync());
1834 assert_eq!(single_sync.error_strategy, FlushErrorStrategy::FailFast);
1835 assert_eq!(single_sync.reason, None);
1836 assert!(single_sync.is_single_region());
1837 assert_eq!(single_sync.single_region_id(), Some(region_id));
1838
1839 let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1841 let batch_async = FlushRegions::async_batch(region_ids.clone());
1842 assert_eq!(batch_async.region_ids, region_ids);
1843 assert_eq!(batch_async.strategy, FlushStrategy::Async);
1844 assert!(batch_async.is_hint());
1845 assert!(!batch_async.is_sync());
1846 assert_eq!(batch_async.error_strategy, FlushErrorStrategy::TryAll);
1847 assert_eq!(batch_async.reason, None);
1848 assert!(!batch_async.is_single_region());
1849 assert_eq!(batch_async.single_region_id(), None);
1850
1851 let batch_sync = FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::FailFast);
1853 assert_eq!(batch_sync.region_ids, region_ids);
1854 assert_eq!(batch_sync.strategy, FlushStrategy::Sync);
1855 assert!(!batch_sync.is_hint());
1856 assert!(batch_sync.is_sync());
1857 assert_eq!(batch_sync.error_strategy, FlushErrorStrategy::FailFast);
1858 assert_eq!(batch_sync.reason, None);
1859
1860 let with_reason = batch_sync.with_reason(RegionFlushReason::RemoteWalPrune);
1861 assert_eq!(with_reason.reason, Some(RegionFlushReason::RemoteWalPrune));
1862 }
1863
1864 #[test]
1865 fn test_flush_regions_conversion() {
1866 let region_id = RegionId::new(1024, 1);
1867
1868 let from_region_id: FlushRegions = region_id.into();
1869 assert_eq!(from_region_id.region_ids, vec![region_id]);
1870 assert_eq!(from_region_id.strategy, FlushStrategy::Sync);
1871 assert!(!from_region_id.is_hint());
1872 assert!(from_region_id.is_sync());
1873
1874 let flush_regions = FlushRegions {
1876 region_ids: vec![region_id],
1877 strategy: FlushStrategy::Async,
1878 error_strategy: FlushErrorStrategy::TryAll,
1879 reason: None,
1880 };
1881 assert_eq!(flush_regions.region_ids, vec![region_id]);
1882 assert_eq!(flush_regions.strategy, FlushStrategy::Async);
1883 assert!(flush_regions.is_hint());
1884 assert!(!flush_regions.is_sync());
1885 }
1886
1887 #[test]
1888 fn test_flush_region_reply() {
1889 let region_id = RegionId::new(1024, 1);
1890
1891 let success_reply = FlushRegionReply::success_single(region_id);
1893 assert!(success_reply.overall_success);
1894 assert_eq!(success_reply.results.len(), 1);
1895 assert_eq!(success_reply.results[0].0, region_id);
1896 assert!(success_reply.results[0].1.is_ok());
1897
1898 let error_reply = FlushRegionReply::error_single(
1900 region_id,
1901 InstructionError::legacy_internal_retryable("test error"),
1902 );
1903 assert!(!error_reply.overall_success);
1904 assert_eq!(error_reply.results.len(), 1);
1905 assert_eq!(error_reply.results[0].0, region_id);
1906 assert!(error_reply.results[0].1.is_err());
1907
1908 let region_id2 = RegionId::new(1024, 2);
1910 let results = vec![
1911 (region_id, Ok(())),
1912 (
1913 region_id2,
1914 Err(InstructionError::legacy_internal_retryable("flush failed")),
1915 ),
1916 ];
1917 let batch_reply = FlushRegionReply::from_results(results);
1918 assert!(!batch_reply.overall_success);
1919 assert_eq!(batch_reply.results.len(), 2);
1920
1921 let simple_reply = batch_reply.to_simple_reply();
1923 assert!(!simple_reply.result);
1924 assert!(simple_reply.error.is_some());
1925 assert!(simple_reply.error.unwrap().message.contains("flush failed"));
1926 }
1927
1928 #[test]
1929 fn test_serialize_flush_regions_instruction() {
1930 let region_id = RegionId::new(1024, 1);
1931 let flush_regions = FlushRegions::sync_single(region_id);
1932 let instruction = Instruction::FlushRegions(flush_regions.clone());
1933
1934 let serialized = serde_json::to_string(&instruction).unwrap();
1935 assert!(!serialized.contains("reason"));
1936 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1937
1938 match deserialized {
1939 Instruction::FlushRegions(fr) => {
1940 assert_eq!(fr.region_ids, vec![region_id]);
1941 assert_eq!(fr.strategy, FlushStrategy::Sync);
1942 assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1943 assert_eq!(fr.reason, None);
1944 }
1945 _ => panic!("Expected FlushRegions instruction"),
1946 }
1947
1948 let legacy = r#"{"FlushRegions":{"region_ids":[4398046511105],"strategy":"Sync","error_strategy":"FailFast"}}"#;
1949 let deserialized: Instruction = serde_json::from_str(legacy).unwrap();
1950 match deserialized {
1951 Instruction::FlushRegions(fr) => {
1952 assert_eq!(fr.region_ids, vec![region_id]);
1953 assert_eq!(fr.strategy, FlushStrategy::Sync);
1954 assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1955 assert_eq!(fr.reason, None);
1956 }
1957 _ => panic!("Expected FlushRegions instruction"),
1958 }
1959
1960 let flush_regions = FlushRegions::async_batch(vec![region_id])
1961 .with_reason(RegionFlushReason::RemoteWalPrune);
1962 let instruction = Instruction::FlushRegions(flush_regions);
1963 let serialized = serde_json::to_string(&instruction).unwrap();
1964 assert!(serialized.contains(r#""reason":"RemoteWalPrune""#));
1965 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1966 match deserialized {
1967 Instruction::FlushRegions(fr) => {
1968 assert_eq!(fr.reason, Some(RegionFlushReason::RemoteWalPrune));
1969 }
1970 _ => panic!("Expected FlushRegions instruction"),
1971 }
1972 }
1973
1974 #[test]
1975 fn test_serialize_flush_regions_batch_instruction() {
1976 let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1977 let flush_regions =
1978 FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::TryAll);
1979 let instruction = Instruction::FlushRegions(flush_regions);
1980
1981 let serialized = serde_json::to_string(&instruction).unwrap();
1982 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1983
1984 match deserialized {
1985 Instruction::FlushRegions(fr) => {
1986 assert_eq!(fr.region_ids, region_ids);
1987 assert_eq!(fr.strategy, FlushStrategy::Sync);
1988 assert!(!fr.is_hint());
1989 assert!(fr.is_sync());
1990 assert_eq!(fr.error_strategy, FlushErrorStrategy::TryAll);
1991 assert_eq!(fr.reason, None);
1992 }
1993 _ => panic!("Expected FlushRegions instruction"),
1994 }
1995 }
1996
1997 #[test]
1998 fn test_legacy_gc_file_refs_compatibility() {
1999 #[derive(Debug, Deserialize)]
2000 struct LegacyGetFileRefs {
2001 query_regions: Vec<RegionId>,
2002 related_regions: HashMap<RegionId, HashSet<RegionId>>,
2003 }
2004
2005 #[derive(Debug, Deserialize)]
2006 struct LegacyGetFileRefsReply {
2007 file_refs_manifest: FileRefsManifest,
2008 success: bool,
2009 error: Option<InstructionError>,
2010 }
2011
2012 #[derive(Debug, Deserialize)]
2013 enum LegacyInstruction {
2014 GetFileRefs(LegacyGetFileRefs),
2015 }
2016
2017 let get_file_refs = Instruction::GetFileRefs(GetFileRefs {
2018 query_regions: vec![RegionId::new(7, 3)],
2019 related_regions: HashMap::new(),
2020 });
2021 let get_file_refs_json = serde_json::to_string(&get_file_refs).unwrap();
2022 let legacy_get_file_refs: LegacyInstruction =
2023 serde_json::from_str(&get_file_refs_json).unwrap();
2024 let LegacyInstruction::GetFileRefs(legacy_get_file_refs) = legacy_get_file_refs;
2025 assert_eq!(legacy_get_file_refs.query_regions.len(), 1);
2026 assert!(legacy_get_file_refs.related_regions.is_empty());
2027
2028 let reply = GetFileRefsReply {
2029 file_refs_manifest: FileRefsManifest::default(),
2030 success: true,
2031 error: None,
2032 };
2033 let reply_json = serde_json::to_string(&reply).unwrap();
2034 let legacy_reply: LegacyGetFileRefsReply = serde_json::from_str(&reply_json).unwrap();
2035 let current_reply: GetFileRefsReply = serde_json::from_str(&reply_json).unwrap();
2036 assert!(legacy_reply.success);
2037 assert!(legacy_reply.error.is_none());
2038 assert!(legacy_reply.file_refs_manifest.file_refs.is_empty());
2039 assert_eq!(current_reply, reply);
2040
2041 let packed = Instruction::PackedGcRegions(PackedGcRegions {
2042 regions: vec![],
2043 packed_file_refs_manifest: PackedFileRefsManifest::default(),
2044 full_file_listing: false,
2045 });
2046 let packed_json = serde_json::to_string(&packed).unwrap();
2047 assert!(serde_json::from_str::<LegacyInstruction>(&packed_json).is_err());
2048 }
2049
2050 #[test]
2051 fn test_packed_gc_regions_round_trip_is_distinct() {
2052 let instruction = Instruction::PackedGcRegions(PackedGcRegions {
2053 regions: vec![RegionId::new(7, 3)],
2054 packed_file_refs_manifest: PackedFileRefsManifest::default(),
2055 full_file_listing: true,
2056 });
2057 let serialized = serde_json::to_string(&instruction).unwrap();
2058 assert!(serialized.contains("PackedGcRegions"));
2059 assert_eq!(
2060 serde_json::from_str::<Instruction>(&serialized).unwrap(),
2061 instruction
2062 );
2063 }
2064
2065 #[test]
2066 fn test_packed_file_refs_manifest_round_trip_and_validation() {
2067 let region = RegionId::new(7, 3);
2068 let file_id = FileId::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
2069 let mut manifest = FileRefsManifest::default();
2070 manifest.file_refs.insert(
2071 region,
2072 HashSet::from([
2073 FileRef::new(region, file_id, None),
2074 FileRef::new(region, file_id, Some(0)),
2075 FileRef::new(region, file_id, Some(u64::MAX)),
2076 ]),
2077 );
2078 manifest.manifest_version.insert(region, 42);
2079 manifest
2080 .cross_region_refs
2081 .insert(region, HashSet::from([RegionId::new(7, 4)]));
2082 let packed = PackedFileRefsManifest::from_manifest(&manifest);
2083 assert_eq!(packed.clone().into_manifest().unwrap(), manifest);
2084 let mut missing_files = serde_json::to_value(&packed).unwrap();
2085 missing_files
2086 .get_mut("file_refs")
2087 .and_then(serde_json::Value::as_object_mut)
2088 .and_then(|file_refs| file_refs.values_mut().next())
2089 .and_then(serde_json::Value::as_object_mut)
2090 .unwrap()
2091 .remove("files");
2092 assert!(serde_json::from_value::<PackedFileRefsManifest>(missing_files).is_err());
2093
2094 let mut missing_indexed = serde_json::to_value(&packed).unwrap();
2095 missing_indexed
2096 .get_mut("file_refs")
2097 .and_then(serde_json::Value::as_object_mut)
2098 .and_then(|file_refs| file_refs.values_mut().next())
2099 .and_then(serde_json::Value::as_object_mut)
2100 .unwrap()
2101 .remove("indexed");
2102 assert!(serde_json::from_value::<PackedFileRefsManifest>(missing_indexed).is_err());
2103
2104 let mut malformed = packed.clone();
2105 malformed.file_refs.get_mut(®ion).unwrap().indexed = "!".to_string();
2106 let err = malformed.into_manifest().unwrap_err();
2107 assert!(matches!(
2108 &err,
2109 crate::error::Error::DecodePackedFileRefs { .. }
2110 ));
2111 assert!(
2112 err.source()
2113 .is_some_and(|source| source.is::<base64::DecodeError>())
2114 );
2115 assert_eq!(err.status_code(), StatusCode::Unexpected);
2116 assert_eq!(err.retry_hint(), RetryHint::NonRetryable);
2117
2118 let mut malformed = packed;
2119 malformed.file_refs.get_mut(®ion).unwrap().indexed =
2120 base64::engine::general_purpose::STANDARD.encode([0; 1]);
2121 let err = malformed.into_manifest().unwrap_err();
2122 assert!(matches!(
2123 &err,
2124 crate::error::Error::InvalidPackedFileRefs { .. }
2125 ));
2126 assert!(err.source().is_none());
2127 assert_eq!(err.status_code(), StatusCode::Unexpected);
2128 assert_eq!(err.retry_hint(), RetryHint::NonRetryable);
2129 }
2130
2131 #[test]
2132 fn test_packed_file_refs_is_smaller_than_legacy() {
2133 let region = RegionId::new(7, 3);
2134 let mut manifest = FileRefsManifest::default();
2135 manifest.file_refs.insert(
2136 region,
2137 (0..500)
2138 .map(|i| {
2139 let id =
2140 FileId::parse_str(&format!("00000000-0000-0000-0000-{i:012}")).unwrap();
2141 FileRef::new(region, id, None)
2142 })
2143 .collect(),
2144 );
2145 let legacy = serde_json::to_vec(&manifest).unwrap().len();
2146 let packed = serde_json::to_vec(&PackedFileRefsManifest::from_manifest(&manifest))
2147 .unwrap()
2148 .len();
2149 assert!(packed * 2 < legacy, "packed={packed}, legacy={legacy}");
2150 }
2151
2152 #[test]
2153 fn test_serialize_get_file_refs_instruction_reply() {
2154 let mut manifest = FileRefsManifest::default();
2155 let r0 = RegionId::new(1024, 1);
2156 let r1 = RegionId::new(1024, 2);
2157 manifest.file_refs.insert(
2158 r0,
2159 HashSet::from([FileRef::new(r0, FileId::random(), None)]),
2160 );
2161 manifest.file_refs.insert(
2162 r1,
2163 HashSet::from([FileRef::new(r1, FileId::random(), None)]),
2164 );
2165 manifest.manifest_version.insert(r0, 10);
2166 manifest.manifest_version.insert(r1, 20);
2167
2168 let instruction_reply = InstructionReply::GetFileRefs(GetFileRefsReply {
2169 file_refs_manifest: manifest,
2170 success: true,
2171 error: None,
2172 });
2173
2174 let serialized = serde_json::to_string(&instruction_reply).unwrap();
2175 let deserialized = serde_json::from_str(&serialized).unwrap();
2176
2177 assert_eq!(instruction_reply, deserialized);
2178 }
2179}