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}
1418
1419#[derive(Debug, Clone, Default)]
1420pub struct RegionFlushRequest {
1421    pub row_group_size: Option<usize>,
1422    pub reason: Option<RegionFlushReason>,
1423}
1424
1425#[derive(Debug)]
1426pub struct RegionCompactRequest {
1427    pub options: compact_request::Options,
1428    pub parallelism: Option<u32>,
1429}
1430
1431impl Default for RegionCompactRequest {
1432    fn default() -> Self {
1433        Self {
1434            // Default to regular compaction.
1435            options: compact_request::Options::Regular(Default::default()),
1436            parallelism: None,
1437        }
1438    }
1439}
1440
1441#[derive(Debug, Clone, Default)]
1442pub struct RegionBuildIndexRequest {}
1443
1444/// Truncate region request.
1445#[derive(Debug)]
1446pub enum RegionTruncateRequest {
1447    /// Truncate all data in the region.
1448    All,
1449    ByTimeRanges {
1450        /// Time ranges to truncate. Both bound are inclusive.
1451        /// only files that are fully contained in the time range will be truncated.
1452        /// so no guarantee that all data in the time range will be truncated.
1453        time_ranges: Vec<(Timestamp, Timestamp)>,
1454    },
1455}
1456
1457/// Catchup region request.
1458///
1459/// Makes a readonly region to catch up to leader region changes.
1460/// There is no effect if it operating on a leader region.
1461#[derive(Debug, Clone, Copy, Default)]
1462pub struct RegionCatchupRequest {
1463    /// Sets it to writable if it's available after it has caught up with all changes.
1464    pub set_writable: bool,
1465    /// The `entry_id` that was expected to reply to.
1466    /// `None` stands replaying to latest.
1467    pub entry_id: Option<entry::Id>,
1468    /// Used for metrics metadata region.
1469    /// The `entry_id` that was expected to reply to.
1470    /// `None` stands replaying to latest.
1471    pub metadata_entry_id: Option<entry::Id>,
1472    /// The hint for replaying memtable.
1473    pub location_id: Option<u64>,
1474    /// Replay checkpoint.
1475    pub checkpoint: Option<ReplayCheckpoint>,
1476}
1477
1478#[derive(Debug, Clone)]
1479pub struct RegionBulkInsertsRequest {
1480    pub region_id: RegionId,
1481    pub payload: DfRecordBatch,
1482    pub raw_data: ArrowIpc,
1483    pub partition_expr_version: Option<u64>,
1484}
1485
1486impl RegionBulkInsertsRequest {
1487    pub fn estimated_size(&self) -> usize {
1488        self.payload.get_array_memory_size()
1489    }
1490}
1491
1492/// Request to stage a region with a new partition directive.
1493///
1494/// This request transitions a region into the staging mode.
1495/// It first flushes the memtable for the old partition expression if it is not
1496/// empty, then enters the staging mode with the new directive.
1497#[derive(Debug, Clone, PartialEq, Eq)]
1498pub enum StagingPartitionDirective {
1499    UpdatePartitionExpr(String),
1500    RejectAllWrites,
1501}
1502
1503impl StagingPartitionDirective {
1504    /// Returns the partition expression carried by this directive, if any.
1505    pub fn partition_expr(&self) -> Option<&str> {
1506        match self {
1507            Self::UpdatePartitionExpr(expr) => Some(expr),
1508            Self::RejectAllWrites => None,
1509        }
1510    }
1511}
1512
1513#[derive(Debug, Clone)]
1514pub struct EnterStagingRequest {
1515    /// The staging partition directive of the region.
1516    pub partition_directive: StagingPartitionDirective,
1517}
1518
1519impl EnterStagingRequest {
1520    /// Builds an enter-staging request with a partition expression directive.
1521    pub fn with_partition_expr(partition_expr: String) -> Self {
1522        Self {
1523            partition_directive: StagingPartitionDirective::UpdatePartitionExpr(partition_expr),
1524        }
1525    }
1526}
1527
1528/// This request is used as part of the region repartition.
1529///
1530/// After a region has entered staging mode with a new partition expression
1531/// expression) and a separate process (for example, `remap_manifests`) has
1532/// generated the new file assignments for the staging region, this request
1533/// applies that generated manifest to the region.
1534///
1535/// In practice, this means:
1536/// - The `partition_expr` identifies the staging partition expression that the manifest
1537///   was generated for.
1538/// - `central_region_id` specifies which region holds the staging blob storage
1539///   where the manifest was written during the `remap_manifests` operation.
1540/// - `manifest_path` is the relative path within the central region's staging
1541///   blob storage to fetch the generated manifest.
1542///
1543/// It should typically be called **after** the staging region has been
1544/// initialized by [`EnterStagingRequest`] and the new file layout has been
1545/// computed, to finalize the repartition operation.
1546#[derive(Debug, Clone)]
1547pub struct ApplyStagingManifestRequest {
1548    /// The partition expression of the staging region.
1549    pub partition_expr: String,
1550    /// The region that stores the staging manifests in its staging blob storage.
1551    pub central_region_id: RegionId,
1552    /// The relative path to the staging manifest within the central region's
1553    /// staging blob storage.
1554    pub manifest_path: String,
1555}
1556
1557impl fmt::Display for RegionRequest {
1558    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1559        match self {
1560            RegionRequest::Put(_) => write!(f, "Put"),
1561            RegionRequest::Delete(_) => write!(f, "Delete"),
1562            RegionRequest::Create(_) => write!(f, "Create"),
1563            RegionRequest::Drop(_) => write!(f, "Drop"),
1564            RegionRequest::Open(_) => write!(f, "Open"),
1565            RegionRequest::Close(_) => write!(f, "Close"),
1566            RegionRequest::Alter(_) => write!(f, "Alter"),
1567            RegionRequest::Flush(_) => write!(f, "Flush"),
1568            RegionRequest::Compact(_) => write!(f, "Compact"),
1569            RegionRequest::BuildIndex(_) => write!(f, "BuildIndex"),
1570            RegionRequest::Truncate(_) => write!(f, "Truncate"),
1571            RegionRequest::Catchup(_) => write!(f, "Catchup"),
1572            RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"),
1573            RegionRequest::EnterStaging(_) => write!(f, "EnterStaging"),
1574            RegionRequest::ApplyStagingManifest(_) => write!(f, "ApplyStagingManifest"),
1575        }
1576    }
1577}
1578
1579#[cfg(test)]
1580mod tests {
1581
1582    use api::v1::region::RegionColumnDef;
1583    use api::v1::{ColumnDataType, ColumnDef};
1584    use datatypes::prelude::ConcreteDataType;
1585    use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
1586
1587    use super::*;
1588    use crate::metadata::RegionMetadataBuilder;
1589
1590    #[test]
1591    fn test_from_proto_location() {
1592        let proto_location = v1::AddColumnLocation {
1593            location_type: LocationType::First as i32,
1594            after_column_name: String::default(),
1595        };
1596        let location = AddColumnLocation::try_from(proto_location).unwrap();
1597        assert_eq!(location, AddColumnLocation::First);
1598
1599        let proto_location = v1::AddColumnLocation {
1600            location_type: 10,
1601            after_column_name: String::default(),
1602        };
1603        AddColumnLocation::try_from(proto_location).unwrap_err();
1604
1605        let proto_location = v1::AddColumnLocation {
1606            location_type: LocationType::After as i32,
1607            after_column_name: "a".to_string(),
1608        };
1609        let location = AddColumnLocation::try_from(proto_location).unwrap();
1610        assert_eq!(
1611            location,
1612            AddColumnLocation::After {
1613                column_name: "a".to_string()
1614            }
1615        );
1616    }
1617
1618    #[test]
1619    fn test_from_none_proto_add_column() {
1620        AddColumn::try_from(v1::region::AddColumn {
1621            column_def: None,
1622            location: None,
1623        })
1624        .unwrap_err();
1625    }
1626
1627    #[test]
1628    fn test_from_proto_alter_request() {
1629        RegionAlterRequest::try_from(AlterRequest {
1630            region_id: 0,
1631            schema_version: 1,
1632            kind: None,
1633        })
1634        .unwrap_err();
1635
1636        let request = RegionAlterRequest::try_from(AlterRequest {
1637            region_id: 0,
1638            schema_version: 1,
1639            kind: Some(alter_request::Kind::AddColumns(v1::region::AddColumns {
1640                add_columns: vec![v1::region::AddColumn {
1641                    column_def: Some(RegionColumnDef {
1642                        column_def: Some(ColumnDef {
1643                            name: "a".to_string(),
1644                            data_type: ColumnDataType::String as i32,
1645                            is_nullable: true,
1646                            default_constraint: vec![],
1647                            semantic_type: SemanticType::Field as i32,
1648                            comment: String::new(),
1649                            ..Default::default()
1650                        }),
1651                        column_id: 1,
1652                    }),
1653                    location: Some(v1::AddColumnLocation {
1654                        location_type: LocationType::First as i32,
1655                        after_column_name: String::default(),
1656                    }),
1657                }],
1658            })),
1659        })
1660        .unwrap();
1661
1662        assert_eq!(
1663            request,
1664            RegionAlterRequest {
1665                kind: AlterKind::AddColumns {
1666                    columns: vec![AddColumn {
1667                        column_metadata: ColumnMetadata {
1668                            column_schema: ColumnSchema::new(
1669                                "a",
1670                                ConcreteDataType::string_datatype(),
1671                                true,
1672                            ),
1673                            semantic_type: SemanticType::Field,
1674                            column_id: 1,
1675                        },
1676                        location: Some(AddColumnLocation::First),
1677                    }]
1678                },
1679            }
1680        );
1681    }
1682
1683    /// Returns a new region metadata for testing. Metadata:
1684    /// `[(ts, ms, 1), (tag_0, string, 2), (field_0, string, 3), (field_1, bool, 4)]`
1685    fn new_metadata() -> RegionMetadata {
1686        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1687        builder
1688            .push_column_metadata(ColumnMetadata {
1689                column_schema: ColumnSchema::new(
1690                    "ts",
1691                    ConcreteDataType::timestamp_millisecond_datatype(),
1692                    false,
1693                ),
1694                semantic_type: SemanticType::Timestamp,
1695                column_id: 1,
1696            })
1697            .push_column_metadata(ColumnMetadata {
1698                column_schema: ColumnSchema::new(
1699                    "tag_0",
1700                    ConcreteDataType::string_datatype(),
1701                    true,
1702                ),
1703                semantic_type: SemanticType::Tag,
1704                column_id: 2,
1705            })
1706            .push_column_metadata(ColumnMetadata {
1707                column_schema: ColumnSchema::new(
1708                    "field_0",
1709                    ConcreteDataType::string_datatype(),
1710                    true,
1711                ),
1712                semantic_type: SemanticType::Field,
1713                column_id: 3,
1714            })
1715            .push_column_metadata(ColumnMetadata {
1716                column_schema: ColumnSchema::new(
1717                    "field_1",
1718                    ConcreteDataType::boolean_datatype(),
1719                    true,
1720                ),
1721                semantic_type: SemanticType::Field,
1722                column_id: 4,
1723            })
1724            .primary_key(vec![2]);
1725        builder.build().unwrap()
1726    }
1727
1728    #[test]
1729    fn test_add_column_validate() {
1730        let metadata = new_metadata();
1731        let add_column = AddColumn {
1732            column_metadata: ColumnMetadata {
1733                column_schema: ColumnSchema::new(
1734                    "tag_1",
1735                    ConcreteDataType::string_datatype(),
1736                    true,
1737                ),
1738                semantic_type: SemanticType::Tag,
1739                column_id: 5,
1740            },
1741            location: None,
1742        };
1743        add_column.validate(&metadata).unwrap();
1744        assert!(add_column.need_alter(&metadata));
1745
1746        // Add not null column.
1747        AddColumn {
1748            column_metadata: ColumnMetadata {
1749                column_schema: ColumnSchema::new(
1750                    "tag_1",
1751                    ConcreteDataType::string_datatype(),
1752                    false,
1753                ),
1754                semantic_type: SemanticType::Tag,
1755                column_id: 5,
1756            },
1757            location: None,
1758        }
1759        .validate(&metadata)
1760        .unwrap_err();
1761
1762        // Add existing column.
1763        let add_column = AddColumn {
1764            column_metadata: ColumnMetadata {
1765                column_schema: ColumnSchema::new(
1766                    "tag_0",
1767                    ConcreteDataType::string_datatype(),
1768                    true,
1769                ),
1770                semantic_type: SemanticType::Tag,
1771                column_id: 2,
1772            },
1773            location: None,
1774        };
1775        add_column.validate(&metadata).unwrap();
1776        assert!(!add_column.need_alter(&metadata));
1777    }
1778
1779    #[test]
1780    fn test_add_duplicate_columns() {
1781        let kind = AlterKind::AddColumns {
1782            columns: vec![
1783                AddColumn {
1784                    column_metadata: ColumnMetadata {
1785                        column_schema: ColumnSchema::new(
1786                            "tag_1",
1787                            ConcreteDataType::string_datatype(),
1788                            true,
1789                        ),
1790                        semantic_type: SemanticType::Tag,
1791                        column_id: 5,
1792                    },
1793                    location: None,
1794                },
1795                AddColumn {
1796                    column_metadata: ColumnMetadata {
1797                        column_schema: ColumnSchema::new(
1798                            "tag_1",
1799                            ConcreteDataType::string_datatype(),
1800                            true,
1801                        ),
1802                        semantic_type: SemanticType::Field,
1803                        column_id: 6,
1804                    },
1805                    location: None,
1806                },
1807            ],
1808        };
1809        let metadata = new_metadata();
1810        kind.validate(&metadata).unwrap();
1811        assert!(kind.need_alter(&metadata));
1812    }
1813
1814    #[test]
1815    fn test_add_existing_column_different_metadata() {
1816        let metadata = new_metadata();
1817
1818        // Add existing column with different id.
1819        let kind = AlterKind::AddColumns {
1820            columns: vec![AddColumn {
1821                column_metadata: ColumnMetadata {
1822                    column_schema: ColumnSchema::new(
1823                        "tag_0",
1824                        ConcreteDataType::string_datatype(),
1825                        true,
1826                    ),
1827                    semantic_type: SemanticType::Tag,
1828                    column_id: 4,
1829                },
1830                location: None,
1831            }],
1832        };
1833        kind.validate(&metadata).unwrap_err();
1834
1835        // Add existing column with different type.
1836        let kind = AlterKind::AddColumns {
1837            columns: vec![AddColumn {
1838                column_metadata: ColumnMetadata {
1839                    column_schema: ColumnSchema::new(
1840                        "tag_0",
1841                        ConcreteDataType::int64_datatype(),
1842                        true,
1843                    ),
1844                    semantic_type: SemanticType::Tag,
1845                    column_id: 2,
1846                },
1847                location: None,
1848            }],
1849        };
1850        kind.validate(&metadata).unwrap_err();
1851
1852        // Add existing column with different name.
1853        let kind = AlterKind::AddColumns {
1854            columns: vec![AddColumn {
1855                column_metadata: ColumnMetadata {
1856                    column_schema: ColumnSchema::new(
1857                        "tag_1",
1858                        ConcreteDataType::string_datatype(),
1859                        true,
1860                    ),
1861                    semantic_type: SemanticType::Tag,
1862                    column_id: 2,
1863                },
1864                location: None,
1865            }],
1866        };
1867        kind.validate(&metadata).unwrap_err();
1868    }
1869
1870    #[test]
1871    fn test_add_existing_column_with_location() {
1872        let metadata = new_metadata();
1873        let kind = AlterKind::AddColumns {
1874            columns: vec![AddColumn {
1875                column_metadata: ColumnMetadata {
1876                    column_schema: ColumnSchema::new(
1877                        "tag_0",
1878                        ConcreteDataType::string_datatype(),
1879                        true,
1880                    ),
1881                    semantic_type: SemanticType::Tag,
1882                    column_id: 2,
1883                },
1884                location: Some(AddColumnLocation::First),
1885            }],
1886        };
1887        kind.validate(&metadata).unwrap_err();
1888    }
1889
1890    #[test]
1891    fn test_validate_drop_column() {
1892        let metadata = new_metadata();
1893        let kind = AlterKind::DropColumns {
1894            names: vec!["xxxx".to_string()],
1895        };
1896        kind.validate(&metadata).unwrap();
1897        assert!(!kind.need_alter(&metadata));
1898
1899        AlterKind::DropColumns {
1900            names: vec!["tag_0".to_string()],
1901        }
1902        .validate(&metadata)
1903        .unwrap_err();
1904
1905        let kind = AlterKind::DropColumns {
1906            names: vec!["field_0".to_string()],
1907        };
1908        kind.validate(&metadata).unwrap();
1909        assert!(kind.need_alter(&metadata));
1910    }
1911
1912    #[test]
1913    fn test_validate_modify_column_type() {
1914        let metadata = new_metadata();
1915        AlterKind::ModifyColumnTypes {
1916            columns: vec![ModifyColumnType {
1917                column_name: "xxxx".to_string(),
1918                target_type: ConcreteDataType::string_datatype(),
1919            }],
1920        }
1921        .validate(&metadata)
1922        .unwrap_err();
1923
1924        AlterKind::ModifyColumnTypes {
1925            columns: vec![ModifyColumnType {
1926                column_name: "field_1".to_string(),
1927                target_type: ConcreteDataType::date_datatype(),
1928            }],
1929        }
1930        .validate(&metadata)
1931        .unwrap_err();
1932
1933        AlterKind::ModifyColumnTypes {
1934            columns: vec![ModifyColumnType {
1935                column_name: "ts".to_string(),
1936                target_type: ConcreteDataType::date_datatype(),
1937            }],
1938        }
1939        .validate(&metadata)
1940        .unwrap_err();
1941
1942        AlterKind::ModifyColumnTypes {
1943            columns: vec![ModifyColumnType {
1944                column_name: "tag_0".to_string(),
1945                target_type: ConcreteDataType::date_datatype(),
1946            }],
1947        }
1948        .validate(&metadata)
1949        .unwrap_err();
1950
1951        let kind = AlterKind::ModifyColumnTypes {
1952            columns: vec![ModifyColumnType {
1953                column_name: "field_0".to_string(),
1954                target_type: ConcreteDataType::int32_datatype(),
1955            }],
1956        };
1957        kind.validate(&metadata).unwrap();
1958        assert!(kind.need_alter(&metadata));
1959    }
1960
1961    #[test]
1962    fn test_validate_add_columns() {
1963        let kind = AlterKind::AddColumns {
1964            columns: vec![
1965                AddColumn {
1966                    column_metadata: ColumnMetadata {
1967                        column_schema: ColumnSchema::new(
1968                            "tag_1",
1969                            ConcreteDataType::string_datatype(),
1970                            true,
1971                        ),
1972                        semantic_type: SemanticType::Tag,
1973                        column_id: 5,
1974                    },
1975                    location: None,
1976                },
1977                AddColumn {
1978                    column_metadata: ColumnMetadata {
1979                        column_schema: ColumnSchema::new(
1980                            "field_2",
1981                            ConcreteDataType::string_datatype(),
1982                            true,
1983                        ),
1984                        semantic_type: SemanticType::Field,
1985                        column_id: 6,
1986                    },
1987                    location: None,
1988                },
1989            ],
1990        };
1991        let request = RegionAlterRequest { kind };
1992        let mut metadata = new_metadata();
1993        metadata.schema_version = 1;
1994        request.validate(&metadata).unwrap();
1995    }
1996
1997    #[test]
1998    fn test_validate_create_region() {
1999        let column_metadatas = vec![
2000            ColumnMetadata {
2001                column_schema: ColumnSchema::new(
2002                    "ts",
2003                    ConcreteDataType::timestamp_millisecond_datatype(),
2004                    false,
2005                ),
2006                semantic_type: SemanticType::Timestamp,
2007                column_id: 1,
2008            },
2009            ColumnMetadata {
2010                column_schema: ColumnSchema::new(
2011                    "tag_0",
2012                    ConcreteDataType::string_datatype(),
2013                    true,
2014                ),
2015                semantic_type: SemanticType::Tag,
2016                column_id: 2,
2017            },
2018            ColumnMetadata {
2019                column_schema: ColumnSchema::new(
2020                    "field_0",
2021                    ConcreteDataType::string_datatype(),
2022                    true,
2023                ),
2024                semantic_type: SemanticType::Field,
2025                column_id: 3,
2026            },
2027        ];
2028        let create = RegionCreateRequest {
2029            engine: "mito".to_string(),
2030            column_metadatas,
2031            primary_key: vec![3, 4],
2032            options: HashMap::new(),
2033            table_dir: "path".to_string(),
2034            path_type: PathType::Bare,
2035            partition_expr_json: Some("".to_string()),
2036        };
2037
2038        assert!(create.validate().is_err());
2039    }
2040
2041    #[test]
2042    fn test_validate_modify_column_fulltext_options() {
2043        let kind = AlterKind::SetIndexes {
2044            options: vec![SetIndexOption::Fulltext {
2045                column_name: "tag_0".to_string(),
2046                options: FulltextOptions::new_unchecked(
2047                    true,
2048                    FulltextAnalyzer::Chinese,
2049                    false,
2050                    FulltextBackend::Bloom,
2051                    1000,
2052                    0.01,
2053                ),
2054            }],
2055        };
2056        let request = RegionAlterRequest { kind };
2057        let mut metadata = new_metadata();
2058        metadata.schema_version = 1;
2059        request.validate(&metadata).unwrap();
2060
2061        let kind = AlterKind::UnsetIndexes {
2062            options: vec![UnsetIndexOption::Fulltext {
2063                column_name: "tag_0".to_string(),
2064            }],
2065        };
2066        let request = RegionAlterRequest { kind };
2067        let mut metadata = new_metadata();
2068        metadata.schema_version = 1;
2069        request.validate(&metadata).unwrap();
2070    }
2071
2072    #[test]
2073    fn test_validate_sync_columns() {
2074        let metadata = new_metadata();
2075        let kind = AlterKind::SyncColumns {
2076            column_metadatas: vec![
2077                ColumnMetadata {
2078                    column_schema: ColumnSchema::new(
2079                        "tag_1",
2080                        ConcreteDataType::string_datatype(),
2081                        true,
2082                    ),
2083                    semantic_type: SemanticType::Tag,
2084                    column_id: 5,
2085                },
2086                ColumnMetadata {
2087                    column_schema: ColumnSchema::new(
2088                        "field_2",
2089                        ConcreteDataType::string_datatype(),
2090                        true,
2091                    ),
2092                    semantic_type: SemanticType::Field,
2093                    column_id: 6,
2094                },
2095            ],
2096        };
2097        let err = kind.validate(&metadata).unwrap_err();
2098        assert!(err.to_string().contains("not a primary key"));
2099
2100        // Change the timestamp column name.
2101        let mut column_metadatas_with_different_ts_column = metadata.column_metadatas.clone();
2102        let ts_column = column_metadatas_with_different_ts_column
2103            .iter_mut()
2104            .find(|c| c.semantic_type == SemanticType::Timestamp)
2105            .unwrap();
2106        ts_column.column_schema.name = "ts1".to_string();
2107
2108        let kind = AlterKind::SyncColumns {
2109            column_metadatas: column_metadatas_with_different_ts_column,
2110        };
2111        let err = kind.validate(&metadata).unwrap_err();
2112        assert!(
2113            err.to_string()
2114                .contains("timestamp column ts has different id")
2115        );
2116
2117        // Change the primary key column name.
2118        let mut column_metadatas_with_different_pk_column = metadata.column_metadatas.clone();
2119        let pk_column = column_metadatas_with_different_pk_column
2120            .iter_mut()
2121            .find(|c| c.column_schema.name == "tag_0")
2122            .unwrap();
2123        pk_column.column_id = 100;
2124        let kind = AlterKind::SyncColumns {
2125            column_metadatas: column_metadatas_with_different_pk_column,
2126        };
2127        let err = kind.validate(&metadata).unwrap_err();
2128        assert!(
2129            err.to_string()
2130                .contains("column with same name tag_0 has different id")
2131        );
2132
2133        // Add a new field column.
2134        let mut column_metadatas_with_new_field_column = metadata.column_metadatas.clone();
2135        column_metadatas_with_new_field_column.push(ColumnMetadata {
2136            column_schema: ColumnSchema::new("field_2", ConcreteDataType::string_datatype(), true),
2137            semantic_type: SemanticType::Field,
2138            column_id: 4,
2139        });
2140        let kind = AlterKind::SyncColumns {
2141            column_metadatas: column_metadatas_with_new_field_column,
2142        };
2143        kind.validate(&metadata).unwrap();
2144    }
2145
2146    #[test]
2147    fn test_cast_path_type_to_primitive() {
2148        assert_eq!(PathType::Bare as u8, 0);
2149        assert_eq!(PathType::Data as u8, 1);
2150        assert_eq!(PathType::Metadata as u8, 2);
2151        assert_eq!(
2152            PathType::try_from(PathType::Bare as u8).unwrap(),
2153            PathType::Bare
2154        );
2155        assert_eq!(
2156            PathType::try_from(PathType::Data as u8).unwrap(),
2157            PathType::Data
2158        );
2159        assert_eq!(
2160            PathType::try_from(PathType::Metadata as u8).unwrap(),
2161            PathType::Metadata
2162        );
2163    }
2164}