1use std::collections::{HashMap, HashSet};
16use std::fmt::{Display, Formatter};
17use std::time::Duration;
18
19use common_error::ext::{ErrorExt, RetryHint};
20use common_error::status_code::StatusCode;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22use store_api::region_engine::SyncRegionFromRequest;
23use store_api::region_request::{RegionFlushReason, RegionRequirements};
24use store_api::storage::{FileRefsManifest, GcReport, RegionId, RegionNumber};
25use strum::Display;
26use table::metadata::TableId;
27use table::table_name::TableName;
28
29use crate::flow_name::FlowName;
30use crate::key::schema_name::SchemaName;
31use crate::key::{FlowId, FlowPartitionId};
32use crate::peer::Peer;
33use crate::wal_provider::{RegionWalOptions, region_wal_options_serde};
34use crate::{DatanodeId, FlownodeId};
35
36#[derive(Debug, Serialize, PartialEq, Eq, Clone)]
38pub struct InstructionError {
39 #[serde(
41 serialize_with = "StatusCode::serialize_as_u32",
42 deserialize_with = "StatusCode::deserialize_from_u32"
43 )]
44 pub code: StatusCode,
45 pub message: String,
47 #[serde(
49 serialize_with = "RetryHint::serialize_as_str",
50 deserialize_with = "RetryHint::deserialize_from_str"
51 )]
52 pub retry_hint: RetryHint,
53}
54
55impl<'de> Deserialize<'de> for InstructionError {
56 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 #[derive(Deserialize)]
61 #[serde(untagged)]
62 enum Compat {
63 Structured {
64 #[serde(deserialize_with = "StatusCode::deserialize_from_u32")]
65 code: StatusCode,
66 message: String,
67 #[serde(deserialize_with = "RetryHint::deserialize_from_str")]
68 retry_hint: RetryHint,
69 },
70 Legacy(String),
71 }
72
73 match Compat::deserialize(deserializer)? {
74 Compat::Structured {
75 code,
76 message,
77 retry_hint,
78 } => Ok(Self {
79 code,
80 message,
81 retry_hint,
82 }),
83 Compat::Legacy(message) => Ok(Self::legacy_internal_retryable(message)),
84 }
85 }
86}
87
88impl InstructionError {
89 pub fn new(code: StatusCode, message: impl Into<String>, retry_hint: RetryHint) -> Self {
90 Self {
91 code,
92 message: message.into(),
93 retry_hint,
94 }
95 }
96
97 pub fn legacy_internal_retryable(message: impl Into<String>) -> Self {
98 Self::new(StatusCode::Internal, message, RetryHint::Retryable)
99 }
100
101 pub fn from_error<E: ErrorExt>(error: &E) -> Self {
102 Self {
103 code: error.status_code(),
104 message: error.output_msg(),
105 retry_hint: error.retry_hint(),
106 }
107 }
108}
109
110impl Display for InstructionError {
111 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112 write!(
113 f,
114 "InstructionError(code={}, retry_hint={}, message={})",
115 self.code as u32,
116 self.retry_hint.as_str(),
117 self.message
118 )
119 }
120}
121
122pub type InstructionResult<T> = std::result::Result<T, InstructionError>;
123
124#[derive(Eq, Hash, PartialEq, Clone, Debug, Serialize, Deserialize)]
125pub struct RegionIdent {
126 pub datanode_id: DatanodeId,
127 pub table_id: TableId,
128 pub region_number: RegionNumber,
129 pub engine: String,
130}
131
132impl RegionIdent {
133 pub fn get_region_id(&self) -> RegionId {
134 RegionId::new(self.table_id, self.region_number)
135 }
136}
137
138impl Display for RegionIdent {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 write!(
141 f,
142 "RegionIdent(datanode_id='{}', table_id={}, region_number={}, engine = {})",
143 self.datanode_id, self.table_id, self.region_number, self.engine
144 )
145 }
146}
147
148#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
150pub struct DowngradeRegionReply {
151 #[serde(default)]
154 pub region_id: RegionId,
155 pub last_entry_id: Option<u64>,
157 pub metadata_last_entry_id: Option<u64>,
159 pub exists: bool,
161 pub error: Option<InstructionError>,
163}
164
165impl Display for DowngradeRegionReply {
166 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
167 write!(
168 f,
169 "(last_entry_id={:?}, exists={}, error={:?})",
170 self.last_entry_id, self.exists, self.error
171 )
172 }
173}
174
175#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
176pub struct SimpleReply {
177 pub result: bool,
178 pub error: Option<InstructionError>,
179}
180
181#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
183pub struct FlushRegionReply {
184 pub results: Vec<(RegionId, InstructionResult<()>)>,
188 pub overall_success: bool,
190}
191
192impl FlushRegionReply {
193 pub fn success_single(region_id: RegionId) -> Self {
195 Self {
196 results: vec![(region_id, Ok(()))],
197 overall_success: true,
198 }
199 }
200
201 pub fn error_single(region_id: RegionId, error: InstructionError) -> Self {
203 Self {
204 results: vec![(region_id, Err(error))],
205 overall_success: false,
206 }
207 }
208
209 pub fn from_results(results: Vec<(RegionId, InstructionResult<()>)>) -> Self {
211 let overall_success = results.iter().all(|(_, result)| result.is_ok());
212 Self {
213 results,
214 overall_success,
215 }
216 }
217
218 pub fn to_simple_reply(&self) -> SimpleReply {
220 if self.overall_success {
221 SimpleReply {
222 result: true,
223 error: None,
224 }
225 } else {
226 let errors: Vec<String> = self
227 .results
228 .iter()
229 .filter_map(|(region_id, result)| {
230 result
231 .as_ref()
232 .err()
233 .map(|err| format!("{}: {}", region_id, err))
234 })
235 .collect();
236 SimpleReply {
237 result: false,
238 error: Some(InstructionError::legacy_internal_retryable(
239 errors.join("; "),
240 )),
241 }
242 }
243 }
244}
245
246impl Display for SimpleReply {
247 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248 write!(f, "(result={}, error={:?})", self.result, self.error)
249 }
250}
251
252impl Display for FlushRegionReply {
253 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
254 let results_str = self
255 .results
256 .iter()
257 .map(|(region_id, result)| match result {
258 Ok(()) => format!("{}:OK", region_id),
259 Err(err) => format!("{}:ERR({})", region_id, err),
260 })
261 .collect::<Vec<_>>()
262 .join(", ");
263 write!(
264 f,
265 "(overall_success={}, results=[{}])",
266 self.overall_success, results_str
267 )
268 }
269}
270
271impl Display for OpenRegion {
272 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
273 write!(
274 f,
275 "OpenRegion(region_ident={}, region_storage_path={}, reason={:?})",
276 self.region_ident, self.region_storage_path, self.reason
277 )
278 }
279}
280
281#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
283pub enum OpenRegionReason {
284 RegionMigration,
286 RegionFailover,
288 #[cfg(feature = "enterprise")]
290 RegionFollower,
291}
292
293#[serde_with::serde_as]
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
295pub struct OpenRegion {
296 pub region_ident: RegionIdent,
297 pub region_storage_path: String,
298 pub region_options: HashMap<String, String>,
299 #[serde(default)]
300 #[serde(with = "region_wal_options_serde")]
301 pub region_wal_options: RegionWalOptions,
302 #[serde(default)]
303 pub skip_wal_replay: bool,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub reason: Option<OpenRegionReason>,
306 #[serde(default)]
307 pub requirements: RegionRequirements,
308}
309
310impl OpenRegion {
311 pub fn new(
312 region_ident: RegionIdent,
313 path: &str,
314 region_options: HashMap<String, String>,
315 region_wal_options: RegionWalOptions,
316 skip_wal_replay: bool,
317 reason: Option<OpenRegionReason>,
318 requirements: RegionRequirements,
319 ) -> Self {
320 Self {
321 region_ident,
322 region_storage_path: path.to_string(),
323 region_options,
324 region_wal_options,
325 skip_wal_replay,
326 reason,
327 requirements,
328 }
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
334pub struct DowngradeRegion {
335 pub region_id: RegionId,
337 #[serde(default)]
341 pub flush_timeout: Option<Duration>,
342}
343
344impl Display for DowngradeRegion {
345 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
346 write!(
347 f,
348 "DowngradeRegion(region_id={}, flush_timeout={:?})",
349 self.region_id, self.flush_timeout,
350 )
351 }
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
356pub struct UpgradeRegion {
357 pub region_id: RegionId,
359 pub last_entry_id: Option<u64>,
361 pub metadata_last_entry_id: Option<u64>,
363 #[serde(with = "humantime_serde")]
368 pub replay_timeout: Duration,
369 #[serde(default)]
371 pub location_id: Option<u64>,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub replay_entry_id: Option<u64>,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub metadata_replay_entry_id: Option<u64>,
376}
377
378impl UpgradeRegion {
379 pub fn with_replay_entry_id(mut self, replay_entry_id: Option<u64>) -> Self {
381 self.replay_entry_id = replay_entry_id;
382 self
383 }
384
385 pub fn with_metadata_replay_entry_id(mut self, metadata_replay_entry_id: Option<u64>) -> Self {
387 self.metadata_replay_entry_id = metadata_replay_entry_id;
388 self
389 }
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
393pub enum CacheIdent {
395 FlowId(FlowId),
396 FlowNodeAddressChange(u64),
398 FlowName(FlowName),
399 TableId(TableId),
400 TableName(TableName),
401 SchemaName(SchemaName),
402 CreateFlow(CreateFlow),
403 DropFlow(DropFlow),
404 User(UserCacheIdent),
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409pub struct UserCacheIdent {
410 pub catalog: String,
411 pub username: String,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
415pub struct CreateFlow {
416 pub flow_id: FlowId,
418 pub source_table_ids: Vec<TableId>,
419 pub partition_to_peer_mapping: Vec<(FlowPartitionId, Peer)>,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
424pub struct DropFlow {
425 pub flow_id: FlowId,
426 pub source_table_ids: Vec<TableId>,
427 pub flow_part2node_id: Vec<(FlowPartitionId, FlownodeId)>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
433pub enum FlushStrategy {
434 #[default]
436 Sync,
437 Async,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
443pub enum FlushErrorStrategy {
444 #[default]
446 FailFast,
447 TryAll,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
454pub struct FlushRegions {
455 pub region_ids: Vec<RegionId>,
457 #[serde(default)]
459 pub strategy: FlushStrategy,
460 #[serde(default)]
462 pub error_strategy: FlushErrorStrategy,
463 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub reason: Option<RegionFlushReason>,
466}
467
468impl Display for FlushRegions {
469 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
470 write!(
471 f,
472 "FlushRegions(region_ids={:?}, strategy={:?}, error_strategy={:?}, reason={:?})",
473 self.region_ids, self.strategy, self.error_strategy, self.reason
474 )
475 }
476}
477
478impl FlushRegions {
479 pub fn sync_single(region_id: RegionId) -> Self {
481 Self {
482 region_ids: vec![region_id],
483 strategy: FlushStrategy::Sync,
484 error_strategy: FlushErrorStrategy::FailFast,
485 reason: None,
486 }
487 }
488
489 pub fn async_batch(region_ids: Vec<RegionId>) -> Self {
491 Self {
492 region_ids,
493 strategy: FlushStrategy::Async,
494 error_strategy: FlushErrorStrategy::TryAll,
495 reason: None,
496 }
497 }
498
499 pub fn sync_batch(region_ids: Vec<RegionId>, error_strategy: FlushErrorStrategy) -> Self {
501 Self {
502 region_ids,
503 strategy: FlushStrategy::Sync,
504 error_strategy,
505 reason: None,
506 }
507 }
508
509 pub fn with_reason(mut self, reason: RegionFlushReason) -> Self {
510 self.reason = Some(reason);
511 self
512 }
513
514 pub fn is_single_region(&self) -> bool {
516 self.region_ids.len() == 1
517 }
518
519 pub fn single_region_id(&self) -> Option<RegionId> {
521 if self.is_single_region() {
522 self.region_ids.first().copied()
523 } else {
524 None
525 }
526 }
527
528 pub fn is_hint(&self) -> bool {
530 matches!(self.strategy, FlushStrategy::Async)
531 }
532
533 pub fn is_sync(&self) -> bool {
535 matches!(self.strategy, FlushStrategy::Sync)
536 }
537}
538
539impl From<RegionId> for FlushRegions {
540 fn from(region_id: RegionId) -> Self {
541 Self::sync_single(region_id)
542 }
543}
544
545#[derive(Debug, Deserialize)]
546#[serde(untagged)]
547enum SingleOrMultiple<T> {
548 Single(T),
549 Multiple(Vec<T>),
550}
551
552fn single_or_multiple_from<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
553where
554 D: Deserializer<'de>,
555 T: Deserialize<'de>,
556{
557 let helper = SingleOrMultiple::<T>::deserialize(deserializer)?;
558 Ok(match helper {
559 SingleOrMultiple::Single(x) => vec![x],
560 SingleOrMultiple::Multiple(xs) => xs,
561 })
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
566pub struct GetFileRefs {
567 pub query_regions: Vec<RegionId>,
569 pub related_regions: HashMap<RegionId, HashSet<RegionId>>,
574}
575
576impl Display for GetFileRefs {
577 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
578 write!(f, "GetFileRefs(region_ids={:?})", self.query_regions)
579 }
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
584pub struct GcRegions {
585 pub regions: Vec<RegionId>,
587 pub file_refs_manifest: FileRefsManifest,
589 pub full_file_listing: bool,
591}
592
593impl Display for GcRegions {
594 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
595 write!(
596 f,
597 "GcRegion(regions={:?}, file_refs_count={}, full_file_listing={})",
598 self.regions,
599 self.file_refs_manifest.file_refs.len(),
600 self.full_file_listing
601 )
602 }
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
607pub struct GetFileRefsReply {
608 pub file_refs_manifest: FileRefsManifest,
610 pub success: bool,
612 pub error: Option<InstructionError>,
614}
615
616impl Display for GetFileRefsReply {
617 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
618 write!(
619 f,
620 "GetFileRefsReply(success={}, file_refs_count={}, error={:?})",
621 self.success,
622 self.file_refs_manifest.file_refs.len(),
623 self.error
624 )
625 }
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
630pub struct GcRegionsReply {
631 pub result: InstructionResult<GcReport>,
632}
633
634impl Display for GcRegionsReply {
635 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
636 write!(
637 f,
638 "GcReply(result={})",
639 match &self.result {
640 Ok(report) => format!(
641 "GcReport(deleted_files_count={}, need_retry_regions_count={})",
642 report.deleted_files.len(),
643 report.need_retry_regions.len()
644 ),
645 Err(err) => format!("Err({})", err),
646 }
647 )
648 }
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
652pub struct EnterStagingRegion {
653 pub region_id: RegionId,
654 #[serde(
655 alias = "partition_expr",
656 deserialize_with = "deserialize_enter_staging_partition_directive",
657 serialize_with = "serialize_enter_staging_partition_directive"
658 )]
659 pub partition_directive: StagingPartitionDirective,
660}
661
662impl Display for EnterStagingRegion {
663 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
664 write!(
665 f,
666 "EnterStagingRegion(region_id={}, partition_directive={})",
667 self.region_id, self.partition_directive
668 )
669 }
670}
671
672#[derive(Debug, Clone, PartialEq, Eq)]
673pub enum StagingPartitionDirective {
674 UpdatePartitionExpr(String),
675 RejectAllWrites,
676}
677
678impl StagingPartitionDirective {
679 pub fn as_partition_expr(&self) -> Option<&str> {
681 match self {
682 Self::UpdatePartitionExpr(expr) => Some(expr),
683 Self::RejectAllWrites => None,
684 }
685 }
686}
687
688impl Display for StagingPartitionDirective {
689 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
690 match self {
691 Self::UpdatePartitionExpr(expr) => write!(f, "UpdatePartitionExpr({})", expr),
692 Self::RejectAllWrites => write!(f, "RejectAllWrites"),
693 }
694 }
695}
696
697fn serialize_enter_staging_partition_directive<S>(
698 rule: &StagingPartitionDirective,
699 serializer: S,
700) -> std::result::Result<S::Ok, S::Error>
701where
702 S: Serializer,
703{
704 match rule {
705 StagingPartitionDirective::UpdatePartitionExpr(expr) => serializer.serialize_str(expr),
706 StagingPartitionDirective::RejectAllWrites => {
707 #[derive(Serialize)]
708 struct RejectAllWritesSer<'a> {
709 r#type: &'a str,
710 }
711
712 RejectAllWritesSer {
713 r#type: "reject_all_writes",
714 }
715 .serialize(serializer)
716 }
717 }
718}
719
720fn deserialize_enter_staging_partition_directive<'de, D>(
721 deserializer: D,
722) -> std::result::Result<StagingPartitionDirective, D::Error>
723where
724 D: Deserializer<'de>,
725{
726 #[derive(Deserialize)]
727 #[serde(untagged)]
728 enum Compat {
729 Legacy(String),
730 TypeTagged { r#type: String },
731 }
732
733 match Compat::deserialize(deserializer)? {
734 Compat::Legacy(expr) => Ok(StagingPartitionDirective::UpdatePartitionExpr(expr)),
735 Compat::TypeTagged { r#type } if r#type == "reject_all_writes" => {
736 Ok(StagingPartitionDirective::RejectAllWrites)
737 }
738 Compat::TypeTagged { r#type } => Err(serde::de::Error::custom(format!(
739 "Unknown enter staging partition directive type: {}",
740 r#type
741 ))),
742 }
743}
744
745#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
747pub struct SyncRegion {
748 pub region_id: RegionId,
750 pub request: SyncRegionFromRequest,
752}
753
754impl Display for SyncRegion {
755 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
756 write!(
757 f,
758 "SyncRegion(region_id={}, request={:?})",
759 self.region_id, self.request
760 )
761 }
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
765pub struct RemapManifest {
766 pub region_id: RegionId,
767 pub input_regions: Vec<RegionId>,
769 pub region_mapping: HashMap<RegionId, Vec<RegionId>>,
771 pub new_partition_exprs: HashMap<RegionId, String>,
773}
774
775impl Display for RemapManifest {
776 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
777 write!(
778 f,
779 "RemapManifest(region_id={}, input_regions={:?}, region_mapping={:?}, new_partition_exprs={:?})",
780 self.region_id, self.input_regions, self.region_mapping, self.new_partition_exprs
781 )
782 }
783}
784
785#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
786pub struct ApplyStagingManifest {
787 pub region_id: RegionId,
789 pub partition_expr: String,
791 pub central_region_id: RegionId,
793 pub manifest_path: String,
795}
796
797impl Display for ApplyStagingManifest {
798 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
799 write!(
800 f,
801 "ApplyStagingManifest(region_id={}, partition_expr={}, central_region_id={}, manifest_path={})",
802 self.region_id, self.partition_expr, self.central_region_id, self.manifest_path
803 )
804 }
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq)]
808pub enum Instruction {
809 #[serde(deserialize_with = "single_or_multiple_from", alias = "OpenRegion")]
811 OpenRegions(Vec<OpenRegion>),
812 #[serde(deserialize_with = "single_or_multiple_from", alias = "CloseRegion")]
814 CloseRegions(Vec<RegionIdent>),
815 #[serde(deserialize_with = "single_or_multiple_from", alias = "UpgradeRegion")]
817 UpgradeRegions(Vec<UpgradeRegion>),
818 #[serde(
819 deserialize_with = "single_or_multiple_from",
820 alias = "DowngradeRegion"
821 )]
822 DowngradeRegions(Vec<DowngradeRegion>),
824 InvalidateCaches(Vec<CacheIdent>),
826 FlushRegions(FlushRegions),
828 GetFileRefs(GetFileRefs),
830 GcRegions(GcRegions),
832 Suspend,
834 EnterStagingRegions(Vec<EnterStagingRegion>),
836 SyncRegions(Vec<SyncRegion>),
838 RemapManifest(RemapManifest),
840
841 ApplyStagingManifests(Vec<ApplyStagingManifest>),
843}
844
845impl Instruction {
846 pub fn into_open_regions(self) -> Option<Vec<OpenRegion>> {
848 match self {
849 Self::OpenRegions(open_regions) => Some(open_regions),
850 _ => None,
851 }
852 }
853
854 pub fn into_close_regions(self) -> Option<Vec<RegionIdent>> {
856 match self {
857 Self::CloseRegions(close_regions) => Some(close_regions),
858 _ => None,
859 }
860 }
861
862 pub fn into_flush_regions(self) -> Option<FlushRegions> {
864 match self {
865 Self::FlushRegions(flush_regions) => Some(flush_regions),
866 _ => None,
867 }
868 }
869
870 pub fn into_downgrade_regions(self) -> Option<Vec<DowngradeRegion>> {
872 match self {
873 Self::DowngradeRegions(downgrade_region) => Some(downgrade_region),
874 _ => None,
875 }
876 }
877
878 pub fn into_upgrade_regions(self) -> Option<Vec<UpgradeRegion>> {
880 match self {
881 Self::UpgradeRegions(upgrade_region) => Some(upgrade_region),
882 _ => None,
883 }
884 }
885
886 pub fn into_get_file_refs(self) -> Option<GetFileRefs> {
887 match self {
888 Self::GetFileRefs(get_file_refs) => Some(get_file_refs),
889 _ => None,
890 }
891 }
892
893 pub fn into_gc_regions(self) -> Option<GcRegions> {
894 match self {
895 Self::GcRegions(gc_regions) => Some(gc_regions),
896 _ => None,
897 }
898 }
899
900 pub fn into_enter_staging_regions(self) -> Option<Vec<EnterStagingRegion>> {
901 match self {
902 Self::EnterStagingRegions(enter_staging) => Some(enter_staging),
903 _ => None,
904 }
905 }
906
907 pub fn into_sync_regions(self) -> Option<Vec<SyncRegion>> {
908 match self {
909 Self::SyncRegions(sync_regions) => Some(sync_regions),
910 _ => None,
911 }
912 }
913}
914
915#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
917pub struct UpgradeRegionReply {
918 #[serde(default)]
921 pub region_id: RegionId,
922 pub ready: bool,
924 pub exists: bool,
926 pub error: Option<InstructionError>,
928}
929
930impl Display for UpgradeRegionReply {
931 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
932 write!(
933 f,
934 "(ready={}, exists={}, error={:?})",
935 self.ready, self.exists, self.error
936 )
937 }
938}
939
940#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
941pub struct DowngradeRegionsReply {
942 pub replies: Vec<DowngradeRegionReply>,
943}
944
945impl DowngradeRegionsReply {
946 pub fn new(replies: Vec<DowngradeRegionReply>) -> Self {
947 Self { replies }
948 }
949
950 pub fn single(reply: DowngradeRegionReply) -> Self {
951 Self::new(vec![reply])
952 }
953}
954
955#[derive(Deserialize)]
956#[serde(untagged)]
957enum DowngradeRegionsCompat {
958 Single(DowngradeRegionReply),
959 Multiple(DowngradeRegionsReply),
960}
961
962fn downgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<DowngradeRegionsReply, D::Error>
963where
964 D: Deserializer<'de>,
965{
966 let helper = DowngradeRegionsCompat::deserialize(deserializer)?;
967 Ok(match helper {
968 DowngradeRegionsCompat::Single(x) => DowngradeRegionsReply::new(vec![x]),
969 DowngradeRegionsCompat::Multiple(reply) => reply,
970 })
971}
972
973#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
974pub struct UpgradeRegionsReply {
975 pub replies: Vec<UpgradeRegionReply>,
976}
977
978impl UpgradeRegionsReply {
979 pub fn new(replies: Vec<UpgradeRegionReply>) -> Self {
980 Self { replies }
981 }
982
983 pub fn single(reply: UpgradeRegionReply) -> Self {
984 Self::new(vec![reply])
985 }
986}
987
988#[derive(Deserialize)]
989#[serde(untagged)]
990enum UpgradeRegionsCompat {
991 Single(UpgradeRegionReply),
992 Multiple(UpgradeRegionsReply),
993}
994
995fn upgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<UpgradeRegionsReply, D::Error>
996where
997 D: Deserializer<'de>,
998{
999 let helper = UpgradeRegionsCompat::deserialize(deserializer)?;
1000 Ok(match helper {
1001 UpgradeRegionsCompat::Single(x) => UpgradeRegionsReply::new(vec![x]),
1002 UpgradeRegionsCompat::Multiple(reply) => reply,
1003 })
1004}
1005
1006#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1007pub struct EnterStagingRegionReply {
1008 pub region_id: RegionId,
1009 pub ready: bool,
1011 pub exists: bool,
1013 pub error: Option<InstructionError>,
1015}
1016
1017#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1018pub struct EnterStagingRegionsReply {
1019 pub replies: Vec<EnterStagingRegionReply>,
1020}
1021
1022impl EnterStagingRegionsReply {
1023 pub fn new(replies: Vec<EnterStagingRegionReply>) -> Self {
1024 Self { replies }
1025 }
1026}
1027
1028#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1030pub struct SyncRegionReply {
1031 pub region_id: RegionId,
1033 pub ready: bool,
1035 pub exists: bool,
1037 pub error: Option<InstructionError>,
1039}
1040
1041#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1043pub struct SyncRegionsReply {
1044 pub replies: Vec<SyncRegionReply>,
1045}
1046
1047impl SyncRegionsReply {
1048 pub fn new(replies: Vec<SyncRegionReply>) -> Self {
1049 Self { replies }
1050 }
1051}
1052
1053#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1054pub struct RemapManifestReply {
1055 pub exists: bool,
1057 pub manifest_paths: HashMap<RegionId, String>,
1059 pub error: Option<InstructionError>,
1061}
1062
1063impl Display for RemapManifestReply {
1064 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1065 write!(
1066 f,
1067 "RemapManifestReply(manifest_paths={:?}, error={:?})",
1068 self.manifest_paths, self.error
1069 )
1070 }
1071}
1072
1073#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1074pub struct ApplyStagingManifestsReply {
1075 pub replies: Vec<ApplyStagingManifestReply>,
1076}
1077
1078impl ApplyStagingManifestsReply {
1079 pub fn new(replies: Vec<ApplyStagingManifestReply>) -> Self {
1080 Self { replies }
1081 }
1082}
1083
1084#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1085pub struct ApplyStagingManifestReply {
1086 pub region_id: RegionId,
1087 pub ready: bool,
1089 pub exists: bool,
1091 pub error: Option<InstructionError>,
1093}
1094
1095#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
1096#[serde(tag = "type", rename_all = "snake_case")]
1097pub enum InstructionReply {
1098 #[serde(alias = "open_region")]
1099 OpenRegions(SimpleReply),
1100 #[serde(alias = "close_region")]
1101 CloseRegions(SimpleReply),
1102 #[serde(
1103 deserialize_with = "upgrade_regions_compat_from",
1104 alias = "upgrade_region"
1105 )]
1106 UpgradeRegions(UpgradeRegionsReply),
1107 #[serde(
1108 alias = "downgrade_region",
1109 deserialize_with = "downgrade_regions_compat_from"
1110 )]
1111 DowngradeRegions(DowngradeRegionsReply),
1112 FlushRegions(FlushRegionReply),
1113 GetFileRefs(GetFileRefsReply),
1114 GcRegions(GcRegionsReply),
1115 EnterStagingRegions(EnterStagingRegionsReply),
1116 SyncRegions(SyncRegionsReply),
1117 RemapManifest(RemapManifestReply),
1118 ApplyStagingManifests(ApplyStagingManifestsReply),
1119}
1120
1121impl Display for InstructionReply {
1122 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1123 match self {
1124 Self::OpenRegions(reply) => write!(f, "InstructionReply::OpenRegions({})", reply),
1125 Self::CloseRegions(reply) => write!(f, "InstructionReply::CloseRegions({})", reply),
1126 Self::UpgradeRegions(reply) => {
1127 write!(f, "InstructionReply::UpgradeRegions({:?})", reply.replies)
1128 }
1129 Self::DowngradeRegions(reply) => {
1130 write!(f, "InstructionReply::DowngradeRegions({:?})", reply.replies)
1131 }
1132 Self::FlushRegions(reply) => write!(f, "InstructionReply::FlushRegions({})", reply),
1133 Self::GetFileRefs(reply) => write!(f, "InstructionReply::GetFileRefs({})", reply),
1134 Self::GcRegions(reply) => write!(f, "InstructionReply::GcRegion({})", reply),
1135 Self::EnterStagingRegions(reply) => {
1136 write!(
1137 f,
1138 "InstructionReply::EnterStagingRegions({:?})",
1139 reply.replies
1140 )
1141 }
1142 Self::SyncRegions(reply) => {
1143 write!(f, "InstructionReply::SyncRegions({:?})", reply.replies)
1144 }
1145 Self::RemapManifest(reply) => write!(f, "InstructionReply::RemapManifest({})", reply),
1146 Self::ApplyStagingManifests(reply) => write!(
1147 f,
1148 "InstructionReply::ApplyStagingManifests({:?})",
1149 reply.replies
1150 ),
1151 }
1152 }
1153}
1154
1155#[cfg(any(test, feature = "testing"))]
1156impl InstructionReply {
1157 pub fn expect_close_regions_reply(self) -> SimpleReply {
1158 match self {
1159 Self::CloseRegions(reply) => reply,
1160 _ => panic!("Expected CloseRegions reply"),
1161 }
1162 }
1163
1164 pub fn expect_open_regions_reply(self) -> SimpleReply {
1165 match self {
1166 Self::OpenRegions(reply) => reply,
1167 _ => panic!("Expected OpenRegions reply"),
1168 }
1169 }
1170
1171 pub fn expect_upgrade_regions_reply(self) -> Vec<UpgradeRegionReply> {
1172 match self {
1173 Self::UpgradeRegions(reply) => reply.replies,
1174 _ => panic!("Expected UpgradeRegion reply"),
1175 }
1176 }
1177
1178 pub fn expect_downgrade_regions_reply(self) -> Vec<DowngradeRegionReply> {
1179 match self {
1180 Self::DowngradeRegions(reply) => reply.replies,
1181 _ => panic!("Expected DowngradeRegion reply"),
1182 }
1183 }
1184
1185 pub fn expect_flush_regions_reply(self) -> FlushRegionReply {
1186 match self {
1187 Self::FlushRegions(reply) => reply,
1188 _ => panic!("Expected FlushRegions reply"),
1189 }
1190 }
1191
1192 pub fn expect_enter_staging_regions_reply(self) -> Vec<EnterStagingRegionReply> {
1193 match self {
1194 Self::EnterStagingRegions(reply) => reply.replies,
1195 _ => panic!("Expected EnterStagingRegion reply"),
1196 }
1197 }
1198
1199 pub fn expect_sync_regions_reply(self) -> Vec<SyncRegionReply> {
1200 match self {
1201 Self::SyncRegions(reply) => reply.replies,
1202 _ => panic!("Expected SyncRegion reply"),
1203 }
1204 }
1205
1206 pub fn expect_remap_manifest_reply(self) -> RemapManifestReply {
1207 match self {
1208 Self::RemapManifest(reply) => reply,
1209 _ => panic!("Expected RemapManifest reply"),
1210 }
1211 }
1212
1213 pub fn expect_apply_staging_manifests_reply(self) -> Vec<ApplyStagingManifestReply> {
1214 match self {
1215 Self::ApplyStagingManifests(reply) => reply.replies,
1216 _ => panic!("Expected ApplyStagingManifest reply"),
1217 }
1218 }
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223 use std::collections::HashSet;
1224
1225 use common_error::mock::MockError;
1226 use common_wal::options::WalOptions;
1227 use store_api::storage::{FileId, FileRef};
1228
1229 use super::*;
1230
1231 #[test]
1232 fn test_instruction_error_serde() {
1233 let error = InstructionError::new(
1234 StatusCode::RegionNotFound,
1235 "region not found",
1236 RetryHint::Retryable,
1237 );
1238
1239 let serialized = serde_json::to_string(&error).unwrap();
1240 assert_eq!(
1241 r#"{"code":4005,"message":"region not found","retry_hint":"retryable"}"#,
1242 serialized
1243 );
1244
1245 let deserialized: InstructionError = serde_json::from_str(&serialized).unwrap();
1246 assert_eq!(error, deserialized);
1247
1248 assert!(
1249 serde_json::from_str::<InstructionError>(
1250 r#"{"code":999999,"message":"unknown","retry_hint":"retryable"}"#
1251 )
1252 .is_err()
1253 );
1254 assert!(
1255 serde_json::from_str::<InstructionError>(
1256 r#"{"code":4005,"message":"unknown","retry_hint":"unknown"}"#
1257 )
1258 .is_err()
1259 );
1260 }
1261
1262 #[test]
1263 fn test_instruction_error_from_error() {
1264 let error = MockError::new(StatusCode::RegionNotFound);
1265
1266 let instruction_error = InstructionError::from_error(&error);
1267
1268 assert_eq!(StatusCode::RegionNotFound, instruction_error.code);
1269 assert_eq!("RegionNotFound", instruction_error.message);
1270 assert_eq!(RetryHint::NonRetryable, instruction_error.retry_hint);
1271 }
1272
1273 #[test]
1274 fn test_instruction_result_serde() {
1275 let success: InstructionResult<bool> = Ok(true);
1276 let serialized = serde_json::to_string(&success).unwrap();
1277 assert_eq!(r#"{"Ok":true}"#, serialized);
1278 let deserialized: InstructionResult<bool> = serde_json::from_str(&serialized).unwrap();
1279 assert_eq!(success, deserialized);
1280
1281 let failure: InstructionResult<bool> = Err(InstructionError::new(
1282 StatusCode::RegionBusy,
1283 "region busy",
1284 RetryHint::Retryable,
1285 ));
1286 let serialized = serde_json::to_string(&failure).unwrap();
1287 assert_eq!(
1288 r#"{"Err":{"code":4009,"message":"region busy","retry_hint":"retryable"}}"#,
1289 serialized
1290 );
1291 let deserialized: InstructionResult<bool> = serde_json::from_str(&serialized).unwrap();
1292 assert_eq!(failure, deserialized);
1293 }
1294
1295 #[test]
1296 fn test_serialize_instruction() {
1297 let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1298 RegionIdent {
1299 datanode_id: 2,
1300 table_id: 1024,
1301 region_number: 1,
1302 engine: "mito2".to_string(),
1303 },
1304 "test/foo",
1305 HashMap::new(),
1306 HashMap::new(),
1307 false,
1308 None,
1309 RegionRequirements::empty(),
1310 )]);
1311
1312 let serialized = serde_json::to_string(&open_region).unwrap();
1313 assert_eq!(
1314 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}}]}"#,
1315 serialized
1316 );
1317
1318 let close_region = Instruction::CloseRegions(vec![RegionIdent {
1319 datanode_id: 2,
1320 table_id: 1024,
1321 region_number: 1,
1322 engine: "mito2".to_string(),
1323 }]);
1324
1325 let serialized = serde_json::to_string(&close_region).unwrap();
1326 assert_eq!(
1327 r#"{"CloseRegions":[{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}]}"#,
1328 serialized
1329 );
1330
1331 let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1332 region_id: RegionId::new(1024, 1),
1333 last_entry_id: None,
1334 metadata_last_entry_id: None,
1335 replay_timeout: Duration::from_millis(1000),
1336 location_id: None,
1337 replay_entry_id: None,
1338 metadata_replay_entry_id: None,
1339 }]);
1340
1341 let serialized = serde_json::to_string(&upgrade_region).unwrap();
1342 assert_eq!(
1343 r#"{"UpgradeRegions":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"replay_timeout":"1s","location_id":null}]}"#,
1344 serialized
1345 );
1346 }
1347
1348 #[test]
1349 fn test_serialize_instruction_reply() {
1350 let downgrade_region_reply = InstructionReply::DowngradeRegions(
1351 DowngradeRegionsReply::single(DowngradeRegionReply {
1352 region_id: RegionId::new(1024, 1),
1353 last_entry_id: None,
1354 metadata_last_entry_id: None,
1355 exists: true,
1356 error: None,
1357 }),
1358 );
1359
1360 let serialized = serde_json::to_string(&downgrade_region_reply).unwrap();
1361 assert_eq!(
1362 r#"{"type":"downgrade_regions","replies":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"exists":true,"error":null}]}"#,
1363 serialized
1364 );
1365
1366 let upgrade_region_reply =
1367 InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1368 region_id: RegionId::new(1024, 1),
1369 ready: true,
1370 exists: true,
1371 error: None,
1372 }));
1373 let serialized = serde_json::to_string(&upgrade_region_reply).unwrap();
1374 assert_eq!(
1375 r#"{"type":"upgrade_regions","replies":[{"region_id":4398046511105,"ready":true,"exists":true,"error":null}]}"#,
1376 serialized
1377 );
1378 }
1379
1380 #[test]
1381 fn test_deserialize_instruction() {
1382 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}}"#;
1384 let open_region_instruction: Instruction =
1385 serde_json::from_str(open_region_instruction).unwrap();
1386 let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1387 RegionIdent {
1388 datanode_id: 2,
1389 table_id: 1024,
1390 region_number: 1,
1391 engine: "mito2".to_string(),
1392 },
1393 "test/foo",
1394 HashMap::new(),
1395 HashMap::new(),
1396 false,
1397 None,
1398 RegionRequirements::empty(),
1399 )]);
1400 assert_eq!(open_region_instruction, open_region);
1401
1402 let close_region_instruction = r#"{"CloseRegion":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}}"#;
1404 let close_region_instruction: Instruction =
1405 serde_json::from_str(close_region_instruction).unwrap();
1406 let close_region = Instruction::CloseRegions(vec![RegionIdent {
1407 datanode_id: 2,
1408 table_id: 1024,
1409 region_number: 1,
1410 engine: "mito2".to_string(),
1411 }]);
1412 assert_eq!(close_region_instruction, close_region);
1413
1414 let downgrade_region_instruction = r#"{"DowngradeRegions":{"region_id":4398046511105,"flush_timeout":{"secs":1,"nanos":0}}}"#;
1416 let downgrade_region_instruction: Instruction =
1417 serde_json::from_str(downgrade_region_instruction).unwrap();
1418 let downgrade_region = Instruction::DowngradeRegions(vec![DowngradeRegion {
1419 region_id: RegionId::new(1024, 1),
1420 flush_timeout: Some(Duration::from_millis(1000)),
1421 }]);
1422 assert_eq!(downgrade_region_instruction, downgrade_region);
1423
1424 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}}"#;
1426 let upgrade_region_instruction: Instruction =
1427 serde_json::from_str(upgrade_region_instruction).unwrap();
1428 let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1429 region_id: RegionId::new(1024, 1),
1430 last_entry_id: None,
1431 metadata_last_entry_id: None,
1432 replay_timeout: Duration::from_millis(1000),
1433 location_id: None,
1434 replay_entry_id: None,
1435 metadata_replay_entry_id: None,
1436 }]);
1437 assert_eq!(upgrade_region_instruction, upgrade_region);
1438 }
1439
1440 #[test]
1441 fn test_deserialize_instruction_reply() {
1442 let close_region_instruction_reply =
1444 r#"{"result":true,"error":null,"type":"close_region"}"#;
1445 let close_region_instruction_reply: InstructionReply =
1446 serde_json::from_str(close_region_instruction_reply).unwrap();
1447 let close_region_reply = InstructionReply::CloseRegions(SimpleReply {
1448 result: true,
1449 error: None,
1450 });
1451 assert_eq!(close_region_instruction_reply, close_region_reply);
1452
1453 let open_region_instruction_reply = r#"{"result":true,"error":null,"type":"open_region"}"#;
1455 let open_region_instruction_reply: InstructionReply =
1456 serde_json::from_str(open_region_instruction_reply).unwrap();
1457 let open_region_reply = InstructionReply::OpenRegions(SimpleReply {
1458 result: true,
1459 error: None,
1460 });
1461 assert_eq!(open_region_instruction_reply, open_region_reply);
1462
1463 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"}"#;
1465 let downgrade_region_instruction_reply: InstructionReply =
1466 serde_json::from_str(downgrade_region_instruction_reply).unwrap();
1467 let downgrade_region_reply = InstructionReply::DowngradeRegions(
1468 DowngradeRegionsReply::single(DowngradeRegionReply {
1469 region_id: RegionId::new(1024, 1),
1470 last_entry_id: None,
1471 metadata_last_entry_id: None,
1472 exists: true,
1473 error: None,
1474 }),
1475 );
1476 assert_eq!(downgrade_region_instruction_reply, downgrade_region_reply);
1477
1478 let upgrade_region_instruction_reply = r#"{"region_id":4398046511105,"ready":true,"exists":true,"error":null,"type":"upgrade_region"}"#;
1480 let upgrade_region_instruction_reply: InstructionReply =
1481 serde_json::from_str(upgrade_region_instruction_reply).unwrap();
1482 let upgrade_region_reply =
1483 InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1484 region_id: RegionId::new(1024, 1),
1485 ready: true,
1486 exists: true,
1487 error: None,
1488 }));
1489 assert_eq!(upgrade_region_instruction_reply, upgrade_region_reply);
1490 }
1491
1492 #[test]
1493 fn test_enter_staging_partition_rule_compatibility() {
1494 let legacy = r#"{"region_id":4398046511105,"partition_expr":"{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"}"#;
1495 let enter: EnterStagingRegion = serde_json::from_str(legacy).unwrap();
1496 assert_eq!(enter.region_id, RegionId::new(1024, 1));
1497 assert_eq!(
1498 enter.partition_directive,
1499 StagingPartitionDirective::UpdatePartitionExpr(
1500 "{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"
1501 .to_string()
1502 )
1503 );
1504
1505 let serialized = serde_json::to_string(&enter).unwrap();
1506 assert!(serialized.contains("\"partition_directive\":\""));
1507 assert!(!serialized.contains("partition_expr"));
1508
1509 let reject = r#"{"region_id":4398046511105,"partition_expr":{"type":"reject_all_writes"}}"#;
1510 let enter: EnterStagingRegion = serde_json::from_str(reject).unwrap();
1511 assert_eq!(
1512 enter.partition_directive,
1513 StagingPartitionDirective::RejectAllWrites
1514 );
1515 }
1516
1517 #[derive(Debug, Clone, Serialize, Deserialize)]
1518 struct LegacyOpenRegion {
1519 region_ident: RegionIdent,
1520 region_storage_path: String,
1521 region_options: HashMap<String, String>,
1522 }
1523
1524 #[test]
1525 fn test_compatible_serialize_open_region() {
1526 let region_ident = RegionIdent {
1527 datanode_id: 2,
1528 table_id: 1024,
1529 region_number: 1,
1530 engine: "mito2".to_string(),
1531 };
1532 let region_storage_path = "test/foo".to_string();
1533 let region_options = HashMap::from([
1534 ("a".to_string(), "aa".to_string()),
1535 ("b".to_string(), "bb".to_string()),
1536 ]);
1537
1538 let legacy_open_region = LegacyOpenRegion {
1540 region_ident: region_ident.clone(),
1541 region_storage_path: region_storage_path.clone(),
1542 region_options: region_options.clone(),
1543 };
1544 let serialized = serde_json::to_string(&legacy_open_region).unwrap();
1545
1546 let deserialized = serde_json::from_str(&serialized).unwrap();
1548 let expected = OpenRegion {
1549 region_ident,
1550 region_storage_path,
1551 region_options,
1552 region_wal_options: HashMap::new(),
1553 skip_wal_replay: false,
1554 reason: None,
1555 requirements: RegionRequirements::empty(),
1556 };
1557 assert_eq!(expected, deserialized);
1558 }
1559
1560 #[test]
1561 fn test_deserialize_open_region_with_legacy_region_wal_options() {
1562 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}"#;
1563
1564 let open_region: OpenRegion = serde_json::from_str(open_region).unwrap();
1565
1566 assert_eq!(
1567 open_region.region_wal_options,
1568 HashMap::from([(1, WalOptions::RaftEngine)])
1569 );
1570 }
1571
1572 #[test]
1573 fn test_serialize_open_region_with_reason_and_requirements() {
1574 let open_region = OpenRegion::new(
1575 RegionIdent {
1576 datanode_id: 2,
1577 table_id: 1024,
1578 region_number: 1,
1579 engine: "mito2".to_string(),
1580 },
1581 "test/foo",
1582 HashMap::new(),
1583 HashMap::new(),
1584 false,
1585 Some(OpenRegionReason::RegionMigration),
1586 RegionRequirements::object_storage(),
1587 );
1588
1589 let serialized = serde_json::to_string(&open_region).unwrap();
1590 assert!(serialized.contains(r#""reason":"RegionMigration""#));
1591 assert!(serialized.contains(r#""object_storage":true"#));
1592
1593 let deserialized: OpenRegion = serde_json::from_str(&serialized).unwrap();
1594 assert_eq!(Some(OpenRegionReason::RegionMigration), deserialized.reason);
1595 assert_eq!(
1596 RegionRequirements::object_storage(),
1597 deserialized.requirements
1598 );
1599 }
1600
1601 #[test]
1602 fn test_flush_regions_creation() {
1603 let region_id = RegionId::new(1024, 1);
1604
1605 let single_sync = FlushRegions::sync_single(region_id);
1607 assert_eq!(single_sync.region_ids, vec![region_id]);
1608 assert_eq!(single_sync.strategy, FlushStrategy::Sync);
1609 assert!(!single_sync.is_hint());
1610 assert!(single_sync.is_sync());
1611 assert_eq!(single_sync.error_strategy, FlushErrorStrategy::FailFast);
1612 assert_eq!(single_sync.reason, None);
1613 assert!(single_sync.is_single_region());
1614 assert_eq!(single_sync.single_region_id(), Some(region_id));
1615
1616 let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1618 let batch_async = FlushRegions::async_batch(region_ids.clone());
1619 assert_eq!(batch_async.region_ids, region_ids);
1620 assert_eq!(batch_async.strategy, FlushStrategy::Async);
1621 assert!(batch_async.is_hint());
1622 assert!(!batch_async.is_sync());
1623 assert_eq!(batch_async.error_strategy, FlushErrorStrategy::TryAll);
1624 assert_eq!(batch_async.reason, None);
1625 assert!(!batch_async.is_single_region());
1626 assert_eq!(batch_async.single_region_id(), None);
1627
1628 let batch_sync = FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::FailFast);
1630 assert_eq!(batch_sync.region_ids, region_ids);
1631 assert_eq!(batch_sync.strategy, FlushStrategy::Sync);
1632 assert!(!batch_sync.is_hint());
1633 assert!(batch_sync.is_sync());
1634 assert_eq!(batch_sync.error_strategy, FlushErrorStrategy::FailFast);
1635 assert_eq!(batch_sync.reason, None);
1636
1637 let with_reason = batch_sync.with_reason(RegionFlushReason::RemoteWalPrune);
1638 assert_eq!(with_reason.reason, Some(RegionFlushReason::RemoteWalPrune));
1639 }
1640
1641 #[test]
1642 fn test_flush_regions_conversion() {
1643 let region_id = RegionId::new(1024, 1);
1644
1645 let from_region_id: FlushRegions = region_id.into();
1646 assert_eq!(from_region_id.region_ids, vec![region_id]);
1647 assert_eq!(from_region_id.strategy, FlushStrategy::Sync);
1648 assert!(!from_region_id.is_hint());
1649 assert!(from_region_id.is_sync());
1650
1651 let flush_regions = FlushRegions {
1653 region_ids: vec![region_id],
1654 strategy: FlushStrategy::Async,
1655 error_strategy: FlushErrorStrategy::TryAll,
1656 reason: None,
1657 };
1658 assert_eq!(flush_regions.region_ids, vec![region_id]);
1659 assert_eq!(flush_regions.strategy, FlushStrategy::Async);
1660 assert!(flush_regions.is_hint());
1661 assert!(!flush_regions.is_sync());
1662 }
1663
1664 #[test]
1665 fn test_flush_region_reply() {
1666 let region_id = RegionId::new(1024, 1);
1667
1668 let success_reply = FlushRegionReply::success_single(region_id);
1670 assert!(success_reply.overall_success);
1671 assert_eq!(success_reply.results.len(), 1);
1672 assert_eq!(success_reply.results[0].0, region_id);
1673 assert!(success_reply.results[0].1.is_ok());
1674
1675 let error_reply = FlushRegionReply::error_single(
1677 region_id,
1678 InstructionError::legacy_internal_retryable("test error"),
1679 );
1680 assert!(!error_reply.overall_success);
1681 assert_eq!(error_reply.results.len(), 1);
1682 assert_eq!(error_reply.results[0].0, region_id);
1683 assert!(error_reply.results[0].1.is_err());
1684
1685 let region_id2 = RegionId::new(1024, 2);
1687 let results = vec![
1688 (region_id, Ok(())),
1689 (
1690 region_id2,
1691 Err(InstructionError::legacy_internal_retryable("flush failed")),
1692 ),
1693 ];
1694 let batch_reply = FlushRegionReply::from_results(results);
1695 assert!(!batch_reply.overall_success);
1696 assert_eq!(batch_reply.results.len(), 2);
1697
1698 let simple_reply = batch_reply.to_simple_reply();
1700 assert!(!simple_reply.result);
1701 assert!(simple_reply.error.is_some());
1702 assert!(simple_reply.error.unwrap().message.contains("flush failed"));
1703 }
1704
1705 #[test]
1706 fn test_serialize_flush_regions_instruction() {
1707 let region_id = RegionId::new(1024, 1);
1708 let flush_regions = FlushRegions::sync_single(region_id);
1709 let instruction = Instruction::FlushRegions(flush_regions.clone());
1710
1711 let serialized = serde_json::to_string(&instruction).unwrap();
1712 assert!(!serialized.contains("reason"));
1713 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1714
1715 match deserialized {
1716 Instruction::FlushRegions(fr) => {
1717 assert_eq!(fr.region_ids, vec![region_id]);
1718 assert_eq!(fr.strategy, FlushStrategy::Sync);
1719 assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1720 assert_eq!(fr.reason, None);
1721 }
1722 _ => panic!("Expected FlushRegions instruction"),
1723 }
1724
1725 let legacy = r#"{"FlushRegions":{"region_ids":[4398046511105],"strategy":"Sync","error_strategy":"FailFast"}}"#;
1726 let deserialized: Instruction = serde_json::from_str(legacy).unwrap();
1727 match deserialized {
1728 Instruction::FlushRegions(fr) => {
1729 assert_eq!(fr.region_ids, vec![region_id]);
1730 assert_eq!(fr.strategy, FlushStrategy::Sync);
1731 assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1732 assert_eq!(fr.reason, None);
1733 }
1734 _ => panic!("Expected FlushRegions instruction"),
1735 }
1736
1737 let flush_regions = FlushRegions::async_batch(vec![region_id])
1738 .with_reason(RegionFlushReason::RemoteWalPrune);
1739 let instruction = Instruction::FlushRegions(flush_regions);
1740 let serialized = serde_json::to_string(&instruction).unwrap();
1741 assert!(serialized.contains(r#""reason":"RemoteWalPrune""#));
1742 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1743 match deserialized {
1744 Instruction::FlushRegions(fr) => {
1745 assert_eq!(fr.reason, Some(RegionFlushReason::RemoteWalPrune));
1746 }
1747 _ => panic!("Expected FlushRegions instruction"),
1748 }
1749 }
1750
1751 #[test]
1752 fn test_serialize_flush_regions_batch_instruction() {
1753 let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1754 let flush_regions =
1755 FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::TryAll);
1756 let instruction = Instruction::FlushRegions(flush_regions);
1757
1758 let serialized = serde_json::to_string(&instruction).unwrap();
1759 let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1760
1761 match deserialized {
1762 Instruction::FlushRegions(fr) => {
1763 assert_eq!(fr.region_ids, region_ids);
1764 assert_eq!(fr.strategy, FlushStrategy::Sync);
1765 assert!(!fr.is_hint());
1766 assert!(fr.is_sync());
1767 assert_eq!(fr.error_strategy, FlushErrorStrategy::TryAll);
1768 assert_eq!(fr.reason, None);
1769 }
1770 _ => panic!("Expected FlushRegions instruction"),
1771 }
1772 }
1773
1774 #[test]
1775 fn test_serialize_get_file_refs_instruction_reply() {
1776 let mut manifest = FileRefsManifest::default();
1777 let r0 = RegionId::new(1024, 1);
1778 let r1 = RegionId::new(1024, 2);
1779 manifest.file_refs.insert(
1780 r0,
1781 HashSet::from([FileRef::new(r0, FileId::random(), None)]),
1782 );
1783 manifest.file_refs.insert(
1784 r1,
1785 HashSet::from([FileRef::new(r1, FileId::random(), None)]),
1786 );
1787 manifest.manifest_version.insert(r0, 10);
1788 manifest.manifest_version.insert(r1, 20);
1789
1790 let instruction_reply = InstructionReply::GetFileRefs(GetFileRefsReply {
1791 file_refs_manifest: manifest,
1792 success: true,
1793 error: None,
1794 });
1795
1796 let serialized = serde_json::to_string(&instruction_reply).unwrap();
1797 let deserialized = serde_json::from_str(&serialized).unwrap();
1798
1799 assert_eq!(instruction_reply, deserialized);
1800 }
1801}