Skip to main content

store_api/
region_request.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;
16use std::fmt::{self, Display};
17
18use api::helper::{ColumnDataTypeWrapper, from_pb_time_ranges};
19use api::v1::add_column_location::LocationType;
20use api::v1::column_def::{
21    as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type,
22};
23use api::v1::region::bulk_insert_request::Body;
24use api::v1::region::{
25    AlterRequest, AlterRequests, BuildIndexRequest, BulkInsertRequest, CloseRequest,
26    CompactRequest, CreateRequest, CreateRequests, DeleteRequests, DropRequest, DropRequests,
27    FlushRequest, InsertRequests, OpenRequest, TruncateRequest, alter_request, compact_request,
28    region_request, truncate_request,
29};
30use api::v1::{
31    self, Analyzer, ArrowIpc, FulltextBackend as PbFulltextBackend, Option as PbOption, Rows,
32    SemanticType, SkippingIndexType as PbSkippingIndexType, WriteHint,
33};
34pub use common_base::AffectedRows;
35use common_grpc::flight::FlightDecoder;
36use common_recordbatch::DfRecordBatch;
37use common_time::{TimeToLive, Timestamp};
38use datatypes::prelude::ConcreteDataType;
39use datatypes::schema::{FulltextOptions, SkippingIndexOptions};
40use num_enum::TryFromPrimitive;
41use serde::{Deserialize, Serialize};
42use snafu::{OptionExt, ResultExt, ensure};
43use strum::{AsRefStr, IntoStaticStr};
44
45use crate::logstore::entry;
46use crate::metadata::{
47    ColumnMetadata, ConvertTimeRangesSnafu, DecodeProtoSnafu, FlightCodecSnafu,
48    InvalidIndexOptionSnafu, InvalidRawRegionRequestSnafu, InvalidRegionRequestSnafu,
49    InvalidSetRegionOptionRequestSnafu, InvalidUnsetRegionOptionRequestSnafu, MetadataError,
50    RegionMetadata, Result, UnexpectedSnafu,
51};
52use crate::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
53use crate::metrics;
54use crate::mito_engine_options::{
55    APPEND_MODE_KEY, SST_FORMAT_KEY, TTL_KEY, TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW,
56    TWCS_TRIGGER_FILE_NUM,
57};
58use crate::path_utils::table_dir;
59use crate::storage::{ColumnId, RegionId, ScanRequest};
60
61/// The type of path to generate.
62#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
63#[repr(u8)]
64pub enum PathType {
65    /// A bare path - the original path of an engine.
66    ///
67    /// The path prefix is `{table_dir}/{table_id}_{region_sequence}/`.
68    Bare,
69    /// A path for the data region of a metric engine table.
70    ///
71    /// The path prefix is `{table_dir}/{table_id}_{region_sequence}/data/`.
72    Data,
73    /// A path for the metadata region of a metric engine table.
74    ///
75    /// The path prefix is `{table_dir}/{table_id}_{region_sequence}/metadata/`.
76    Metadata,
77}
78
79#[derive(Debug, IntoStaticStr)]
80pub enum BatchRegionDdlRequest {
81    Create(Vec<(RegionId, RegionCreateRequest)>),
82    Drop(Vec<(RegionId, RegionDropRequest)>),
83    Alter(Vec<(RegionId, RegionAlterRequest)>),
84}
85
86impl BatchRegionDdlRequest {
87    /// Converts [Body](region_request::Body) to [`BatchRegionDdlRequest`].
88    pub fn try_from_request_body(body: region_request::Body) -> Result<Option<Self>> {
89        match body {
90            region_request::Body::Creates(creates) => {
91                let requests = creates
92                    .requests
93                    .into_iter()
94                    .map(parse_region_create)
95                    .collect::<Result<Vec<_>>>()?;
96                Ok(Some(Self::Create(requests)))
97            }
98            region_request::Body::Drops(drops) => {
99                let requests = drops
100                    .requests
101                    .into_iter()
102                    .map(parse_region_drop)
103                    .collect::<Result<Vec<_>>>()?;
104                Ok(Some(Self::Drop(requests)))
105            }
106            region_request::Body::Alters(alters) => {
107                let requests = alters
108                    .requests
109                    .into_iter()
110                    .map(parse_region_alter)
111                    .collect::<Result<Vec<_>>>()?;
112                Ok(Some(Self::Alter(requests)))
113            }
114            _ => Ok(None),
115        }
116    }
117
118    pub fn request_type(&self) -> &'static str {
119        self.into()
120    }
121
122    pub fn into_region_requests(self) -> Vec<(RegionId, RegionRequest)> {
123        match self {
124            Self::Create(requests) => requests
125                .into_iter()
126                .map(|(region_id, request)| (region_id, RegionRequest::Create(request)))
127                .collect(),
128            Self::Drop(requests) => requests
129                .into_iter()
130                .map(|(region_id, request)| (region_id, RegionRequest::Drop(request)))
131                .collect(),
132            Self::Alter(requests) => requests
133                .into_iter()
134                .map(|(region_id, request)| (region_id, RegionRequest::Alter(request)))
135                .collect(),
136        }
137    }
138}
139
140#[derive(Debug, IntoStaticStr)]
141pub enum RegionRequest {
142    Put(RegionPutRequest),
143    Delete(RegionDeleteRequest),
144    Create(RegionCreateRequest),
145    Drop(RegionDropRequest),
146    Open(RegionOpenRequest),
147    Close(RegionCloseRequest),
148    Alter(RegionAlterRequest),
149    Flush(RegionFlushRequest),
150    Compact(RegionCompactRequest),
151    BuildIndex(RegionBuildIndexRequest),
152    Truncate(RegionTruncateRequest),
153    Catchup(RegionCatchupRequest),
154    BulkInserts(RegionBulkInsertsRequest),
155    EnterStaging(EnterStagingRequest),
156    ApplyStagingManifest(ApplyStagingManifestRequest),
157}
158
159impl RegionRequest {
160    /// Convert [Body](region_request::Body) to a group of [RegionRequest] with region id.
161    /// Inserts/Deletes request might become multiple requests. Others are one-to-one.
162    pub fn try_from_request_body(body: region_request::Body) -> Result<Vec<(RegionId, Self)>> {
163        match body {
164            region_request::Body::Inserts(inserts) => make_region_puts(inserts),
165            region_request::Body::Deletes(deletes) => make_region_deletes(deletes),
166            region_request::Body::Create(create) => make_region_create(create),
167            region_request::Body::Drop(drop) => make_region_drop(drop),
168            region_request::Body::Open(open) => make_region_open(open),
169            region_request::Body::Close(close) => make_region_close(close),
170            region_request::Body::Alter(alter) => make_region_alter(alter),
171            region_request::Body::Flush(flush) => make_region_flush(flush),
172            region_request::Body::Compact(compact) => make_region_compact(compact),
173            region_request::Body::BuildIndex(index) => make_region_build_index(index),
174            region_request::Body::Truncate(truncate) => make_region_truncate(truncate),
175            region_request::Body::Creates(creates) => make_region_creates(creates),
176            region_request::Body::Drops(drops) => make_region_drops(drops),
177            region_request::Body::Alters(alters) => make_region_alters(alters),
178            region_request::Body::BulkInsert(bulk) => make_region_bulk_inserts(bulk),
179            region_request::Body::Sync(_) => UnexpectedSnafu {
180                reason: "Sync request should be handled separately by RegionServer",
181            }
182            .fail(),
183            region_request::Body::ListMetadata(_) => UnexpectedSnafu {
184                reason: "ListMetadata request should be handled separately by RegionServer",
185            }
186            .fail(),
187            region_request::Body::RemoteDynFilter(_) => UnexpectedSnafu {
188                reason: "RemoteDynFilter request should be handled separately by RegionServer",
189            }
190            .fail(),
191            region_request::Body::ApplyStagingManifest(apply) => {
192                make_region_apply_staging_manifest(apply)
193            }
194        }
195    }
196
197    /// Returns the type name of the request.
198    pub fn request_type(&self) -> &'static str {
199        self.into()
200    }
201}
202
203fn make_region_puts(inserts: InsertRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
204    let requests = inserts
205        .requests
206        .into_iter()
207        .filter_map(|r| {
208            let region_id = r.region_id.into();
209            r.rows.map(|rows| {
210                (
211                    region_id,
212                    RegionRequest::Put(RegionPutRequest {
213                        rows,
214                        hint: None,
215                        partition_expr_version: r.partition_expr_version.map(|v| v.value),
216                    }),
217                )
218            })
219        })
220        .collect();
221    Ok(requests)
222}
223
224fn make_region_deletes(deletes: DeleteRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
225    let requests = deletes
226        .requests
227        .into_iter()
228        .filter_map(|r| {
229            let region_id = r.region_id.into();
230            r.rows.map(|rows| {
231                (
232                    region_id,
233                    RegionRequest::Delete(RegionDeleteRequest {
234                        rows,
235                        hint: None,
236                        partition_expr_version: r.partition_expr_version.map(|v| v.value),
237                    }),
238                )
239            })
240        })
241        .collect();
242    Ok(requests)
243}
244
245fn parse_region_create(create: CreateRequest) -> Result<(RegionId, RegionCreateRequest)> {
246    let column_metadatas = create
247        .column_defs
248        .into_iter()
249        .map(ColumnMetadata::try_from_column_def)
250        .collect::<Result<Vec<_>>>()?;
251    let region_id = RegionId::from(create.region_id);
252    let table_dir = table_dir(&create.path, region_id.table_id());
253    let partition_expr_json = create.partition.as_ref().map(|p| p.expression.clone());
254    Ok((
255        region_id,
256        RegionCreateRequest {
257            engine: create.engine,
258            column_metadatas,
259            primary_key: create.primary_key,
260            options: create.options,
261            table_dir,
262            path_type: PathType::Bare,
263            partition_expr_json,
264        },
265    ))
266}
267
268fn make_region_create(create: CreateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
269    let (region_id, request) = parse_region_create(create)?;
270    Ok(vec![(region_id, RegionRequest::Create(request))])
271}
272
273fn make_region_creates(creates: CreateRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
274    let mut requests = Vec::with_capacity(creates.requests.len());
275    for create in creates.requests {
276        requests.extend(make_region_create(create)?);
277    }
278    Ok(requests)
279}
280
281fn parse_region_drop(drop: DropRequest) -> Result<(RegionId, RegionDropRequest)> {
282    let region_id = drop.region_id.into();
283    Ok((
284        region_id,
285        RegionDropRequest {
286            fast_path: drop.fast_path,
287            force: drop.force,
288            partial_drop: drop.partial_drop,
289        },
290    ))
291}
292
293fn make_region_drop(drop: DropRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
294    let (region_id, request) = parse_region_drop(drop)?;
295    Ok(vec![(region_id, RegionRequest::Drop(request))])
296}
297
298fn make_region_drops(drops: DropRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
299    let mut requests = Vec::with_capacity(drops.requests.len());
300    for drop in drops.requests {
301        requests.extend(make_region_drop(drop)?);
302    }
303    Ok(requests)
304}
305
306fn make_region_open(open: OpenRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
307    let region_id = RegionId::from(open.region_id);
308    let table_dir = table_dir(&open.path, region_id.table_id());
309    Ok(vec![(
310        region_id,
311        RegionRequest::Open(RegionOpenRequest {
312            engine: open.engine,
313            table_dir,
314            path_type: PathType::Bare,
315            options: open.options,
316            skip_wal_replay: false,
317            checkpoint: None,
318            requirements: Default::default(),
319        }),
320    )])
321}
322
323fn make_region_close(close: CloseRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
324    let region_id = close.region_id.into();
325    Ok(vec![(
326        region_id,
327        RegionRequest::Close(RegionCloseRequest {}),
328    )])
329}
330
331fn parse_region_alter(alter: AlterRequest) -> Result<(RegionId, RegionAlterRequest)> {
332    let region_id = alter.region_id.into();
333    let request = RegionAlterRequest::try_from(alter)?;
334    Ok((region_id, request))
335}
336
337fn make_region_alter(alter: AlterRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
338    let (region_id, request) = parse_region_alter(alter)?;
339    Ok(vec![(region_id, RegionRequest::Alter(request))])
340}
341
342fn make_region_alters(alters: AlterRequests) -> Result<Vec<(RegionId, RegionRequest)>> {
343    let mut requests = Vec::with_capacity(alters.requests.len());
344    for alter in alters.requests {
345        requests.extend(make_region_alter(alter)?);
346    }
347    Ok(requests)
348}
349
350fn make_region_flush(flush: FlushRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
351    let region_id = flush.region_id.into();
352    Ok(vec![(
353        region_id,
354        RegionRequest::Flush(RegionFlushRequest::default()),
355    )])
356}
357
358fn make_region_compact(compact: CompactRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
359    let region_id = compact.region_id.into();
360    let options = compact
361        .options
362        .unwrap_or(compact_request::Options::Regular(Default::default()));
363    // Convert parallelism: a value of 0 indicates no specific parallelism requested (None)
364    let parallelism = if compact.parallelism == 0 {
365        None
366    } else {
367        Some(compact.parallelism)
368    };
369    Ok(vec![(
370        region_id,
371        RegionRequest::Compact(RegionCompactRequest {
372            options,
373            parallelism,
374        }),
375    )])
376}
377
378fn make_region_build_index(index: BuildIndexRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
379    let region_id = index.region_id.into();
380    Ok(vec![(
381        region_id,
382        RegionRequest::BuildIndex(RegionBuildIndexRequest {}),
383    )])
384}
385
386fn make_region_truncate(truncate: TruncateRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
387    let region_id = truncate.region_id.into();
388    match truncate.kind {
389        None => InvalidRawRegionRequestSnafu {
390            err: "missing kind in TruncateRequest".to_string(),
391        }
392        .fail(),
393        Some(truncate_request::Kind::All(_)) => Ok(vec![(
394            region_id,
395            RegionRequest::Truncate(RegionTruncateRequest::All),
396        )]),
397        Some(truncate_request::Kind::TimeRanges(time_ranges)) => {
398            let time_ranges = from_pb_time_ranges(time_ranges).context(ConvertTimeRangesSnafu)?;
399
400            Ok(vec![(
401                region_id,
402                RegionRequest::Truncate(RegionTruncateRequest::ByTimeRanges { time_ranges }),
403            )])
404        }
405    }
406}
407
408/// Convert [BulkInsertRequest] to [RegionRequest] and group by [RegionId].
409fn make_region_bulk_inserts(request: BulkInsertRequest) -> Result<Vec<(RegionId, RegionRequest)>> {
410    let region_id = request.region_id.into();
411    let partition_expr_version = request.partition_expr_version.map(|v| v.value);
412    let Some(Body::ArrowIpc(request)) = request.body else {
413        return Ok(vec![]);
414    };
415
416    let decoder_timer = metrics::CONVERT_REGION_BULK_REQUEST
417        .with_label_values(&["decode"])
418        .start_timer();
419    let mut decoder =
420        FlightDecoder::try_from_schema_bytes(&request.schema).context(FlightCodecSnafu)?;
421    let payload = decoder
422        .try_decode_record_batch(&request.data_header, &request.payload)
423        .context(FlightCodecSnafu)?;
424    decoder_timer.observe_duration();
425    Ok(vec![(
426        region_id,
427        RegionRequest::BulkInserts(RegionBulkInsertsRequest {
428            region_id,
429            payload,
430            raw_data: request,
431            partition_expr_version,
432        }),
433    )])
434}
435
436fn make_region_apply_staging_manifest(
437    api::v1::region::ApplyStagingManifestRequest {
438        region_id,
439        partition_expr,
440        central_region_id,
441        manifest_path,
442    }: api::v1::region::ApplyStagingManifestRequest,
443) -> Result<Vec<(RegionId, RegionRequest)>> {
444    let region_id = region_id.into();
445    Ok(vec![(
446        region_id,
447        RegionRequest::ApplyStagingManifest(ApplyStagingManifestRequest {
448            partition_expr,
449            central_region_id: central_region_id.into(),
450            manifest_path,
451        }),
452    )])
453}
454
455/// Request to put data into a region.
456#[derive(Debug)]
457pub struct RegionPutRequest {
458    /// Rows to put.
459    pub rows: Rows,
460    /// Write hint.
461    pub hint: Option<WriteHint>,
462    /// Partition expression version for the region.
463    pub partition_expr_version: Option<u64>,
464}
465
466#[derive(Debug)]
467pub struct RegionReadRequest {
468    pub request: ScanRequest,
469}
470
471/// Request to delete data from a region.
472#[derive(Debug)]
473pub struct RegionDeleteRequest {
474    /// Keys to rows to delete.
475    ///
476    /// Each row only contains primary key columns and a time index column.
477    pub rows: Rows,
478    /// Write hint.
479    pub hint: Option<WriteHint>,
480    /// Partition expression version for the region.
481    pub partition_expr_version: Option<u64>,
482}
483
484#[derive(Debug, Clone)]
485pub struct RegionCreateRequest {
486    /// Region engine name
487    pub engine: String,
488    /// Columns in this region.
489    pub column_metadatas: Vec<ColumnMetadata>,
490    /// Columns in the primary key.
491    pub primary_key: Vec<ColumnId>,
492    /// Options of the created region.
493    pub options: HashMap<String, String>,
494    /// Directory for table's data home. Usually is composed by catalog and table id
495    pub table_dir: String,
496    /// Path type for generating paths
497    pub path_type: PathType,
498    /// Partition expression JSON from table metadata. Set to empty string for a region without partition.
499    /// `Option` to keep compatibility with old clients.
500    pub partition_expr_json: Option<String>,
501}
502
503impl RegionCreateRequest {
504    /// Checks whether the request is valid, returns an error if it is invalid.
505    pub fn validate(&self) -> Result<()> {
506        // time index must exist
507        ensure!(
508            self.column_metadatas
509                .iter()
510                .any(|x| x.semantic_type == SemanticType::Timestamp),
511            InvalidRegionRequestSnafu {
512                region_id: RegionId::new(0, 0),
513                err: "missing timestamp column in create region request".to_string(),
514            }
515        );
516
517        // build column id to indices
518        let mut column_id_to_indices = HashMap::with_capacity(self.column_metadatas.len());
519        for (i, c) in self.column_metadatas.iter().enumerate() {
520            if let Some(previous) = column_id_to_indices.insert(c.column_id, i) {
521                return InvalidRegionRequestSnafu {
522                    region_id: RegionId::new(0, 0),
523                    err: format!(
524                        "duplicate column id {} (at position {} and {}) in create region request",
525                        c.column_id, previous, i
526                    ),
527                }
528                .fail();
529            }
530        }
531
532        // primary key must exist
533        for column_id in &self.primary_key {
534            ensure!(
535                column_id_to_indices.contains_key(column_id),
536                InvalidRegionRequestSnafu {
537                    region_id: RegionId::new(0, 0),
538                    err: format!(
539                        "missing primary key column {} in create region request",
540                        column_id
541                    ),
542                }
543            );
544        }
545
546        Ok(())
547    }
548
549    /// Returns true when the region belongs to the metric engine's physical table.
550    pub fn is_physical_table(&self) -> bool {
551        self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
552    }
553}
554
555#[derive(Debug, Clone)]
556pub struct RegionDropRequest {
557    /// Enables fast-path drop optimizations for logical regions.
558    /// Only applicable to the Metric Engine; ignored by others.
559    pub fast_path: bool,
560
561    /// Forces the drop of a physical region and all its associated logical regions.
562    /// Only relevant for physical regions managed by the Metric Engine.
563    pub force: bool,
564
565    /// If true, indicates that only a portion of the region is being dropped, and files may still be referenced by other regions.
566    /// This is used to prevent deletion of files that are still in use by other regions.
567    pub partial_drop: bool,
568}
569
570/// Requirements for a region request.
571#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
572#[serde(default)]
573pub struct RegionRequirements {
574    /// Whether the region data must be backed by object storage.
575    pub object_storage: bool,
576}
577
578impl RegionRequirements {
579    /// Returns empty requirements.
580    pub fn empty() -> Self {
581        Self::default()
582    }
583
584    /// Returns requirements for object storage.
585    pub fn object_storage() -> Self {
586        Self {
587            object_storage: true,
588        }
589    }
590}
591
592/// Open region request.
593#[derive(Debug, Clone)]
594pub struct RegionOpenRequest {
595    /// Region engine name
596    pub engine: String,
597    /// Directory for table's data home. Usually is composed by catalog and table id
598    pub table_dir: String,
599    /// Path type for generating paths
600    pub path_type: PathType,
601    /// Options of the opened region.
602    pub options: HashMap<String, String>,
603    /// To skip replaying the WAL.
604    pub skip_wal_replay: bool,
605    /// Replay checkpoint.
606    pub checkpoint: Option<ReplayCheckpoint>,
607    /// Requirements for opening the region.
608    pub requirements: RegionRequirements,
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq)]
612pub struct ReplayCheckpoint {
613    pub entry_id: u64,
614    pub metadata_entry_id: Option<u64>,
615}
616
617impl RegionOpenRequest {
618    /// Returns true when the region belongs to the metric engine's physical table.
619    pub fn is_physical_table(&self) -> bool {
620        self.options.contains_key(PHYSICAL_TABLE_METADATA_KEY)
621    }
622}
623
624/// Close region request.
625#[derive(Debug)]
626pub struct RegionCloseRequest {}
627
628/// Alter metadata of a region.
629#[derive(Debug, PartialEq, Eq, Clone)]
630pub struct RegionAlterRequest {
631    /// Kind of alteration to do.
632    pub kind: AlterKind,
633}
634
635impl RegionAlterRequest {
636    /// Checks whether the request is valid, returns an error if it is invalid.
637    pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
638        self.kind.validate(metadata)?;
639
640        Ok(())
641    }
642
643    /// Returns true if we need to apply the request to the region.
644    ///
645    /// The `request` should be valid.
646    pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
647        debug_assert!(self.validate(metadata).is_ok());
648        self.kind.need_alter(metadata)
649    }
650}
651
652impl TryFrom<AlterRequest> for RegionAlterRequest {
653    type Error = MetadataError;
654
655    fn try_from(value: AlterRequest) -> Result<Self> {
656        let kind = value.kind.context(InvalidRawRegionRequestSnafu {
657            err: "missing kind in AlterRequest",
658        })?;
659
660        let kind = AlterKind::try_from(kind)?;
661        Ok(RegionAlterRequest { kind })
662    }
663}
664
665/// Kind of the alteration.
666#[derive(Debug, PartialEq, Eq, Clone, AsRefStr)]
667pub enum AlterKind {
668    /// Add columns to the region.
669    AddColumns {
670        /// Columns to add.
671        columns: Vec<AddColumn>,
672    },
673    /// Drop columns from the region, only fields are allowed to drop.
674    DropColumns {
675        /// Name of columns to drop.
676        names: Vec<String>,
677    },
678    /// Change columns datatype form the region, only fields are allowed to change.
679    ModifyColumnTypes {
680        /// Columns to change.
681        columns: Vec<ModifyColumnType>,
682    },
683    /// Set region options.
684    SetRegionOptions { options: Vec<SetRegionOption> },
685    /// Unset region options.
686    UnsetRegionOptions { keys: Vec<UnsetRegionOption> },
687    /// Set index options.
688    SetIndexes { options: Vec<SetIndexOption> },
689    /// Unset index options.
690    UnsetIndexes { options: Vec<UnsetIndexOption> },
691    /// Drop column default value.
692    DropDefaults {
693        /// Name of columns to drop.
694        names: Vec<String>,
695    },
696    /// Set column default value.
697    SetDefaults {
698        /// Columns to change.
699        columns: Vec<SetDefault>,
700    },
701    /// Sync column metadatas.
702    SyncColumns {
703        column_metadatas: Vec<ColumnMetadata>,
704    },
705}
706#[derive(Debug, PartialEq, Eq, Clone)]
707pub struct SetDefault {
708    pub name: String,
709    pub default_constraint: Vec<u8>,
710}
711
712#[derive(Debug, PartialEq, Eq, Clone)]
713pub enum SetIndexOption {
714    Fulltext {
715        column_name: String,
716        options: FulltextOptions,
717    },
718    Inverted {
719        column_name: String,
720    },
721    Skipping {
722        column_name: String,
723        options: SkippingIndexOptions,
724    },
725}
726
727impl SetIndexOption {
728    /// Returns the column name of the index option.
729    pub fn column_name(&self) -> &String {
730        match self {
731            SetIndexOption::Fulltext { column_name, .. } => column_name,
732            SetIndexOption::Inverted { column_name } => column_name,
733            SetIndexOption::Skipping { column_name, .. } => column_name,
734        }
735    }
736
737    /// Returns true if the index option is fulltext.
738    pub fn is_fulltext(&self) -> bool {
739        match self {
740            SetIndexOption::Fulltext { .. } => true,
741            SetIndexOption::Inverted { .. } => false,
742            SetIndexOption::Skipping { .. } => false,
743        }
744    }
745}
746
747impl TryFrom<v1::SetIndex> for SetIndexOption {
748    type Error = MetadataError;
749
750    fn try_from(value: v1::SetIndex) -> Result<Self> {
751        let option = value.options.context(InvalidRawRegionRequestSnafu {
752            err: "missing options in SetIndex",
753        })?;
754
755        let opt = match option {
756            v1::set_index::Options::Fulltext(x) => SetIndexOption::Fulltext {
757                column_name: x.column_name.clone(),
758                options: FulltextOptions::new(
759                    x.enable,
760                    as_fulltext_option_analyzer(
761                        Analyzer::try_from(x.analyzer).context(DecodeProtoSnafu)?,
762                    ),
763                    x.case_sensitive,
764                    as_fulltext_option_backend(
765                        PbFulltextBackend::try_from(x.backend).context(DecodeProtoSnafu)?,
766                    ),
767                    x.granularity as u32,
768                    x.false_positive_rate,
769                )
770                .context(InvalidIndexOptionSnafu)?,
771            },
772            v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
773                column_name: i.column_name,
774            },
775            v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
776                column_name: s.column_name,
777                options: SkippingIndexOptions::new(
778                    s.granularity as u32,
779                    s.false_positive_rate,
780                    as_skipping_index_type(
781                        PbSkippingIndexType::try_from(s.skipping_index_type)
782                            .context(DecodeProtoSnafu)?,
783                    ),
784                )
785                .context(InvalidIndexOptionSnafu)?,
786            },
787        };
788
789        Ok(opt)
790    }
791}
792
793#[derive(Debug, PartialEq, Eq, Clone)]
794pub enum UnsetIndexOption {
795    Fulltext { column_name: String },
796    Inverted { column_name: String },
797    Skipping { column_name: String },
798}
799
800impl UnsetIndexOption {
801    pub fn column_name(&self) -> &String {
802        match self {
803            UnsetIndexOption::Fulltext { column_name } => column_name,
804            UnsetIndexOption::Inverted { column_name } => column_name,
805            UnsetIndexOption::Skipping { column_name } => column_name,
806        }
807    }
808
809    pub fn is_fulltext(&self) -> bool {
810        match self {
811            UnsetIndexOption::Fulltext { .. } => true,
812            UnsetIndexOption::Inverted { .. } => false,
813            UnsetIndexOption::Skipping { .. } => false,
814        }
815    }
816}
817
818impl TryFrom<v1::UnsetIndex> for UnsetIndexOption {
819    type Error = MetadataError;
820
821    fn try_from(value: v1::UnsetIndex) -> Result<Self> {
822        let option = value.options.context(InvalidRawRegionRequestSnafu {
823            err: "missing options in UnsetIndex",
824        })?;
825
826        let opt = match option {
827            v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
828                column_name: f.column_name,
829            },
830            v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
831                column_name: i.column_name,
832            },
833            v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
834                column_name: s.column_name,
835            },
836        };
837
838        Ok(opt)
839    }
840}
841
842impl AlterKind {
843    /// Returns an error if the alter kind is invalid.
844    ///
845    /// It allows adding column if not exists and dropping column if exists.
846    pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
847        match self {
848            AlterKind::AddColumns { columns } => {
849                for col_to_add in columns {
850                    col_to_add.validate(metadata)?;
851                }
852            }
853            AlterKind::DropColumns { names } => {
854                for name in names {
855                    Self::validate_column_to_drop(name, metadata)?;
856                }
857            }
858            AlterKind::ModifyColumnTypes { columns } => {
859                for col_to_change in columns {
860                    col_to_change.validate(metadata)?;
861                }
862            }
863            AlterKind::SetRegionOptions { .. } => {}
864            AlterKind::UnsetRegionOptions { .. } => {}
865            AlterKind::SetIndexes { options } => {
866                for option in options {
867                    Self::validate_column_alter_index_option(
868                        option.column_name(),
869                        metadata,
870                        option.is_fulltext(),
871                    )?;
872                }
873            }
874            AlterKind::UnsetIndexes { options } => {
875                for option in options {
876                    Self::validate_column_alter_index_option(
877                        option.column_name(),
878                        metadata,
879                        option.is_fulltext(),
880                    )?;
881                }
882            }
883            AlterKind::DropDefaults { names } => {
884                names
885                    .iter()
886                    .try_for_each(|name| Self::validate_column_existence(name, metadata))?;
887            }
888            AlterKind::SetDefaults { columns } => {
889                columns
890                    .iter()
891                    .try_for_each(|col| Self::validate_column_existence(&col.name, metadata))?;
892            }
893            AlterKind::SyncColumns { column_metadatas } => {
894                let new_primary_keys = column_metadatas
895                    .iter()
896                    .filter(|c| c.semantic_type == SemanticType::Tag)
897                    .map(|c| (c.column_schema.name.as_str(), c.column_id))
898                    .collect::<HashMap<_, _>>();
899
900                let old_primary_keys = metadata
901                    .column_metadatas
902                    .iter()
903                    .filter(|c| c.semantic_type == SemanticType::Tag)
904                    .map(|c| (c.column_schema.name.as_str(), c.column_id));
905
906                for (name, id) in old_primary_keys {
907                    let primary_key =
908                        new_primary_keys
909                            .get(name)
910                            .with_context(|| InvalidRegionRequestSnafu {
911                                region_id: metadata.region_id,
912                                err: format!("column {} is not a primary key", name),
913                            })?;
914
915                    ensure!(
916                        *primary_key == id,
917                        InvalidRegionRequestSnafu {
918                            region_id: metadata.region_id,
919                            err: format!(
920                                "column with same name {} has different id, existing: {}, got: {}",
921                                name, id, primary_key
922                            ),
923                        }
924                    );
925                }
926
927                let new_ts_column = column_metadatas
928                    .iter()
929                    .find(|c| c.semantic_type == SemanticType::Timestamp)
930                    .map(|c| (c.column_schema.name.as_str(), c.column_id))
931                    .context(InvalidRegionRequestSnafu {
932                        region_id: metadata.region_id,
933                        err: "timestamp column not found",
934                    })?;
935
936                // Safety: timestamp column must exist.
937                let old_ts_column = metadata
938                    .column_metadatas
939                    .iter()
940                    .find(|c| c.semantic_type == SemanticType::Timestamp)
941                    .map(|c| (c.column_schema.name.as_str(), c.column_id))
942                    .unwrap();
943
944                ensure!(
945                    new_ts_column == old_ts_column,
946                    InvalidRegionRequestSnafu {
947                        region_id: metadata.region_id,
948                        err: format!(
949                            "timestamp column {} has different id, existing: {}, got: {}",
950                            old_ts_column.0, old_ts_column.1, new_ts_column.1
951                        ),
952                    }
953                );
954            }
955        }
956        Ok(())
957    }
958
959    /// Returns true if we need to apply the alteration to the region.
960    pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
961        debug_assert!(self.validate(metadata).is_ok());
962        match self {
963            AlterKind::AddColumns { columns } => columns
964                .iter()
965                .any(|col_to_add| col_to_add.need_alter(metadata)),
966            AlterKind::DropColumns { names } => names
967                .iter()
968                .any(|name| metadata.column_by_name(name).is_some()),
969            AlterKind::ModifyColumnTypes { columns } => columns
970                .iter()
971                .any(|col_to_change| col_to_change.need_alter(metadata)),
972            AlterKind::SetRegionOptions { .. } => true,
973            AlterKind::UnsetRegionOptions { .. } => true,
974            AlterKind::SetIndexes { options, .. } => options
975                .iter()
976                .any(|option| metadata.column_by_name(option.column_name()).is_some()),
977            AlterKind::UnsetIndexes { options } => options
978                .iter()
979                .any(|option| metadata.column_by_name(option.column_name()).is_some()),
980            AlterKind::DropDefaults { names } => names
981                .iter()
982                .any(|name| metadata.column_by_name(name).is_some()),
983
984            AlterKind::SetDefaults { columns } => columns
985                .iter()
986                .any(|x| metadata.column_by_name(&x.name).is_some()),
987            AlterKind::SyncColumns { column_metadatas } => {
988                metadata.column_metadatas != *column_metadatas
989            }
990        }
991    }
992
993    /// Returns an error if the column to drop is invalid.
994    fn validate_column_to_drop(name: &str, metadata: &RegionMetadata) -> Result<()> {
995        let Some(column) = metadata.column_by_name(name) else {
996            return Ok(());
997        };
998        ensure!(
999            column.semantic_type == SemanticType::Field,
1000            InvalidRegionRequestSnafu {
1001                region_id: metadata.region_id,
1002                err: format!("column {} is not a field and could not be dropped", name),
1003            }
1004        );
1005        Ok(())
1006    }
1007
1008    /// Returns an error if the column's alter index option is invalid.
1009    fn validate_column_alter_index_option(
1010        column_name: &String,
1011        metadata: &RegionMetadata,
1012        is_fulltext: bool,
1013    ) -> Result<()> {
1014        let column = metadata
1015            .column_by_name(column_name)
1016            .context(InvalidRegionRequestSnafu {
1017                region_id: metadata.region_id,
1018                err: format!("column {} not found", column_name),
1019            })?;
1020
1021        if is_fulltext {
1022            ensure!(
1023                column.column_schema.data_type.is_string(),
1024                InvalidRegionRequestSnafu {
1025                    region_id: metadata.region_id,
1026                    err: format!(
1027                        "cannot change alter index options for non-string column {}",
1028                        column_name
1029                    ),
1030                }
1031            );
1032        }
1033
1034        Ok(())
1035    }
1036
1037    /// Returns an error if the column isn't exist.
1038    fn validate_column_existence(column_name: &String, metadata: &RegionMetadata) -> Result<()> {
1039        metadata
1040            .column_by_name(column_name)
1041            .context(InvalidRegionRequestSnafu {
1042                region_id: metadata.region_id,
1043                err: format!("column {} not found", column_name),
1044            })?;
1045
1046        Ok(())
1047    }
1048}
1049
1050impl TryFrom<alter_request::Kind> for AlterKind {
1051    type Error = MetadataError;
1052
1053    fn try_from(kind: alter_request::Kind) -> Result<Self> {
1054        let alter_kind = match kind {
1055            alter_request::Kind::AddColumns(x) => {
1056                let columns = x
1057                    .add_columns
1058                    .into_iter()
1059                    .map(|x| x.try_into())
1060                    .collect::<Result<Vec<_>>>()?;
1061                AlterKind::AddColumns { columns }
1062            }
1063            alter_request::Kind::ModifyColumnTypes(x) => {
1064                let columns = x
1065                    .modify_column_types
1066                    .into_iter()
1067                    .map(|x| x.into())
1068                    .collect::<Vec<_>>();
1069                AlterKind::ModifyColumnTypes { columns }
1070            }
1071            alter_request::Kind::DropColumns(x) => {
1072                let names = x.drop_columns.into_iter().map(|x| x.name).collect();
1073                AlterKind::DropColumns { names }
1074            }
1075            alter_request::Kind::SetTableOptions(options) => AlterKind::SetRegionOptions {
1076                options: options
1077                    .table_options
1078                    .iter()
1079                    .map(TryFrom::try_from)
1080                    .collect::<Result<Vec<_>>>()?,
1081            },
1082            alter_request::Kind::UnsetTableOptions(options) => AlterKind::UnsetRegionOptions {
1083                keys: options
1084                    .keys
1085                    .iter()
1086                    .map(|key| UnsetRegionOption::try_from(key.as_str()))
1087                    .collect::<Result<Vec<_>>>()?,
1088            },
1089            alter_request::Kind::SetIndex(o) => AlterKind::SetIndexes {
1090                options: vec![SetIndexOption::try_from(o)?],
1091            },
1092            alter_request::Kind::UnsetIndex(o) => AlterKind::UnsetIndexes {
1093                options: vec![UnsetIndexOption::try_from(o)?],
1094            },
1095            alter_request::Kind::SetIndexes(o) => AlterKind::SetIndexes {
1096                options: o
1097                    .set_indexes
1098                    .into_iter()
1099                    .map(SetIndexOption::try_from)
1100                    .collect::<Result<Vec<_>>>()?,
1101            },
1102            alter_request::Kind::UnsetIndexes(o) => AlterKind::UnsetIndexes {
1103                options: o
1104                    .unset_indexes
1105                    .into_iter()
1106                    .map(UnsetIndexOption::try_from)
1107                    .collect::<Result<Vec<_>>>()?,
1108            },
1109            alter_request::Kind::DropDefaults(x) => AlterKind::DropDefaults {
1110                names: x.drop_defaults.into_iter().map(|x| x.column_name).collect(),
1111            },
1112            alter_request::Kind::SetDefaults(x) => AlterKind::SetDefaults {
1113                columns: x
1114                    .set_defaults
1115                    .into_iter()
1116                    .map(|x| {
1117                        Ok(SetDefault {
1118                            name: x.column_name,
1119                            default_constraint: x.default_constraint.clone(),
1120                        })
1121                    })
1122                    .collect::<Result<Vec<_>>>()?,
1123            },
1124            alter_request::Kind::SyncColumns(x) => AlterKind::SyncColumns {
1125                column_metadatas: x
1126                    .column_defs
1127                    .into_iter()
1128                    .map(ColumnMetadata::try_from_column_def)
1129                    .collect::<Result<Vec<_>>>()?,
1130            },
1131        };
1132
1133        Ok(alter_kind)
1134    }
1135}
1136
1137/// Adds a column.
1138#[derive(Debug, PartialEq, Eq, Clone)]
1139pub struct AddColumn {
1140    /// Metadata of the column to add.
1141    pub column_metadata: ColumnMetadata,
1142    /// Location to add the column. If location is None, the region adds
1143    /// the column to the last.
1144    pub location: Option<AddColumnLocation>,
1145}
1146
1147impl AddColumn {
1148    /// Returns an error if the column to add is invalid.
1149    ///
1150    /// It allows adding existing columns. However, the existing column must have the same metadata
1151    /// and the location must be None.
1152    pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1153        ensure!(
1154            self.column_metadata.column_schema.is_nullable()
1155                || self
1156                    .column_metadata
1157                    .column_schema
1158                    .default_constraint()
1159                    .is_some(),
1160            InvalidRegionRequestSnafu {
1161                region_id: metadata.region_id,
1162                err: format!(
1163                    "no default value for column {}",
1164                    self.column_metadata.column_schema.name
1165                ),
1166            }
1167        );
1168
1169        if let Some(existing_column) =
1170            metadata.column_by_name(&self.column_metadata.column_schema.name)
1171        {
1172            // If the column already exists.
1173            ensure!(
1174                *existing_column == self.column_metadata,
1175                InvalidRegionRequestSnafu {
1176                    region_id: metadata.region_id,
1177                    err: format!(
1178                        "column {} already exists with different metadata, existing: {:?}, got: {:?}",
1179                        self.column_metadata.column_schema.name,
1180                        existing_column,
1181                        self.column_metadata,
1182                    ),
1183                }
1184            );
1185            ensure!(
1186                self.location.is_none(),
1187                InvalidRegionRequestSnafu {
1188                    region_id: metadata.region_id,
1189                    err: format!(
1190                        "column {} already exists, but location is specified",
1191                        self.column_metadata.column_schema.name
1192                    ),
1193                }
1194            );
1195        }
1196
1197        if let Some(existing_column) = metadata.column_by_id(self.column_metadata.column_id) {
1198            // Ensures the existing column has the same name.
1199            ensure!(
1200                existing_column.column_schema.name == self.column_metadata.column_schema.name,
1201                InvalidRegionRequestSnafu {
1202                    region_id: metadata.region_id,
1203                    err: format!(
1204                        "column id {} already exists with different name {}",
1205                        self.column_metadata.column_id, existing_column.column_schema.name
1206                    ),
1207                }
1208            );
1209        }
1210
1211        Ok(())
1212    }
1213
1214    /// Returns true if no column to add to the region.
1215    pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1216        debug_assert!(self.validate(metadata).is_ok());
1217        metadata
1218            .column_by_name(&self.column_metadata.column_schema.name)
1219            .is_none()
1220    }
1221}
1222
1223impl TryFrom<v1::region::AddColumn> for AddColumn {
1224    type Error = MetadataError;
1225
1226    fn try_from(add_column: v1::region::AddColumn) -> Result<Self> {
1227        let column_def = add_column
1228            .column_def
1229            .context(InvalidRawRegionRequestSnafu {
1230                err: "missing column_def in AddColumn",
1231            })?;
1232
1233        let column_metadata = ColumnMetadata::try_from_column_def(column_def)?;
1234        let location = add_column
1235            .location
1236            .map(AddColumnLocation::try_from)
1237            .transpose()?;
1238
1239        Ok(AddColumn {
1240            column_metadata,
1241            location,
1242        })
1243    }
1244}
1245
1246/// Location to add a column.
1247#[derive(Debug, PartialEq, Eq, Clone)]
1248pub enum AddColumnLocation {
1249    /// Add the column to the first position of columns.
1250    First,
1251    /// Add the column after specific column.
1252    After {
1253        /// Add the column after this column.
1254        column_name: String,
1255    },
1256}
1257
1258impl TryFrom<v1::AddColumnLocation> for AddColumnLocation {
1259    type Error = MetadataError;
1260
1261    fn try_from(location: v1::AddColumnLocation) -> Result<Self> {
1262        let location_type = LocationType::try_from(location.location_type)
1263            .map_err(|e| InvalidRawRegionRequestSnafu { err: e.to_string() }.build())?;
1264        let add_column_location = match location_type {
1265            LocationType::First => AddColumnLocation::First,
1266            LocationType::After => AddColumnLocation::After {
1267                column_name: location.after_column_name,
1268            },
1269        };
1270
1271        Ok(add_column_location)
1272    }
1273}
1274
1275/// Change a column's datatype.
1276#[derive(Debug, PartialEq, Eq, Clone)]
1277pub struct ModifyColumnType {
1278    /// Schema of the column to modify.
1279    pub column_name: String,
1280    /// Column will be changed to this type.
1281    pub target_type: ConcreteDataType,
1282}
1283
1284impl ModifyColumnType {
1285    /// Returns an error if the column's datatype to change is invalid.
1286    pub fn validate(&self, metadata: &RegionMetadata) -> Result<()> {
1287        let column_meta = metadata
1288            .column_by_name(&self.column_name)
1289            .with_context(|| InvalidRegionRequestSnafu {
1290                region_id: metadata.region_id,
1291                err: format!("column {} not found", self.column_name),
1292            })?;
1293
1294        ensure!(
1295            matches!(column_meta.semantic_type, SemanticType::Field),
1296            InvalidRegionRequestSnafu {
1297                region_id: metadata.region_id,
1298                err: "'timestamp' or 'tag' column cannot change type".to_string()
1299            }
1300        );
1301        ensure!(
1302            column_meta
1303                .column_schema
1304                .data_type
1305                .can_arrow_type_cast_to(&self.target_type),
1306            InvalidRegionRequestSnafu {
1307                region_id: metadata.region_id,
1308                err: format!(
1309                    "column '{}' cannot be cast automatically to type '{}'",
1310                    self.column_name, self.target_type
1311                ),
1312            }
1313        );
1314
1315        Ok(())
1316    }
1317
1318    /// Returns true if no column's datatype to change to the region.
1319    pub fn need_alter(&self, metadata: &RegionMetadata) -> bool {
1320        debug_assert!(self.validate(metadata).is_ok());
1321        metadata.column_by_name(&self.column_name).is_some()
1322    }
1323}
1324
1325impl From<v1::ModifyColumnType> for ModifyColumnType {
1326    fn from(modify_column_type: v1::ModifyColumnType) -> Self {
1327        let target_type = ColumnDataTypeWrapper::new(
1328            modify_column_type.target_type(),
1329            modify_column_type.target_type_extension,
1330        )
1331        .into();
1332
1333        ModifyColumnType {
1334            column_name: modify_column_type.column_name,
1335            target_type,
1336        }
1337    }
1338}
1339
1340#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1341pub enum SetRegionOption {
1342    Ttl(Option<TimeToLive>),
1343    // Modifying TwscOptions with values as (option name, new value).
1344    Twsc(String, String),
1345    // Modifying the SST format.
1346    Format(String),
1347    // Modifying the append mode.
1348    AppendMode(bool),
1349}
1350
1351impl TryFrom<&PbOption> for SetRegionOption {
1352    type Error = MetadataError;
1353
1354    fn try_from(value: &PbOption) -> std::result::Result<Self, Self::Error> {
1355        let PbOption { key, value } = value;
1356        match key.as_str() {
1357            TTL_KEY => {
1358                let ttl = TimeToLive::from_humantime_or_str(value)
1359                    .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1360
1361                Ok(Self::Ttl(Some(ttl)))
1362            }
1363            TWCS_TRIGGER_FILE_NUM | TWCS_MAX_OUTPUT_FILE_SIZE | TWCS_TIME_WINDOW => {
1364                Ok(Self::Twsc(key.clone(), value.clone()))
1365            }
1366            SST_FORMAT_KEY => Ok(Self::Format(value.clone())),
1367            APPEND_MODE_KEY => {
1368                let append_mode = value
1369                    .parse::<bool>()
1370                    .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?;
1371                Ok(Self::AppendMode(append_mode))
1372            }
1373            _ => InvalidSetRegionOptionRequestSnafu { key, value }.fail(),
1374        }
1375    }
1376}
1377
1378impl From<&UnsetRegionOption> for SetRegionOption {
1379    fn from(unset_option: &UnsetRegionOption) -> Self {
1380        match unset_option {
1381            UnsetRegionOption::TwcsTriggerFileNum => {
1382                SetRegionOption::Twsc(unset_option.to_string(), String::new())
1383            }
1384            UnsetRegionOption::TwcsMaxOutputFileSize => {
1385                SetRegionOption::Twsc(unset_option.to_string(), String::new())
1386            }
1387            UnsetRegionOption::TwcsTimeWindow => {
1388                SetRegionOption::Twsc(unset_option.to_string(), String::new())
1389            }
1390            UnsetRegionOption::Ttl => SetRegionOption::Ttl(Default::default()),
1391        }
1392    }
1393}
1394
1395impl TryFrom<&str> for UnsetRegionOption {
1396    type Error = MetadataError;
1397
1398    fn try_from(key: &str) -> Result<Self> {
1399        match key.to_ascii_lowercase().as_str() {
1400            TTL_KEY => Ok(Self::Ttl),
1401            TWCS_TRIGGER_FILE_NUM => Ok(Self::TwcsTriggerFileNum),
1402            TWCS_MAX_OUTPUT_FILE_SIZE => Ok(Self::TwcsMaxOutputFileSize),
1403            TWCS_TIME_WINDOW => Ok(Self::TwcsTimeWindow),
1404            _ => InvalidUnsetRegionOptionRequestSnafu { key }.fail(),
1405        }
1406    }
1407}
1408
1409#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
1410pub enum UnsetRegionOption {
1411    TwcsTriggerFileNum,
1412    TwcsMaxOutputFileSize,
1413    TwcsTimeWindow,
1414    Ttl,
1415}
1416
1417impl UnsetRegionOption {
1418    pub fn as_str(&self) -> &str {
1419        match self {
1420            Self::Ttl => TTL_KEY,
1421            Self::TwcsTriggerFileNum => TWCS_TRIGGER_FILE_NUM,
1422            Self::TwcsMaxOutputFileSize => TWCS_MAX_OUTPUT_FILE_SIZE,
1423            Self::TwcsTimeWindow => TWCS_TIME_WINDOW,
1424        }
1425    }
1426}
1427
1428impl Display for UnsetRegionOption {
1429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1430        write!(f, "{}", self.as_str())
1431    }
1432}
1433
1434#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1435pub enum RegionFlushReason {
1436    /// Flush triggered before region migration.
1437    RegionMigration,
1438    /// Flush triggered by repartition procedure.
1439    Repartition,
1440    /// Flush triggered by remote WAL pruning.
1441    RemoteWalPrune,
1442    /// Flush region before closing region.
1443    Closing,
1444    /// Flush region before downgrading region.
1445    Downgrading,
1446}
1447
1448#[derive(Debug, Clone, Default)]
1449pub struct RegionFlushRequest {
1450    pub row_group_size: Option<usize>,
1451    pub reason: Option<RegionFlushReason>,
1452}
1453
1454#[derive(Debug)]
1455pub struct RegionCompactRequest {
1456    pub options: compact_request::Options,
1457    pub parallelism: Option<u32>,
1458}
1459
1460impl Default for RegionCompactRequest {
1461    fn default() -> Self {
1462        Self {
1463            // Default to regular compaction.
1464            options: compact_request::Options::Regular(Default::default()),
1465            parallelism: None,
1466        }
1467    }
1468}
1469
1470#[derive(Debug, Clone, Default)]
1471pub struct RegionBuildIndexRequest {}
1472
1473/// Truncate region request.
1474#[derive(Debug)]
1475pub enum RegionTruncateRequest {
1476    /// Truncate all data in the region.
1477    All,
1478    ByTimeRanges {
1479        /// Time ranges to truncate. Both bound are inclusive.
1480        /// only files that are fully contained in the time range will be truncated.
1481        /// so no guarantee that all data in the time range will be truncated.
1482        time_ranges: Vec<(Timestamp, Timestamp)>,
1483    },
1484}
1485
1486/// Catchup region request.
1487///
1488/// Makes a readonly region to catch up to leader region changes.
1489/// There is no effect if it operating on a leader region.
1490#[derive(Debug, Clone, Copy, Default)]
1491pub struct RegionCatchupRequest {
1492    /// Sets it to writable if it's available after it has caught up with all changes.
1493    pub set_writable: bool,
1494    /// The `entry_id` that was expected to reply to.
1495    /// `None` stands replaying to latest.
1496    pub entry_id: Option<entry::Id>,
1497    /// Used for metrics metadata region.
1498    /// The `entry_id` that was expected to reply to.
1499    /// `None` stands replaying to latest.
1500    pub metadata_entry_id: Option<entry::Id>,
1501    /// The hint for replaying memtable.
1502    pub location_id: Option<u64>,
1503    /// Replay checkpoint.
1504    pub checkpoint: Option<ReplayCheckpoint>,
1505}
1506
1507#[derive(Debug, Clone)]
1508pub struct RegionBulkInsertsRequest {
1509    pub region_id: RegionId,
1510    pub payload: DfRecordBatch,
1511    pub raw_data: ArrowIpc,
1512    pub partition_expr_version: Option<u64>,
1513}
1514
1515impl RegionBulkInsertsRequest {
1516    pub fn estimated_size(&self) -> usize {
1517        self.payload.get_array_memory_size()
1518    }
1519}
1520
1521/// Request to stage a region with a new partition directive.
1522///
1523/// This request transitions a region into the staging mode.
1524/// It first flushes the memtable for the old partition expression if it is not
1525/// empty, then enters the staging mode with the new directive.
1526#[derive(Debug, Clone, PartialEq, Eq)]
1527pub enum StagingPartitionDirective {
1528    UpdatePartitionExpr(String),
1529    RejectAllWrites,
1530}
1531
1532impl StagingPartitionDirective {
1533    /// Returns the partition expression carried by this directive, if any.
1534    pub fn partition_expr(&self) -> Option<&str> {
1535        match self {
1536            Self::UpdatePartitionExpr(expr) => Some(expr),
1537            Self::RejectAllWrites => None,
1538        }
1539    }
1540}
1541
1542#[derive(Debug, Clone)]
1543pub struct EnterStagingRequest {
1544    /// The staging partition directive of the region.
1545    pub partition_directive: StagingPartitionDirective,
1546}
1547
1548impl EnterStagingRequest {
1549    /// Builds an enter-staging request with a partition expression directive.
1550    pub fn with_partition_expr(partition_expr: String) -> Self {
1551        Self {
1552            partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
1553        }
1554    }
1555}
1556
1557/// This request is used as part of the region repartition.
1558///
1559/// After a region has entered staging mode with a new partition expression
1560/// expression) and a separate process (for example, `remap_manifests`) has
1561/// generated the new file assignments for the staging region, this request
1562/// applies that generated manifest to the region.
1563///
1564/// In practice, this means:
1565/// - The `partition_expr` identifies the staging partition expression that the manifest
1566///   was generated for.
1567/// - `central_region_id` specifies which region holds the staging blob storage
1568///   where the manifest was written during the `remap_manifests` operation.
1569/// - `manifest_path` is the relative path within the central region's staging
1570///   blob storage to fetch the generated manifest.
1571///
1572/// It should typically be called **after** the staging region has been
1573/// initialized by [`EnterStagingRequest`] and the new file layout has been
1574/// computed, to finalize the repartition operation.
1575#[derive(Debug, Clone)]
1576pub struct ApplyStagingManifestRequest {
1577    /// The partition expression of the staging region.
1578    pub partition_expr: String,
1579    /// The region that stores the staging manifests in its staging blob storage.
1580    pub central_region_id: RegionId,
1581    /// The relative path to the staging manifest within the central region's
1582    /// staging blob storage.
1583    pub manifest_path: String,
1584}
1585
1586impl fmt::Display for RegionRequest {
1587    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1588        match self {
1589            RegionRequest::Put(_) => write!(f, "Put"),
1590            RegionRequest::Delete(_) => write!(f, "Delete"),
1591            RegionRequest::Create(_) => write!(f, "Create"),
1592            RegionRequest::Drop(_) => write!(f, "Drop"),
1593            RegionRequest::Open(_) => write!(f, "Open"),
1594            RegionRequest::Close(_) => write!(f, "Close"),
1595            RegionRequest::Alter(_) => write!(f, "Alter"),
1596            RegionRequest::Flush(_) => write!(f, "Flush"),
1597            RegionRequest::Compact(_) => write!(f, "Compact"),
1598            RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
1599            RegionRequest::Truncate(_) => write!(f, "Truncate"),
1600            RegionRequest::Catchup(_) => write!(f, "Catchup"),
1601            RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
1602            RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
1603            RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
1604        }
1605    }
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610
1611    use api::v1::region::RegionColumnDef;
1612    use api::v1::{ColumnDataType, ColumnDef};
1613    use datatypes::prelude::ConcreteDataType;
1614    use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
1615
1616    use super::*;
1617    use crate::metadata::RegionMetadataBuilder;
1618
1619    #[test]
1620    fn test_from_proto_location() {
1621        let proto_location = v1::AddColumnLocation {
1622            location_type: LocationType::First as i32,
1623            after_column_name: String::default(),
1624        };
1625        let location = AddColumnLocation::try_from(proto_location).unwrap();
1626        assert_eq!(location, AddColumnLocation::First);
1627
1628        let proto_location = v1::AddColumnLocation {
1629            location_type: 10,
1630            after_column_name: String::default(),
1631        };
1632        AddColumnLocation::try_from(proto_location).unwrap_err();
1633
1634        let proto_location = v1::AddColumnLocation {
1635            location_type: LocationType::After as i32,
1636            after_column_name: "a".to_string(),
1637        };
1638        let location = AddColumnLocation::try_from(proto_location).unwrap();
1639        assert_eq!(
1640            location,
1641            AddColumnLocation::After {
1642                column_name: "a".to_string()
1643            }
1644        );
1645    }
1646
1647    #[test]
1648    fn test_from_none_proto_add_column() {
1649        AddColumn::try_from(v1::region::AddColumn {
1650            column_def: None,
1651            location: None,
1652        })
1653        .unwrap_err();
1654    }
1655
1656    #[test]
1657    fn test_from_proto_alter_request() {
1658        RegionAlterRequest::try_from(AlterRequest {
1659            region_id: 0,
1660            schema_version: 1,
1661            kind: None,
1662        })
1663        .unwrap_err();
1664
1665        let request = RegionAlterRequest::try_from(AlterRequest {
1666            region_id: 0,
1667            schema_version: 1,
1668            kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
1669                add_columns: vec![v1::region::AddColumn {
1670                    column_def: Some(RegionColumnDef {
1671                        column_def: Some(ColumnDef {
1672                            name: "a".to_string(),
1673                            data_type: ColumnDataType::String as i32,
1674                            is_nullable: true,
1675                            default_constraint: vec![],
1676                            semantic_type: SemanticType::Field as i32,
1677                            comment: String::new(),
1678                            ..Default::default()
1679                        }),
1680                        column_id: 1,
1681                    }),
1682                    location: Some(v1::AddColumnLocation {
1683                        location_type: LocationType::First as i32,
1684                        after_column_name: String::default(),
1685                    }),
1686                }],
1687            })),
1688        })
1689        .unwrap();
1690
1691        assert_eq!(
1692            request,
1693            RegionAlterRequest {
1694                kind: AlterKind::AddColumns {
1695                    columns: vec![AddColumn {
1696                        column_metadata: ColumnMetadata {
1697                            column_schema: ColumnSchema::new(
1698                                "a",
1699                                ConcreteDataType::string_datatype(),
1700                                true,
1701                            ),
1702                            semantic_type: SemanticType::Field,
1703                            column_id: 1,
1704                        },
1705                        location: Some(AddColumnLocation::First),
1706                    }]
1707                },
1708            }
1709        );
1710    }
1711
1712    /// Returns a new region metadata for testing. Metadata:
1713    /// `[(ts, ms, 1), (tag_0, string, 2), (field_0, string, 3), (field_1, bool, 4)]`
1714    fn new_metadata() -> RegionMetadata {
1715        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1716        builder
1717            .push_column_metadata(ColumnMetadata {
1718                column_schema: ColumnSchema::new(
1719                    "ts",
1720                    ConcreteDataType::timestamp_millisecond_datatype(),
1721                    false,
1722                ),
1723                semantic_type: SemanticType::Timestamp,
1724                column_id: 1,
1725            })
1726            .push_column_metadata(ColumnMetadata {
1727                column_schema: ColumnSchema::new(
1728                    "tag_0",
1729                    ConcreteDataType::string_datatype(),
1730                    true,
1731                ),
1732                semantic_type: SemanticType::Tag,
1733                column_id: 2,
1734            })
1735            .push_column_metadata(ColumnMetadata {
1736                column_schema: ColumnSchema::new(
1737                    "field_0",
1738                    ConcreteDataType::string_datatype(),
1739                    true,
1740                ),
1741                semantic_type: SemanticType::Field,
1742                column_id: 3,
1743            })
1744            .push_column_metadata(ColumnMetadata {
1745                column_schema: ColumnSchema::new(
1746                    "field_1",
1747                    ConcreteDataType::boolean_datatype(),
1748                    true,
1749                ),
1750                semantic_type: SemanticType::Field,
1751                column_id: 4,
1752            })
1753            .primary_key(vec![2]);
1754        builder.build().unwrap()
1755    }
1756
1757    #[test]
1758    fn test_add_column_validate() {
1759        let metadata = new_metadata();
1760        let add_column = AddColumn {
1761            column_metadata: ColumnMetadata {
1762                column_schema: ColumnSchema::new(
1763                    "tag_1",
1764                    ConcreteDataType::string_datatype(),
1765                    true,
1766                ),
1767                semantic_type: SemanticType::Tag,
1768                column_id: 5,
1769            },
1770            location: None,
1771        };
1772        add_column.validate(&metadata).unwrap();
1773        assert!(add_column.need_alter(&metadata));
1774
1775        // Add not null column.
1776        AddColumn {
1777            column_metadata: ColumnMetadata {
1778                column_schema: ColumnSchema::new(
1779                    "tag_1",
1780                    ConcreteDataType::string_datatype(),
1781                    false,
1782                ),
1783                semantic_type: SemanticType::Tag,
1784                column_id: 5,
1785            },
1786            location: None,
1787        }
1788        .validate(&metadata)
1789        .unwrap_err();
1790
1791        // Add existing column.
1792        let add_column = AddColumn {
1793            column_metadata: ColumnMetadata {
1794                column_schema: ColumnSchema::new(
1795                    "tag_0",
1796                    ConcreteDataType::string_datatype(),
1797                    true,
1798                ),
1799                semantic_type: SemanticType::Tag,
1800                column_id: 2,
1801            },
1802            location: None,
1803        };
1804        add_column.validate(&metadata).unwrap();
1805        assert!(!add_column.need_alter(&metadata));
1806    }
1807
1808    #[test]
1809    fn test_add_duplicate_columns() {
1810        let kind = AlterKind::AddColumns {
1811            columns: vec![
1812                AddColumn {
1813                    column_metadata: ColumnMetadata {
1814                        column_schema: ColumnSchema::new(
1815                            "tag_1",
1816                            ConcreteDataType::string_datatype(),
1817                            true,
1818                        ),
1819                        semantic_type: SemanticType::Tag,
1820                        column_id: 5,
1821                    },
1822                    location: None,
1823                },
1824                AddColumn {
1825                    column_metadata: ColumnMetadata {
1826                        column_schema: ColumnSchema::new(
1827                            "tag_1",
1828                            ConcreteDataType::string_datatype(),
1829                            true,
1830                        ),
1831                        semantic_type: SemanticType::Field,
1832                        column_id: 6,
1833                    },
1834                    location: None,
1835                },
1836            ],
1837        };
1838        let metadata = new_metadata();
1839        kind.validate(&metadata).unwrap();
1840        assert!(kind.need_alter(&metadata));
1841    }
1842
1843    #[test]
1844    fn test_add_existing_column_different_metadata() {
1845        let metadata = new_metadata();
1846
1847        // Add existing column with different id.
1848        let kind = AlterKind::AddColumns {
1849            columns: vec![AddColumn {
1850                column_metadata: ColumnMetadata {
1851                    column_schema: ColumnSchema::new(
1852                        "tag_0",
1853                        ConcreteDataType::string_datatype(),
1854                        true,
1855                    ),
1856                    semantic_type: SemanticType::Tag,
1857                    column_id: 4,
1858                },
1859                location: None,
1860            }],
1861        };
1862        kind.validate(&metadata).unwrap_err();
1863
1864        // Add existing column with different type.
1865        let kind = AlterKind::AddColumns {
1866            columns: vec![AddColumn {
1867                column_metadata: ColumnMetadata {
1868                    column_schema: ColumnSchema::new(
1869                        "tag_0",
1870                        ConcreteDataType::int64_datatype(),
1871                        true,
1872                    ),
1873                    semantic_type: SemanticType::Tag,
1874                    column_id: 2,
1875                },
1876                location: None,
1877            }],
1878        };
1879        kind.validate(&metadata).unwrap_err();
1880
1881        // Add existing column with different name.
1882        let kind = AlterKind::AddColumns {
1883            columns: vec![AddColumn {
1884                column_metadata: ColumnMetadata {
1885                    column_schema: ColumnSchema::new(
1886                        "tag_1",
1887                        ConcreteDataType::string_datatype(),
1888                        true,
1889                    ),
1890                    semantic_type: SemanticType::Tag,
1891                    column_id: 2,
1892                },
1893                location: None,
1894            }],
1895        };
1896        kind.validate(&metadata).unwrap_err();
1897    }
1898
1899    #[test]
1900    fn test_add_existing_column_with_location() {
1901        let metadata = new_metadata();
1902        let kind = AlterKind::AddColumns {
1903            columns: vec![AddColumn {
1904                column_metadata: ColumnMetadata {
1905                    column_schema: ColumnSchema::new(
1906                        "tag_0",
1907                        ConcreteDataType::string_datatype(),
1908                        true,
1909                    ),
1910                    semantic_type: SemanticType::Tag,
1911                    column_id: 2,
1912                },
1913                location: Some(AddColumnLocation::First),
1914            }],
1915        };
1916        kind.validate(&metadata).unwrap_err();
1917    }
1918
1919    #[test]
1920    fn test_validate_drop_column() {
1921        let metadata = new_metadata();
1922        let kind = AlterKind::DropColumns {
1923            names: vec!["xxxx".to_string()],
1924        };
1925        kind.validate(&metadata).unwrap();
1926        assert!(!kind.need_alter(&metadata));
1927
1928        AlterKind::DropColumns {
1929            names: vec!["tag_0".to_string()],
1930        }
1931        .validate(&metadata)
1932        .unwrap_err();
1933
1934        let kind = AlterKind::DropColumns {
1935            names: vec!["field_0".to_string()],
1936        };
1937        kind.validate(&metadata).unwrap();
1938        assert!(kind.need_alter(&metadata));
1939    }
1940
1941    #[test]
1942    fn test_validate_modify_column_type() {
1943        let metadata = new_metadata();
1944        AlterKind::ModifyColumnTypes {
1945            columns: vec![ModifyColumnType {
1946                column_name: "xxxx".to_string(),
1947                target_type: ConcreteDataType::string_datatype(),
1948            }],
1949        }
1950        .validate(&metadata)
1951        .unwrap_err();
1952
1953        AlterKind::ModifyColumnTypes {
1954            columns: vec![ModifyColumnType {
1955                column_name: "field_1".to_string(),
1956                target_type: ConcreteDataType::date_datatype(),
1957            }],
1958        }
1959        .validate(&metadata)
1960        .unwrap_err();
1961
1962        AlterKind::ModifyColumnTypes {
1963            columns: vec![ModifyColumnType {
1964                column_name: "ts".to_string(),
1965                target_type: ConcreteDataType::date_datatype(),
1966            }],
1967        }
1968        .validate(&metadata)
1969        .unwrap_err();
1970
1971        AlterKind::ModifyColumnTypes {
1972            columns: vec![ModifyColumnType {
1973                column_name: "tag_0".to_string(),
1974                target_type: ConcreteDataType::date_datatype(),
1975            }],
1976        }
1977        .validate(&metadata)
1978        .unwrap_err();
1979
1980        let kind = AlterKind::ModifyColumnTypes {
1981            columns: vec![ModifyColumnType {
1982                column_name: "field_0".to_string(),
1983                target_type: ConcreteDataType::int32_datatype(),
1984            }],
1985        };
1986        kind.validate(&metadata).unwrap();
1987        assert!(kind.need_alter(&metadata));
1988    }
1989
1990    #[test]
1991    fn test_validate_add_columns() {
1992        let kind = AlterKind::AddColumns {
1993            columns: vec![
1994                AddColumn {
1995                    column_metadata: ColumnMetadata {
1996                        column_schema: ColumnSchema::new(
1997                            "tag_1",
1998                            ConcreteDataType::string_datatype(),
1999                            true,
2000                        ),
2001                        semantic_type: SemanticType::Tag,
2002                        column_id: 5,
2003                    },
2004                    location: None,
2005                },
2006                AddColumn {
2007                    column_metadata: ColumnMetadata {
2008                        column_schema: ColumnSchema::new(
2009                            "field_2",
2010                            ConcreteDataType::string_datatype(),
2011                            true,
2012                        ),
2013                        semantic_type: SemanticType::Field,
2014                        column_id: 6,
2015                    },
2016                    location: None,
2017                },
2018            ],
2019        };
2020        let request = RegionAlterRequest { kind };
2021        let mut metadata = new_metadata();
2022        metadata.schema_version = 1;
2023        request.validate(&metadata).unwrap();
2024    }
2025
2026    #[test]
2027    fn test_validate_create_region() {
2028        let column_metadatas = vec![
2029            ColumnMetadata {
2030                column_schema: ColumnSchema::new(
2031                    "ts",
2032                    ConcreteDataType::timestamp_millisecond_datatype(),
2033                    false,
2034                ),
2035                semantic_type: SemanticType::Timestamp,
2036                column_id: 1,
2037            },
2038            ColumnMetadata {
2039                column_schema: ColumnSchema::new(
2040                    "tag_0",
2041                    ConcreteDataType::string_datatype(),
2042                    true,
2043                ),
2044                semantic_type: SemanticType::Tag,
2045                column_id: 2,
2046            },
2047            ColumnMetadata {
2048                column_schema: ColumnSchema::new(
2049                    "field_0",
2050                    ConcreteDataType::string_datatype(),
2051                    true,
2052                ),
2053                semantic_type: SemanticType::Field,
2054                column_id: 3,
2055            },
2056        ];
2057        let create = RegionCreateRequest {
2058            engine: "mito".to_string(),
2059            column_metadatas,
2060            primary_key: vec![3, 4],
2061            options: HashMap::new(),
2062            table_dir: "path".to_string(),
2063            path_type: PathType::Bare,
2064            partition_expr_json: Some("".to_string()),
2065        };
2066
2067        assert!(create.validate().is_err());
2068    }
2069
2070    #[test]
2071    fn test_validate_modify_column_fulltext_options() {
2072        let kind = AlterKind::SetIndexes {
2073            options: vec![SetIndexOption::Fulltext {
2074                column_name: "tag_0".to_string(),
2075                options: FulltextOptions::new_unchecked(
2076                    true,
2077                    FulltextAnalyzer::Chinese,
2078                    false,
2079                    FulltextBackend::Bloom,
2080                    1000,
2081                    0.01,
2082                ),
2083            }],
2084        };
2085        let request = RegionAlterRequest { kind };
2086        let mut metadata = new_metadata();
2087        metadata.schema_version = 1;
2088        request.validate(&metadata).unwrap();
2089
2090        let kind = AlterKind::UnsetIndexes {
2091            options: vec![UnsetIndexOption::Fulltext {
2092                column_name: "tag_0".to_string(),
2093            }],
2094        };
2095        let request = RegionAlterRequest { kind };
2096        let mut metadata = new_metadata();
2097        metadata.schema_version = 1;
2098        request.validate(&metadata).unwrap();
2099    }
2100
2101    #[test]
2102    fn test_validate_sync_columns() {
2103        let metadata = new_metadata();
2104        let kind = AlterKind::SyncColumns {
2105            column_metadatas: vec![
2106                ColumnMetadata {
2107                    column_schema: ColumnSchema::new(
2108                        "tag_1",
2109                        ConcreteDataType::string_datatype(),
2110                        true,
2111                    ),
2112                    semantic_type: SemanticType::Tag,
2113                    column_id: 5,
2114                },
2115                ColumnMetadata {
2116                    column_schema: ColumnSchema::new(
2117                        "field_2",
2118                        ConcreteDataType::string_datatype(),
2119                        true,
2120                    ),
2121                    semantic_type: SemanticType::Field,
2122                    column_id: 6,
2123                },
2124            ],
2125        };
2126        let err = kind.validate(&metadata).unwrap_err();
2127        assert!(err.to_string().contains("not a primary key"));
2128
2129        // Change the timestamp column name.
2130        let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
2131        let ts_column = column_metadatas_with_different_ts_column
2132            .iter_mut()
2133            .find(|c| c.semantic_type == SemanticType::Timestamp)
2134            .unwrap();
2135        ts_column.column_schema.name = "ts1".to_string();
2136
2137        let kind = AlterKind::SyncColumns {
2138            column_metadatas: column_metadatas_with_different_ts_column,
2139        };
2140        let err = kind.validate(&metadata).unwrap_err();
2141        assert!(
2142            err.to_string()
2143                .contains("timestamp column ts has different id")
2144        );
2145
2146        // Change the primary key column name.
2147        let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
2148        let pk_column = column_metadatas_with_different_pk_column
2149            .iter_mut()
2150            .find(|c| c.column_schema.name == "tag_0")
2151            .unwrap();
2152        pk_column.column_id = 100;
2153        let kind = AlterKind::SyncColumns {
2154            column_metadatas: column_metadatas_with_different_pk_column,
2155        };
2156        let err = kind.validate(&metadata).unwrap_err();
2157        assert!(
2158            err.to_string()
2159                .contains("column with same name tag_0 has different id")
2160        );
2161
2162        // Add a new field column.
2163        let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
2164        column_metadatas_with_new_field_column.push(ColumnMetadata {
2165            column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
2166            semantic_type: SemanticType::Field,
2167            column_id: 4,
2168        });
2169        let kind = AlterKind::SyncColumns {
2170            column_metadatas: column_metadatas_with_new_field_column,
2171        };
2172        kind.validate(&metadata).unwrap();
2173    }
2174
2175    #[test]
2176    fn test_cast_path_type_to_primitive() {
2177        assert_eq!(PathType::Bare as u8, 0);
2178        assert_eq!(PathType::Data as u8, 1);
2179        assert_eq!(PathType::Metadata as u8, 2);
2180        assert_eq!(
2181            PathType::try_from(PathType::Bare as u8).unwrap(),
2182            PathType::Bare
2183        );
2184        assert_eq!(
2185            PathType::try_from(PathType::Data as u8).unwrap(),
2186            PathType::Data
2187        );
2188        assert_eq!(
2189            PathType::try_from(PathType::Metadata as u8).unwrap(),
2190            PathType::Metadata
2191        );
2192    }
2193}