Skip to main content

store_api/
region_request.rs

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