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