Skip to main content

common_meta/
instruction.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16use std::fmt::{Display, Formatter};
17use std::time::Duration;
18
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20use store_api::region_engine::SyncRegionFromRequest;
21use store_api::region_request::RegionFlushReason;
22use store_api::storage::{FileRefsManifest, GcReport, RegionId, RegionNumber};
23use strum::Display;
24use table::metadata::TableId;
25use table::table_name::TableName;
26
27use crate::flow_name::FlowName;
28use crate::key::schema_name::SchemaName;
29use crate::key::{FlowId, FlowPartitionId};
30use crate::peer::Peer;
31use crate::{DatanodeId, FlownodeId};
32
33#[derive(Eq, Hash, PartialEq, Clone, Debug, Serialize, Deserialize)]
34pub struct RegionIdent {
35    pub datanode_id: DatanodeId,
36    pub table_id: TableId,
37    pub region_number: RegionNumber,
38    pub engine: String,
39}
40
41impl RegionIdent {
42    pub fn get_region_id(&self) -> RegionId {
43        RegionId::new(self.table_id, self.region_number)
44    }
45}
46
47impl Display for RegionIdent {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        write!(
50            f,
51            "RegionIdent(datanode_id='{}', table_id={}, region_number={}, engine = {})",
52            self.datanode_id, self.table_id, self.region_number, self.engine
53        )
54    }
55}
56
57/// The result of downgrade leader region.
58#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
59pub struct DowngradeRegionReply {
60    /// The [RegionId].
61    /// For compatibility, it is defaulted to [RegionId::new(0, 0)].
62    #[serde(default)]
63    pub region_id: RegionId,
64    /// Returns the `last_entry_id` if available.
65    pub last_entry_id: Option<u64>,
66    /// Returns the `metadata_last_entry_id` if available (Only available for metric engine).
67    pub metadata_last_entry_id: Option<u64>,
68    /// Indicates whether the region exists.
69    pub exists: bool,
70    /// Return error if any during the operation.
71    pub error: Option<String>,
72}
73
74impl Display for DowngradeRegionReply {
75    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
76        write!(
77            f,
78            "(last_entry_id={:?}, exists={}, error={:?})",
79            self.last_entry_id, self.exists, self.error
80        )
81    }
82}
83
84#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
85pub struct SimpleReply {
86    pub result: bool,
87    pub error: Option<String>,
88}
89
90/// Reply for flush region operations with support for batch results.
91#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
92pub struct FlushRegionReply {
93    /// Results for each region that was attempted to be flushed.
94    /// For single region flushes, this will contain one result.
95    /// For batch flushes, this contains results for all attempted regions.
96    pub results: Vec<(RegionId, Result<(), String>)>,
97    /// Overall success: true if all regions were flushed successfully.
98    pub overall_success: bool,
99}
100
101impl FlushRegionReply {
102    /// Create a successful single region reply.
103    pub fn success_single(region_id: RegionId) -> Self {
104        Self {
105            results: vec![(region_id, Ok(()))],
106            overall_success: true,
107        }
108    }
109
110    /// Create a failed single region reply.
111    pub fn error_single(region_id: RegionId, error: String) -> Self {
112        Self {
113            results: vec![(region_id, Err(error))],
114            overall_success: false,
115        }
116    }
117
118    /// Create a batch reply from individual results.
119    pub fn from_results(results: Vec<(RegionId, Result<(), String>)>) -> Self {
120        let overall_success = results.iter().all(|(_, result)| result.is_ok());
121        Self {
122            results,
123            overall_success,
124        }
125    }
126
127    /// Convert to SimpleReply for backward compatibility.
128    pub fn to_simple_reply(&self) -> SimpleReply {
129        if self.overall_success {
130            SimpleReply {
131                result: true,
132                error: None,
133            }
134        } else {
135            let errors: Vec<String> = self
136                .results
137                .iter()
138                .filter_map(|(region_id, result)| {
139                    result
140                        .as_ref()
141                        .err()
142                        .map(|err| format!("{}: {}", region_id, err))
143                })
144                .collect();
145            SimpleReply {
146                result: false,
147                error: Some(errors.join("; ")),
148            }
149        }
150    }
151}
152
153impl Display for SimpleReply {
154    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155        write!(f, "(result={}, error={:?})", self.result, self.error)
156    }
157}
158
159impl Display for FlushRegionReply {
160    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
161        let results_str = self
162            .results
163            .iter()
164            .map(|(region_id, result)| match result {
165                Ok(()) => format!("{}:OK", region_id),
166                Err(err) => format!("{}:ERR({})", region_id, err),
167            })
168            .collect::<Vec<_>>()
169            .join(", ");
170        write!(
171            f,
172            "(overall_success={}, results=[{}])",
173            self.overall_success, results_str
174        )
175    }
176}
177
178impl Display for OpenRegion {
179    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
180        write!(
181            f,
182            "OpenRegion(region_ident={}, region_storage_path={})",
183            self.region_ident, self.region_storage_path
184        )
185    }
186}
187
188#[serde_with::serde_as]
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
190pub struct OpenRegion {
191    pub region_ident: RegionIdent,
192    pub region_storage_path: String,
193    pub region_options: HashMap<String, String>,
194    #[serde(default)]
195    #[serde_as(as = "HashMap<serde_with::DisplayFromStr, _>")]
196    pub region_wal_options: HashMap<RegionNumber, String>,
197    #[serde(default)]
198    pub skip_wal_replay: bool,
199}
200
201impl OpenRegion {
202    pub fn new(
203        region_ident: RegionIdent,
204        path: &str,
205        region_options: HashMap<String, String>,
206        region_wal_options: HashMap<RegionNumber, String>,
207        skip_wal_replay: bool,
208    ) -> Self {
209        Self {
210            region_ident,
211            region_storage_path: path.to_string(),
212            region_options,
213            region_wal_options,
214            skip_wal_replay,
215        }
216    }
217}
218
219/// The instruction of downgrading leader region.
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
221pub struct DowngradeRegion {
222    /// The [RegionId].
223    pub region_id: RegionId,
224    /// The timeout of waiting for flush the region.
225    ///
226    /// `None` stands for don't flush before downgrading the region.
227    #[serde(default)]
228    pub flush_timeout: Option<Duration>,
229}
230
231impl Display for DowngradeRegion {
232    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
233        write!(
234            f,
235            "DowngradeRegion(region_id={}, flush_timeout={:?})",
236            self.region_id, self.flush_timeout,
237        )
238    }
239}
240
241/// Upgrades a follower region to leader region.
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
243pub struct UpgradeRegion {
244    /// The [RegionId].
245    pub region_id: RegionId,
246    /// The `last_entry_id` of old leader region.
247    pub last_entry_id: Option<u64>,
248    /// The `last_entry_id` of old leader metadata region (Only used for metric engine).
249    pub metadata_last_entry_id: Option<u64>,
250    /// The timeout of waiting for a wal replay.
251    ///
252    /// `None` stands for no wait,
253    /// it's helpful to verify whether the leader region is ready.
254    #[serde(with = "humantime_serde")]
255    pub replay_timeout: Duration,
256    /// The hint for replaying memtable.
257    #[serde(default)]
258    pub location_id: Option<u64>,
259    #[serde(default, skip_serializing_if = "Option::is_none")]
260    pub replay_entry_id: Option<u64>,
261    #[serde(default, skip_serializing_if = "Option::is_none")]
262    pub metadata_replay_entry_id: Option<u64>,
263}
264
265impl UpgradeRegion {
266    /// Sets the replay entry id.
267    pub fn with_replay_entry_id(mut self, replay_entry_id: Option<u64>) -> Self {
268        self.replay_entry_id = replay_entry_id;
269        self
270    }
271
272    /// Sets the metadata replay entry id.
273    pub fn with_metadata_replay_entry_id(mut self, metadata_replay_entry_id: Option<u64>) -> Self {
274        self.metadata_replay_entry_id = metadata_replay_entry_id;
275        self
276    }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
280/// The identifier of cache.
281pub enum CacheIdent {
282    FlowId(FlowId),
283    /// Indicate change of address of flownode.
284    FlowNodeAddressChange(u64),
285    FlowName(FlowName),
286    TableId(TableId),
287    TableName(TableName),
288    SchemaName(SchemaName),
289    CreateFlow(CreateFlow),
290    DropFlow(DropFlow),
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
294pub struct CreateFlow {
295    /// The unique identifier for the flow.
296    pub flow_id: FlowId,
297    pub source_table_ids: Vec<TableId>,
298    /// Mapping of flow partition to peer information
299    pub partition_to_peer_mapping: Vec<(FlowPartitionId, Peer)>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
303pub struct DropFlow {
304    pub flow_id: FlowId,
305    pub source_table_ids: Vec<TableId>,
306    /// Mapping of flow partition to flownode id
307    pub flow_part2node_id: Vec<(FlowPartitionId, FlownodeId)>,
308}
309
310/// Strategy for executing flush operations.
311#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
312pub enum FlushStrategy {
313    /// Synchronous operation that waits for completion and expects a reply
314    #[default]
315    Sync,
316    /// Asynchronous hint operation (fire-and-forget, no reply expected)
317    Async,
318}
319
320/// Error handling strategy for batch flush operations.
321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
322pub enum FlushErrorStrategy {
323    /// Abort on first error (fail-fast)
324    #[default]
325    FailFast,
326    /// Attempt to flush all regions and collect all errors
327    TryAll,
328}
329
330/// Unified flush instruction supporting both single and batch operations
331/// with configurable execution strategies and error handling.
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
333pub struct FlushRegions {
334    /// List of region IDs to flush. Can contain a single region or multiple regions.
335    pub region_ids: Vec<RegionId>,
336    /// Execution strategy: Sync (expects reply) or Async (fire-and-forget hint).
337    #[serde(default)]
338    pub strategy: FlushStrategy,
339    /// Error handling strategy for batch operations (only applies when multiple regions and sync strategy).
340    #[serde(default)]
341    pub error_strategy: FlushErrorStrategy,
342    /// The source that triggered this flush.
343    #[serde(default, skip_serializing_if = "Option::is_none")]
344    pub reason: Option<RegionFlushReason>,
345}
346
347impl Display for FlushRegions {
348    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
349        write!(
350            f,
351            "FlushRegions(region_ids={:?}, strategy={:?}, error_strategy={:?}, reason={:?})",
352            self.region_ids, self.strategy, self.error_strategy, self.reason
353        )
354    }
355}
356
357impl FlushRegions {
358    /// Create synchronous single-region flush
359    pub fn sync_single(region_id: RegionId) -> Self {
360        Self {
361            region_ids: vec![region_id],
362            strategy: FlushStrategy::Sync,
363            error_strategy: FlushErrorStrategy::FailFast,
364            reason: None,
365        }
366    }
367
368    /// Create asynchronous batch flush (fire-and-forget)
369    pub fn async_batch(region_ids: Vec<RegionId>) -> Self {
370        Self {
371            region_ids,
372            strategy: FlushStrategy::Async,
373            error_strategy: FlushErrorStrategy::TryAll,
374            reason: None,
375        }
376    }
377
378    /// Create synchronous batch flush with error strategy
379    pub fn sync_batch(region_ids: Vec<RegionId>, error_strategy: FlushErrorStrategy) -> Self {
380        Self {
381            region_ids,
382            strategy: FlushStrategy::Sync,
383            error_strategy,
384            reason: None,
385        }
386    }
387
388    pub fn with_reason(mut self, reason: RegionFlushReason) -> Self {
389        self.reason = Some(reason);
390        self
391    }
392
393    /// Check if this is a single region flush.
394    pub fn is_single_region(&self) -> bool {
395        self.region_ids.len() == 1
396    }
397
398    /// Get the single region ID if this is a single region flush.
399    pub fn single_region_id(&self) -> Option<RegionId> {
400        if self.is_single_region() {
401            self.region_ids.first().copied()
402        } else {
403            None
404        }
405    }
406
407    /// Check if this is a hint (asynchronous) operation.
408    pub fn is_hint(&self) -> bool {
409        matches!(self.strategy, FlushStrategy::Async)
410    }
411
412    /// Check if this is a synchronous operation.
413    pub fn is_sync(&self) -> bool {
414        matches!(self.strategy, FlushStrategy::Sync)
415    }
416}
417
418impl From<RegionId> for FlushRegions {
419    fn from(region_id: RegionId) -> Self {
420        Self::sync_single(region_id)
421    }
422}
423
424#[derive(Debug, Deserialize)]
425#[serde(untagged)]
426enum SingleOrMultiple<T> {
427    Single(T),
428    Multiple(Vec<T>),
429}
430
431fn single_or_multiple_from<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
432where
433    D: Deserializer<'de>,
434    T: Deserialize<'de>,
435{
436    let helper = SingleOrMultiple::<T>::deserialize(deserializer)?;
437    Ok(match helper {
438        SingleOrMultiple::Single(x) => vec![x],
439        SingleOrMultiple::Multiple(xs) => xs,
440    })
441}
442
443/// Instruction to get file references for specified regions.
444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
445pub struct GetFileRefs {
446    /// List of region IDs to get file references from active FileHandles (in-memory).
447    pub query_regions: Vec<RegionId>,
448    /// Mapping from the src region IDs (whose file references to look for) to
449    /// the dst region IDs (where to read the manifests).
450    /// Key: The source region IDs (where files originally came from).
451    /// Value: The set of destination region IDs (whose manifests need to be read).
452    pub related_regions: HashMap<RegionId, HashSet<RegionId>>,
453}
454
455impl Display for GetFileRefs {
456    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
457        write!(f, "GetFileRefs(region_ids={:?})", self.query_regions)
458    }
459}
460
461/// Instruction to trigger garbage collection for a region.
462#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
463pub struct GcRegions {
464    /// The region ID to perform GC on, only regions that are currently on the given datanode can be garbage collected, regions not on the datanode will report errors.
465    pub regions: Vec<RegionId>,
466    /// The file references manifest containing temporary file references.
467    pub file_refs_manifest: FileRefsManifest,
468    /// Whether to perform a full file listing to find orphan files.
469    pub full_file_listing: bool,
470}
471
472impl Display for GcRegions {
473    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
474        write!(
475            f,
476            "GcRegion(regions={:?}, file_refs_count={}, full_file_listing={})",
477            self.regions,
478            self.file_refs_manifest.file_refs.len(),
479            self.full_file_listing
480        )
481    }
482}
483
484/// Reply for GetFileRefs instruction.
485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
486pub struct GetFileRefsReply {
487    /// The file references manifest.
488    pub file_refs_manifest: FileRefsManifest,
489    /// Whether the operation was successful.
490    pub success: bool,
491    /// Error message if any.
492    pub error: Option<String>,
493}
494
495impl Display for GetFileRefsReply {
496    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
497        write!(
498            f,
499            "GetFileRefsReply(success={}, file_refs_count={}, error={:?})",
500            self.success,
501            self.file_refs_manifest.file_refs.len(),
502            self.error
503        )
504    }
505}
506
507/// Reply for GC instruction.
508#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
509pub struct GcRegionsReply {
510    pub result: Result<GcReport, String>,
511}
512
513impl Display for GcRegionsReply {
514    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
515        write!(
516            f,
517            "GcReply(result={})",
518            match &self.result {
519                Ok(report) => format!(
520                    "GcReport(deleted_files_count={}, need_retry_regions_count={})",
521                    report.deleted_files.len(),
522                    report.need_retry_regions.len()
523                ),
524                Err(err) => format!("Err({})", err),
525            }
526        )
527    }
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
531pub struct EnterStagingRegion {
532    pub region_id: RegionId,
533    #[serde(
534        alias = "partition_expr",
535        deserialize_with = "deserialize_enter_staging_partition_directive",
536        serialize_with = "serialize_enter_staging_partition_directive"
537    )]
538    pub partition_directive: StagingPartitionDirective,
539}
540
541impl Display for EnterStagingRegion {
542    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
543        write!(
544            f,
545            "EnterStagingRegion(region_id={}, partition_directive={})",
546            self.region_id, self.partition_directive
547        )
548    }
549}
550
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub enum StagingPartitionDirective {
553    UpdatePartitionExpr(String),
554    RejectAllWrites,
555}
556
557impl StagingPartitionDirective {
558    /// Returns the partition expression carried by this directive, if any.
559    pub fn as_partition_expr(&self) -> Option<&str> {
560        match self {
561            Self::UpdatePartitionExpr(expr) => Some(expr),
562            Self::RejectAllWrites => None,
563        }
564    }
565}
566
567impl Display for StagingPartitionDirective {
568    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
569        match self {
570            Self::UpdatePartitionExpr(expr) => write!(f, "UpdatePartitionExpr({})", expr),
571            Self::RejectAllWrites => write!(f, "RejectAllWrites"),
572        }
573    }
574}
575
576fn serialize_enter_staging_partition_directive<S>(
577    rule: &StagingPartitionDirective,
578    serializer: S,
579) -> std::result::Result<S::Ok, S::Error>
580where
581    S: Serializer,
582{
583    match rule {
584        StagingPartitionDirective::UpdatePartitionExpr(expr) => serializer.serialize_str(expr),
585        StagingPartitionDirective::RejectAllWrites => {
586            #[derive(Serialize)]
587            struct RejectAllWritesSer<'a> {
588                r#type: &'a str,
589            }
590
591            RejectAllWritesSer {
592                r#type: "reject_all_writes",
593            }
594            .serialize(serializer)
595        }
596    }
597}
598
599fn deserialize_enter_staging_partition_directive<'de, D>(
600    deserializer: D,
601) -> std::result::Result<StagingPartitionDirective, D::Error>
602where
603    D: Deserializer<'de>,
604{
605    #[derive(Deserialize)]
606    #[serde(untagged)]
607    enum Compat {
608        Legacy(String),
609        TypeTagged { r#type: String },
610    }
611
612    match Compat::deserialize(deserializer)? {
613        Compat::Legacy(expr) => Ok(StagingPartitionDirective::UpdatePartitionExpr(expr)),
614        Compat::TypeTagged { r#type } if r#type == "reject_all_writes" => {
615            Ok(StagingPartitionDirective::RejectAllWrites)
616        }
617        Compat::TypeTagged { r#type } => Err(serde::de::Error::custom(format!(
618            "Unknown enter staging partition directive type: {}",
619            r#type
620        ))),
621    }
622}
623
624/// Instruction payload for syncing a region from a manifest or another region.
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
626pub struct SyncRegion {
627    /// Region id to sync.
628    pub region_id: RegionId,
629    /// Request to sync the region.
630    pub request: SyncRegionFromRequest,
631}
632
633impl Display for SyncRegion {
634    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
635        write!(
636            f,
637            "SyncRegion(region_id={}, request={:?})",
638            self.region_id, self.request
639        )
640    }
641}
642
643#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
644pub struct RemapManifest {
645    pub region_id: RegionId,
646    /// Regions to remap manifests from.
647    pub input_regions: Vec<RegionId>,
648    /// For each old region, which new regions should receive its files
649    pub region_mapping: HashMap<RegionId, Vec<RegionId>>,
650    /// New partition expressions for the new regions.
651    pub new_partition_exprs: HashMap<RegionId, String>,
652}
653
654impl Display for RemapManifest {
655    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
656        write!(
657            f,
658            "RemapManifest(region_id={}, input_regions={:?}, region_mapping={:?}, new_partition_exprs={:?})",
659            self.region_id, self.input_regions, self.region_mapping, self.new_partition_exprs
660        )
661    }
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
665pub struct ApplyStagingManifest {
666    /// The region ID to apply the staging manifest to.
667    pub region_id: RegionId,
668    /// The partition expression of the staging region.
669    pub partition_expr: String,
670    /// The region that stores the staging manifests in its staging blob storage.
671    pub central_region_id: RegionId,
672    /// The relative path to the staging manifest within the central region's staging blob storage.
673    pub manifest_path: String,
674}
675
676impl Display for ApplyStagingManifest {
677    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
678        write!(
679            f,
680            "ApplyStagingManifest(region_id={}, partition_expr={}, central_region_id={}, manifest_path={})",
681            self.region_id, self.partition_expr, self.central_region_id, self.manifest_path
682        )
683    }
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, Display, PartialEq)]
687pub enum Instruction {
688    /// Opens regions.
689    #[serde(deserialize_with = "single_or_multiple_from", alias = "OpenRegion")]
690    OpenRegions(Vec<OpenRegion>),
691    /// Closes regions.
692    #[serde(deserialize_with = "single_or_multiple_from", alias = "CloseRegion")]
693    CloseRegions(Vec<RegionIdent>),
694    /// Upgrades regions.
695    #[serde(deserialize_with = "single_or_multiple_from", alias = "UpgradeRegion")]
696    UpgradeRegions(Vec<UpgradeRegion>),
697    #[serde(
698        deserialize_with = "single_or_multiple_from",
699        alias = "DowngradeRegion"
700    )]
701    /// Downgrades regions.
702    DowngradeRegions(Vec<DowngradeRegion>),
703    /// Invalidates batch cache.
704    InvalidateCaches(Vec<CacheIdent>),
705    /// Flushes regions.
706    FlushRegions(FlushRegions),
707    /// Gets file references for regions.
708    GetFileRefs(GetFileRefs),
709    /// Triggers garbage collection for a region.
710    GcRegions(GcRegions),
711    /// Temporary suspend serving reads or writes
712    Suspend,
713    /// Makes regions enter staging state.
714    EnterStagingRegions(Vec<EnterStagingRegion>),
715    /// Syncs regions.
716    SyncRegions(Vec<SyncRegion>),
717    /// Remaps manifests for a region.
718    RemapManifest(RemapManifest),
719
720    /// Applies staging manifests for a region.
721    ApplyStagingManifests(Vec<ApplyStagingManifest>),
722}
723
724impl Instruction {
725    /// Converts the instruction into a vector of [OpenRegion].
726    pub fn into_open_regions(self) -> Option<Vec<OpenRegion>> {
727        match self {
728            Self::OpenRegions(open_regions) => Some(open_regions),
729            _ => None,
730        }
731    }
732
733    /// Converts the instruction into a vector of [RegionIdent].
734    pub fn into_close_regions(self) -> Option<Vec<RegionIdent>> {
735        match self {
736            Self::CloseRegions(close_regions) => Some(close_regions),
737            _ => None,
738        }
739    }
740
741    /// Converts the instruction into a [FlushRegions].
742    pub fn into_flush_regions(self) -> Option<FlushRegions> {
743        match self {
744            Self::FlushRegions(flush_regions) => Some(flush_regions),
745            _ => None,
746        }
747    }
748
749    /// Converts the instruction into a [DowngradeRegion].
750    pub fn into_downgrade_regions(self) -> Option<Vec<DowngradeRegion>> {
751        match self {
752            Self::DowngradeRegions(downgrade_region) => Some(downgrade_region),
753            _ => None,
754        }
755    }
756
757    /// Converts the instruction into a [UpgradeRegion].
758    pub fn into_upgrade_regions(self) -> Option<Vec<UpgradeRegion>> {
759        match self {
760            Self::UpgradeRegions(upgrade_region) => Some(upgrade_region),
761            _ => None,
762        }
763    }
764
765    pub fn into_get_file_refs(self) -> Option<GetFileRefs> {
766        match self {
767            Self::GetFileRefs(get_file_refs) => Some(get_file_refs),
768            _ => None,
769        }
770    }
771
772    pub fn into_gc_regions(self) -> Option<GcRegions> {
773        match self {
774            Self::GcRegions(gc_regions) => Some(gc_regions),
775            _ => None,
776        }
777    }
778
779    pub fn into_enter_staging_regions(self) -> Option<Vec<EnterStagingRegion>> {
780        match self {
781            Self::EnterStagingRegions(enter_staging) => Some(enter_staging),
782            _ => None,
783        }
784    }
785
786    pub fn into_sync_regions(self) -> Option<Vec<SyncRegion>> {
787        match self {
788            Self::SyncRegions(sync_regions) => Some(sync_regions),
789            _ => None,
790        }
791    }
792}
793
794/// The reply of [UpgradeRegion].
795#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
796pub struct UpgradeRegionReply {
797    /// The [RegionId].
798    /// For compatibility, it is defaulted to [RegionId::new(0, 0)].
799    #[serde(default)]
800    pub region_id: RegionId,
801    /// Returns true if `last_entry_id` has been replayed to the latest.
802    pub ready: bool,
803    /// Indicates whether the region exists.
804    pub exists: bool,
805    /// Returns error if any.
806    pub error: Option<String>,
807}
808
809impl Display for UpgradeRegionReply {
810    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
811        write!(
812            f,
813            "(ready={}, exists={}, error={:?})",
814            self.ready, self.exists, self.error
815        )
816    }
817}
818
819#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
820pub struct DowngradeRegionsReply {
821    pub replies: Vec<DowngradeRegionReply>,
822}
823
824impl DowngradeRegionsReply {
825    pub fn new(replies: Vec<DowngradeRegionReply>) -> Self {
826        Self { replies }
827    }
828
829    pub fn single(reply: DowngradeRegionReply) -> Self {
830        Self::new(vec![reply])
831    }
832}
833
834#[derive(Deserialize)]
835#[serde(untagged)]
836enum DowngradeRegionsCompat {
837    Single(DowngradeRegionReply),
838    Multiple(DowngradeRegionsReply),
839}
840
841fn downgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<DowngradeRegionsReply, D::Error>
842where
843    D: Deserializer<'de>,
844{
845    let helper = DowngradeRegionsCompat::deserialize(deserializer)?;
846    Ok(match helper {
847        DowngradeRegionsCompat::Single(x) => DowngradeRegionsReply::new(vec![x]),
848        DowngradeRegionsCompat::Multiple(reply) => reply,
849    })
850}
851
852#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
853pub struct UpgradeRegionsReply {
854    pub replies: Vec<UpgradeRegionReply>,
855}
856
857impl UpgradeRegionsReply {
858    pub fn new(replies: Vec<UpgradeRegionReply>) -> Self {
859        Self { replies }
860    }
861
862    pub fn single(reply: UpgradeRegionReply) -> Self {
863        Self::new(vec![reply])
864    }
865}
866
867#[derive(Deserialize)]
868#[serde(untagged)]
869enum UpgradeRegionsCompat {
870    Single(UpgradeRegionReply),
871    Multiple(UpgradeRegionsReply),
872}
873
874fn upgrade_regions_compat_from<'de, D>(deserializer: D) -> Result<UpgradeRegionsReply, D::Error>
875where
876    D: Deserializer<'de>,
877{
878    let helper = UpgradeRegionsCompat::deserialize(deserializer)?;
879    Ok(match helper {
880        UpgradeRegionsCompat::Single(x) => UpgradeRegionsReply::new(vec![x]),
881        UpgradeRegionsCompat::Multiple(reply) => reply,
882    })
883}
884
885#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
886pub struct EnterStagingRegionReply {
887    pub region_id: RegionId,
888    /// Returns true if the region has entered staging with the target directive.
889    pub ready: bool,
890    /// Indicates whether the region exists.
891    pub exists: bool,
892    /// Return error if any during the operation.
893    pub error: Option<String>,
894}
895
896#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
897pub struct EnterStagingRegionsReply {
898    pub replies: Vec<EnterStagingRegionReply>,
899}
900
901impl EnterStagingRegionsReply {
902    pub fn new(replies: Vec<EnterStagingRegionReply>) -> Self {
903        Self { replies }
904    }
905}
906
907/// Reply for a single region sync request.
908#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
909pub struct SyncRegionReply {
910    /// Region id of the synced region.
911    pub region_id: RegionId,
912    /// Returns true if the region is successfully synced and ready.
913    pub ready: bool,
914    /// Indicates whether the region exists.
915    pub exists: bool,
916    /// Return error message if any during the operation.
917    pub error: Option<String>,
918}
919
920/// Reply for a batch of region sync requests.
921#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
922pub struct SyncRegionsReply {
923    pub replies: Vec<SyncRegionReply>,
924}
925
926impl SyncRegionsReply {
927    pub fn new(replies: Vec<SyncRegionReply>) -> Self {
928        Self { replies }
929    }
930}
931
932#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
933pub struct RemapManifestReply {
934    /// Returns false if the region does not exist.
935    pub exists: bool,
936    /// A map from region IDs to their corresponding remapped manifest paths.
937    pub manifest_paths: HashMap<RegionId, String>,
938    /// Return error if any during the operation.
939    pub error: Option<String>,
940}
941
942impl Display for RemapManifestReply {
943    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
944        write!(
945            f,
946            "RemapManifestReply(manifest_paths={:?}, error={:?})",
947            self.manifest_paths, self.error
948        )
949    }
950}
951
952#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
953pub struct ApplyStagingManifestsReply {
954    pub replies: Vec<ApplyStagingManifestReply>,
955}
956
957impl ApplyStagingManifestsReply {
958    pub fn new(replies: Vec<ApplyStagingManifestReply>) -> Self {
959        Self { replies }
960    }
961}
962
963#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
964pub struct ApplyStagingManifestReply {
965    pub region_id: RegionId,
966    /// Returns true if the region is ready to serve reads and writes.
967    pub ready: bool,
968    /// Indicates whether the region exists.
969    pub exists: bool,
970    /// Return error if any during the operation.
971    pub error: Option<String>,
972}
973
974#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
975#[serde(tag = "type", rename_all = "snake_case")]
976pub enum InstructionReply {
977    #[serde(alias = "open_region")]
978    OpenRegions(SimpleReply),
979    #[serde(alias = "close_region")]
980    CloseRegions(SimpleReply),
981    #[serde(
982        deserialize_with = "upgrade_regions_compat_from",
983        alias = "upgrade_region"
984    )]
985    UpgradeRegions(UpgradeRegionsReply),
986    #[serde(
987        alias = "downgrade_region",
988        deserialize_with = "downgrade_regions_compat_from"
989    )]
990    DowngradeRegions(DowngradeRegionsReply),
991    FlushRegions(FlushRegionReply),
992    GetFileRefs(GetFileRefsReply),
993    GcRegions(GcRegionsReply),
994    EnterStagingRegions(EnterStagingRegionsReply),
995    SyncRegions(SyncRegionsReply),
996    RemapManifest(RemapManifestReply),
997    ApplyStagingManifests(ApplyStagingManifestsReply),
998}
999
1000impl Display for InstructionReply {
1001    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1002        match self {
1003            Self::OpenRegions(reply) => write!(f, "InstructionReply::OpenRegions({})", reply),
1004            Self::CloseRegions(reply) => write!(f, "InstructionReply::CloseRegions({})", reply),
1005            Self::UpgradeRegions(reply) => {
1006                write!(f, "InstructionReply::UpgradeRegions({:?})", reply.replies)
1007            }
1008            Self::DowngradeRegions(reply) => {
1009                write!(f, "InstructionReply::DowngradeRegions({:?})", reply.replies)
1010            }
1011            Self::FlushRegions(reply) => write!(f, "InstructionReply::FlushRegions({})", reply),
1012            Self::GetFileRefs(reply) => write!(f, "InstructionReply::GetFileRefs({})", reply),
1013            Self::GcRegions(reply) => write!(f, "InstructionReply::GcRegion({})", reply),
1014            Self::EnterStagingRegions(reply) => {
1015                write!(
1016                    f,
1017                    "InstructionReply::EnterStagingRegions({:?})",
1018                    reply.replies
1019                )
1020            }
1021            Self::SyncRegions(reply) => {
1022                write!(f, "InstructionReply::SyncRegions({:?})", reply.replies)
1023            }
1024            Self::RemapManifest(reply) => write!(f, "InstructionReply::RemapManifest({})", reply),
1025            Self::ApplyStagingManifests(reply) => write!(
1026                f,
1027                "InstructionReply::ApplyStagingManifests({:?})",
1028                reply.replies
1029            ),
1030        }
1031    }
1032}
1033
1034#[cfg(any(test, feature = "testing"))]
1035impl InstructionReply {
1036    pub fn expect_close_regions_reply(self) -> SimpleReply {
1037        match self {
1038            Self::CloseRegions(reply) => reply,
1039            _ => panic!("Expected CloseRegions reply"),
1040        }
1041    }
1042
1043    pub fn expect_open_regions_reply(self) -> SimpleReply {
1044        match self {
1045            Self::OpenRegions(reply) => reply,
1046            _ => panic!("Expected OpenRegions reply"),
1047        }
1048    }
1049
1050    pub fn expect_upgrade_regions_reply(self) -> Vec<UpgradeRegionReply> {
1051        match self {
1052            Self::UpgradeRegions(reply) => reply.replies,
1053            _ => panic!("Expected UpgradeRegion reply"),
1054        }
1055    }
1056
1057    pub fn expect_downgrade_regions_reply(self) -> Vec<DowngradeRegionReply> {
1058        match self {
1059            Self::DowngradeRegions(reply) => reply.replies,
1060            _ => panic!("Expected DowngradeRegion reply"),
1061        }
1062    }
1063
1064    pub fn expect_flush_regions_reply(self) -> FlushRegionReply {
1065        match self {
1066            Self::FlushRegions(reply) => reply,
1067            _ => panic!("Expected FlushRegions reply"),
1068        }
1069    }
1070
1071    pub fn expect_enter_staging_regions_reply(self) -> Vec<EnterStagingRegionReply> {
1072        match self {
1073            Self::EnterStagingRegions(reply) => reply.replies,
1074            _ => panic!("Expected EnterStagingRegion reply"),
1075        }
1076    }
1077
1078    pub fn expect_sync_regions_reply(self) -> Vec<SyncRegionReply> {
1079        match self {
1080            Self::SyncRegions(reply) => reply.replies,
1081            _ => panic!("Expected SyncRegion reply"),
1082        }
1083    }
1084
1085    pub fn expect_remap_manifest_reply(self) -> RemapManifestReply {
1086        match self {
1087            Self::RemapManifest(reply) => reply,
1088            _ => panic!("Expected RemapManifest reply"),
1089        }
1090    }
1091
1092    pub fn expect_apply_staging_manifests_reply(self) -> Vec<ApplyStagingManifestReply> {
1093        match self {
1094            Self::ApplyStagingManifests(reply) => reply.replies,
1095            _ => panic!("Expected ApplyStagingManifest reply"),
1096        }
1097    }
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102    use std::collections::HashSet;
1103
1104    use store_api::storage::{FileId, FileRef};
1105
1106    use super::*;
1107
1108    #[test]
1109    fn test_serialize_instruction() {
1110        let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1111            RegionIdent {
1112                datanode_id: 2,
1113                table_id: 1024,
1114                region_number: 1,
1115                engine: "mito2".to_string(),
1116            },
1117            "test/foo",
1118            HashMap::new(),
1119            HashMap::new(),
1120            false,
1121        )]);
1122
1123        let serialized = serde_json::to_string(&open_region).unwrap();
1124        assert_eq!(
1125            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}]}"#,
1126            serialized
1127        );
1128
1129        let close_region = Instruction::CloseRegions(vec![RegionIdent {
1130            datanode_id: 2,
1131            table_id: 1024,
1132            region_number: 1,
1133            engine: "mito2".to_string(),
1134        }]);
1135
1136        let serialized = serde_json::to_string(&close_region).unwrap();
1137        assert_eq!(
1138            r#"{"CloseRegions":[{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}]}"#,
1139            serialized
1140        );
1141
1142        let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1143            region_id: RegionId::new(1024, 1),
1144            last_entry_id: None,
1145            metadata_last_entry_id: None,
1146            replay_timeout: Duration::from_millis(1000),
1147            location_id: None,
1148            replay_entry_id: None,
1149            metadata_replay_entry_id: None,
1150        }]);
1151
1152        let serialized = serde_json::to_string(&upgrade_region).unwrap();
1153        assert_eq!(
1154            r#"{"UpgradeRegions":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"replay_timeout":"1s","location_id":null}]}"#,
1155            serialized
1156        );
1157    }
1158
1159    #[test]
1160    fn test_serialize_instruction_reply() {
1161        let downgrade_region_reply = InstructionReply::DowngradeRegions(
1162            DowngradeRegionsReply::single(DowngradeRegionReply {
1163                region_id: RegionId::new(1024, 1),
1164                last_entry_id: None,
1165                metadata_last_entry_id: None,
1166                exists: true,
1167                error: None,
1168            }),
1169        );
1170
1171        let serialized = serde_json::to_string(&downgrade_region_reply).unwrap();
1172        assert_eq!(
1173            r#"{"type":"downgrade_regions","replies":[{"region_id":4398046511105,"last_entry_id":null,"metadata_last_entry_id":null,"exists":true,"error":null}]}"#,
1174            serialized
1175        );
1176
1177        let upgrade_region_reply =
1178            InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1179                region_id: RegionId::new(1024, 1),
1180                ready: true,
1181                exists: true,
1182                error: None,
1183            }));
1184        let serialized = serde_json::to_string(&upgrade_region_reply).unwrap();
1185        assert_eq!(
1186            r#"{"type":"upgrade_regions","replies":[{"region_id":4398046511105,"ready":true,"exists":true,"error":null}]}"#,
1187            serialized
1188        );
1189    }
1190
1191    #[test]
1192    fn test_deserialize_instruction() {
1193        // legacy open region instruction
1194        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}}"#;
1195        let open_region_instruction: Instruction =
1196            serde_json::from_str(open_region_instruction).unwrap();
1197        let open_region = Instruction::OpenRegions(vec![OpenRegion::new(
1198            RegionIdent {
1199                datanode_id: 2,
1200                table_id: 1024,
1201                region_number: 1,
1202                engine: "mito2".to_string(),
1203            },
1204            "test/foo",
1205            HashMap::new(),
1206            HashMap::new(),
1207            false,
1208        )]);
1209        assert_eq!(open_region_instruction, open_region);
1210
1211        // legacy close region instruction
1212        let close_region_instruction = r#"{"CloseRegion":{"datanode_id":2,"table_id":1024,"region_number":1,"engine":"mito2"}}"#;
1213        let close_region_instruction: Instruction =
1214            serde_json::from_str(close_region_instruction).unwrap();
1215        let close_region = Instruction::CloseRegions(vec![RegionIdent {
1216            datanode_id: 2,
1217            table_id: 1024,
1218            region_number: 1,
1219            engine: "mito2".to_string(),
1220        }]);
1221        assert_eq!(close_region_instruction, close_region);
1222
1223        // legacy downgrade region instruction
1224        let downgrade_region_instruction = r#"{"DowngradeRegions":{"region_id":4398046511105,"flush_timeout":{"secs":1,"nanos":0}}}"#;
1225        let downgrade_region_instruction: Instruction =
1226            serde_json::from_str(downgrade_region_instruction).unwrap();
1227        let downgrade_region = Instruction::DowngradeRegions(vec![DowngradeRegion {
1228            region_id: RegionId::new(1024, 1),
1229            flush_timeout: Some(Duration::from_millis(1000)),
1230        }]);
1231        assert_eq!(downgrade_region_instruction, downgrade_region);
1232
1233        // legacy upgrade region instruction
1234        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}}"#;
1235        let upgrade_region_instruction: Instruction =
1236            serde_json::from_str(upgrade_region_instruction).unwrap();
1237        let upgrade_region = Instruction::UpgradeRegions(vec![UpgradeRegion {
1238            region_id: RegionId::new(1024, 1),
1239            last_entry_id: None,
1240            metadata_last_entry_id: None,
1241            replay_timeout: Duration::from_millis(1000),
1242            location_id: None,
1243            replay_entry_id: None,
1244            metadata_replay_entry_id: None,
1245        }]);
1246        assert_eq!(upgrade_region_instruction, upgrade_region);
1247    }
1248
1249    #[test]
1250    fn test_deserialize_instruction_reply() {
1251        // legacy close region reply
1252        let close_region_instruction_reply =
1253            r#"{"result":true,"error":null,"type":"close_region"}"#;
1254        let close_region_instruction_reply: InstructionReply =
1255            serde_json::from_str(close_region_instruction_reply).unwrap();
1256        let close_region_reply = InstructionReply::CloseRegions(SimpleReply {
1257            result: true,
1258            error: None,
1259        });
1260        assert_eq!(close_region_instruction_reply, close_region_reply);
1261
1262        // legacy open region reply
1263        let open_region_instruction_reply = r#"{"result":true,"error":null,"type":"open_region"}"#;
1264        let open_region_instruction_reply: InstructionReply =
1265            serde_json::from_str(open_region_instruction_reply).unwrap();
1266        let open_region_reply = InstructionReply::OpenRegions(SimpleReply {
1267            result: true,
1268            error: None,
1269        });
1270        assert_eq!(open_region_instruction_reply, open_region_reply);
1271
1272        // legacy downgrade region reply
1273        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"}"#;
1274        let downgrade_region_instruction_reply: InstructionReply =
1275            serde_json::from_str(downgrade_region_instruction_reply).unwrap();
1276        let downgrade_region_reply = InstructionReply::DowngradeRegions(
1277            DowngradeRegionsReply::single(DowngradeRegionReply {
1278                region_id: RegionId::new(1024, 1),
1279                last_entry_id: None,
1280                metadata_last_entry_id: None,
1281                exists: true,
1282                error: None,
1283            }),
1284        );
1285        assert_eq!(downgrade_region_instruction_reply, downgrade_region_reply);
1286
1287        // legacy upgrade region reply
1288        let upgrade_region_instruction_reply = r#"{"region_id":4398046511105,"ready":true,"exists":true,"error":null,"type":"upgrade_region"}"#;
1289        let upgrade_region_instruction_reply: InstructionReply =
1290            serde_json::from_str(upgrade_region_instruction_reply).unwrap();
1291        let upgrade_region_reply =
1292            InstructionReply::UpgradeRegions(UpgradeRegionsReply::single(UpgradeRegionReply {
1293                region_id: RegionId::new(1024, 1),
1294                ready: true,
1295                exists: true,
1296                error: None,
1297            }));
1298        assert_eq!(upgrade_region_instruction_reply, upgrade_region_reply);
1299    }
1300
1301    #[test]
1302    fn test_enter_staging_partition_rule_compatibility() {
1303        let legacy = r#"{"region_id":4398046511105,"partition_expr":"{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"}"#;
1304        let enter: EnterStagingRegion = serde_json::from_str(legacy).unwrap();
1305        assert_eq!(enter.region_id, RegionId::new(1024, 1));
1306        assert_eq!(
1307            enter.partition_directive,
1308            StagingPartitionDirective::UpdatePartitionExpr(
1309                "{\"Expr\":{\"lhs\":{\"Column\":\"x\"},\"op\":\"GtEq\",\"rhs\":{\"Value\":{\"Int32\":0}}}}"
1310                    .to_string()
1311            )
1312        );
1313
1314        let serialized = serde_json::to_string(&enter).unwrap();
1315        assert!(serialized.contains("\"partition_directive\":\""));
1316        assert!(!serialized.contains("partition_expr"));
1317
1318        let reject = r#"{"region_id":4398046511105,"partition_expr":{"type":"reject_all_writes"}}"#;
1319        let enter: EnterStagingRegion = serde_json::from_str(reject).unwrap();
1320        assert_eq!(
1321            enter.partition_directive,
1322            StagingPartitionDirective::RejectAllWrites
1323        );
1324    }
1325
1326    #[derive(Debug, Clone, Serialize, Deserialize)]
1327    struct LegacyOpenRegion {
1328        region_ident: RegionIdent,
1329        region_storage_path: String,
1330        region_options: HashMap<String, String>,
1331    }
1332
1333    #[test]
1334    fn test_compatible_serialize_open_region() {
1335        let region_ident = RegionIdent {
1336            datanode_id: 2,
1337            table_id: 1024,
1338            region_number: 1,
1339            engine: "mito2".to_string(),
1340        };
1341        let region_storage_path = "test/foo".to_string();
1342        let region_options = HashMap::from([
1343            ("a".to_string(), "aa".to_string()),
1344            ("b".to_string(), "bb".to_string()),
1345        ]);
1346
1347        // Serialize a legacy OpenRegion.
1348        let legacy_open_region = LegacyOpenRegion {
1349            region_ident: region_ident.clone(),
1350            region_storage_path: region_storage_path.clone(),
1351            region_options: region_options.clone(),
1352        };
1353        let serialized = serde_json::to_string(&legacy_open_region).unwrap();
1354
1355        // Deserialize to OpenRegion.
1356        let deserialized = serde_json::from_str(&serialized).unwrap();
1357        let expected = OpenRegion {
1358            region_ident,
1359            region_storage_path,
1360            region_options,
1361            region_wal_options: HashMap::new(),
1362            skip_wal_replay: false,
1363        };
1364        assert_eq!(expected, deserialized);
1365    }
1366
1367    #[test]
1368    fn test_flush_regions_creation() {
1369        let region_id = RegionId::new(1024, 1);
1370
1371        // Single region sync flush
1372        let single_sync = FlushRegions::sync_single(region_id);
1373        assert_eq!(single_sync.region_ids, vec![region_id]);
1374        assert_eq!(single_sync.strategy, FlushStrategy::Sync);
1375        assert!(!single_sync.is_hint());
1376        assert!(single_sync.is_sync());
1377        assert_eq!(single_sync.error_strategy, FlushErrorStrategy::FailFast);
1378        assert_eq!(single_sync.reason, None);
1379        assert!(single_sync.is_single_region());
1380        assert_eq!(single_sync.single_region_id(), Some(region_id));
1381
1382        // Batch async flush (hint)
1383        let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1384        let batch_async = FlushRegions::async_batch(region_ids.clone());
1385        assert_eq!(batch_async.region_ids, region_ids);
1386        assert_eq!(batch_async.strategy, FlushStrategy::Async);
1387        assert!(batch_async.is_hint());
1388        assert!(!batch_async.is_sync());
1389        assert_eq!(batch_async.error_strategy, FlushErrorStrategy::TryAll);
1390        assert_eq!(batch_async.reason, None);
1391        assert!(!batch_async.is_single_region());
1392        assert_eq!(batch_async.single_region_id(), None);
1393
1394        // Batch sync flush
1395        let batch_sync = FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::FailFast);
1396        assert_eq!(batch_sync.region_ids, region_ids);
1397        assert_eq!(batch_sync.strategy, FlushStrategy::Sync);
1398        assert!(!batch_sync.is_hint());
1399        assert!(batch_sync.is_sync());
1400        assert_eq!(batch_sync.error_strategy, FlushErrorStrategy::FailFast);
1401        assert_eq!(batch_sync.reason, None);
1402
1403        let with_reason = batch_sync.with_reason(RegionFlushReason::RemoteWalPrune);
1404        assert_eq!(with_reason.reason, Some(RegionFlushReason::RemoteWalPrune));
1405    }
1406
1407    #[test]
1408    fn test_flush_regions_conversion() {
1409        let region_id = RegionId::new(1024, 1);
1410
1411        let from_region_id: FlushRegions = region_id.into();
1412        assert_eq!(from_region_id.region_ids, vec![region_id]);
1413        assert_eq!(from_region_id.strategy, FlushStrategy::Sync);
1414        assert!(!from_region_id.is_hint());
1415        assert!(from_region_id.is_sync());
1416
1417        // Test default construction
1418        let flush_regions = FlushRegions {
1419            region_ids: vec![region_id],
1420            strategy: FlushStrategy::Async,
1421            error_strategy: FlushErrorStrategy::TryAll,
1422            reason: None,
1423        };
1424        assert_eq!(flush_regions.region_ids, vec![region_id]);
1425        assert_eq!(flush_regions.strategy, FlushStrategy::Async);
1426        assert!(flush_regions.is_hint());
1427        assert!(!flush_regions.is_sync());
1428    }
1429
1430    #[test]
1431    fn test_flush_region_reply() {
1432        let region_id = RegionId::new(1024, 1);
1433
1434        // Successful single region reply
1435        let success_reply = FlushRegionReply::success_single(region_id);
1436        assert!(success_reply.overall_success);
1437        assert_eq!(success_reply.results.len(), 1);
1438        assert_eq!(success_reply.results[0].0, region_id);
1439        assert!(success_reply.results[0].1.is_ok());
1440
1441        // Failed single region reply
1442        let error_reply = FlushRegionReply::error_single(region_id, "test error".to_string());
1443        assert!(!error_reply.overall_success);
1444        assert_eq!(error_reply.results.len(), 1);
1445        assert_eq!(error_reply.results[0].0, region_id);
1446        assert!(error_reply.results[0].1.is_err());
1447
1448        // Batch reply
1449        let region_id2 = RegionId::new(1024, 2);
1450        let results = vec![
1451            (region_id, Ok(())),
1452            (region_id2, Err("flush failed".to_string())),
1453        ];
1454        let batch_reply = FlushRegionReply::from_results(results);
1455        assert!(!batch_reply.overall_success);
1456        assert_eq!(batch_reply.results.len(), 2);
1457
1458        // Conversion to SimpleReply
1459        let simple_reply = batch_reply.to_simple_reply();
1460        assert!(!simple_reply.result);
1461        assert!(simple_reply.error.is_some());
1462        assert!(simple_reply.error.unwrap().contains("flush failed"));
1463    }
1464
1465    #[test]
1466    fn test_serialize_flush_regions_instruction() {
1467        let region_id = RegionId::new(1024, 1);
1468        let flush_regions = FlushRegions::sync_single(region_id);
1469        let instruction = Instruction::FlushRegions(flush_regions.clone());
1470
1471        let serialized = serde_json::to_string(&instruction).unwrap();
1472        assert!(!serialized.contains("reason"));
1473        let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1474
1475        match deserialized {
1476            Instruction::FlushRegions(fr) => {
1477                assert_eq!(fr.region_ids, vec![region_id]);
1478                assert_eq!(fr.strategy, FlushStrategy::Sync);
1479                assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1480                assert_eq!(fr.reason, None);
1481            }
1482            _ => panic!("Expected FlushRegions instruction"),
1483        }
1484
1485        let legacy = r#"{"FlushRegions":{"region_ids":[4398046511105],"strategy":"Sync","error_strategy":"FailFast"}}"#;
1486        let deserialized: Instruction = serde_json::from_str(legacy).unwrap();
1487        match deserialized {
1488            Instruction::FlushRegions(fr) => {
1489                assert_eq!(fr.region_ids, vec![region_id]);
1490                assert_eq!(fr.strategy, FlushStrategy::Sync);
1491                assert_eq!(fr.error_strategy, FlushErrorStrategy::FailFast);
1492                assert_eq!(fr.reason, None);
1493            }
1494            _ => panic!("Expected FlushRegions instruction"),
1495        }
1496
1497        let flush_regions = FlushRegions::async_batch(vec![region_id])
1498            .with_reason(RegionFlushReason::RemoteWalPrune);
1499        let instruction = Instruction::FlushRegions(flush_regions);
1500        let serialized = serde_json::to_string(&instruction).unwrap();
1501        assert!(serialized.contains(r#""reason":"RemoteWalPrune""#));
1502        let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1503        match deserialized {
1504            Instruction::FlushRegions(fr) => {
1505                assert_eq!(fr.reason, Some(RegionFlushReason::RemoteWalPrune));
1506            }
1507            _ => panic!("Expected FlushRegions instruction"),
1508        }
1509    }
1510
1511    #[test]
1512    fn test_serialize_flush_regions_batch_instruction() {
1513        let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
1514        let flush_regions =
1515            FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::TryAll);
1516        let instruction = Instruction::FlushRegions(flush_regions);
1517
1518        let serialized = serde_json::to_string(&instruction).unwrap();
1519        let deserialized: Instruction = serde_json::from_str(&serialized).unwrap();
1520
1521        match deserialized {
1522            Instruction::FlushRegions(fr) => {
1523                assert_eq!(fr.region_ids, region_ids);
1524                assert_eq!(fr.strategy, FlushStrategy::Sync);
1525                assert!(!fr.is_hint());
1526                assert!(fr.is_sync());
1527                assert_eq!(fr.error_strategy, FlushErrorStrategy::TryAll);
1528                assert_eq!(fr.reason, None);
1529            }
1530            _ => panic!("Expected FlushRegions instruction"),
1531        }
1532    }
1533
1534    #[test]
1535    fn test_serialize_get_file_refs_instruction_reply() {
1536        let mut manifest = FileRefsManifest::default();
1537        let r0 = RegionId::new(1024, 1);
1538        let r1 = RegionId::new(1024, 2);
1539        manifest.file_refs.insert(
1540            r0,
1541            HashSet::from([FileRef::new(r0, FileId::random(), None)]),
1542        );
1543        manifest.file_refs.insert(
1544            r1,
1545            HashSet::from([FileRef::new(r1, FileId::random(), None)]),
1546        );
1547        manifest.manifest_version.insert(r0, 10);
1548        manifest.manifest_version.insert(r1, 20);
1549
1550        let instruction_reply = InstructionReply::GetFileRefs(GetFileRefsReply {
1551            file_refs_manifest: manifest,
1552            success: true,
1553            error: None,
1554        });
1555
1556        let serialized = serde_json::to_string(&instruction_reply).unwrap();
1557        let deserialized = serde_json::from_str(&serialized).unwrap();
1558
1559        assert_eq!(instruction_reply, deserialized);
1560    }
1561}