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