Skip to main content

table/
metadata.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, HashSet};
16use std::sync::Arc;
17
18use chrono::{DateTime, Utc};
19use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
20use common_macro::ToMetaBuilder;
21use common_query::AddColumnLocation;
22use datafusion_expr::TableProviderFilterPushDown;
23use datatypes::error::time_index_not_widening_error;
24pub use datatypes::error::{Error as ConvertError, Result as ConvertResult};
25use datatypes::extension::json::json2_metadata_with_updated_settings;
26use datatypes::schema::{
27    ColumnSchema, FulltextOptions, Schema, SchemaBuilder, SchemaRef, SkippingIndexOptions,
28};
29use derive_builder::Builder;
30use serde::{Deserialize, Deserializer, Serialize};
31use snafu::{OptionExt, ResultExt, ensure};
32use store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
33use store_api::mito_engine_options::{
34    APPEND_MODE_KEY, AUTO_FLUSH_INTERVAL_KEY, COMPACTION_TYPE, COMPACTION_TYPE_TWCS,
35    MAX_ROW_GROUP_ROW_COUNT, MERGE_MODE_KEY, PRESERVE_ROW_SEQUENCE, SKIP_WAL_KEY, SST_FORMAT_KEY,
36    TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM, TWCS_TRIGGER_FILE_NUM,
37};
38use store_api::region_request::{SetRegionOption, UnsetRegionOption};
39use store_api::storage::{ColumnDescriptor, ColumnDescriptorBuilder, ColumnId};
40
41use crate::error::{self, Result};
42use crate::requests::{
43    AddColumnRequest, AlterKind, AnnotationContext, AnnotationFamily, AnnotationValidationError,
44    ModifyColumnTypeRequest, REPARTITION_COLUMN_HINT_KEY, REPARTITION_PARTITION_NUM_HINT_KEY,
45    SetDefaultRequest, SetIndexOption, SetJsonSettingsRequest, TableOptions, UnsetIndexOption,
46    has_stable_string_form, parse_entity_columns, parse_entity_option_key,
47    validate_and_normalize_annotation, validate_annotation_keys,
48};
49use crate::table_reference::TableReference;
50
51pub type TableId = u32;
52pub type TableVersion = u64;
53
54/// Indicates whether and how a filter expression can be handled by a
55/// Table for table scans.
56#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
57pub enum FilterPushDownType {
58    /// The expression cannot be used by the provider.
59    Unsupported,
60    /// The expression can be used to help minimise the data retrieved,
61    /// but the provider cannot guarantee that all returned tuples
62    /// satisfy the filter. The Filter plan node containing this expression
63    /// will be preserved.
64    Inexact,
65    /// The provider guarantees that all returned data satisfies this
66    /// filter expression. The Filter plan node containing this expression
67    /// will be removed.
68    Exact,
69}
70
71impl From<TableProviderFilterPushDown> for FilterPushDownType {
72    fn from(value: TableProviderFilterPushDown) -> Self {
73        match value {
74            TableProviderFilterPushDown::Unsupported => FilterPushDownType::Unsupported,
75            TableProviderFilterPushDown::Inexact => FilterPushDownType::Inexact,
76            TableProviderFilterPushDown::Exact => FilterPushDownType::Exact,
77        }
78    }
79}
80
81impl From<FilterPushDownType> for TableProviderFilterPushDown {
82    fn from(value: FilterPushDownType) -> Self {
83        match value {
84            FilterPushDownType::Unsupported => TableProviderFilterPushDown::Unsupported,
85            FilterPushDownType::Inexact => TableProviderFilterPushDown::Inexact,
86            FilterPushDownType::Exact => TableProviderFilterPushDown::Exact,
87        }
88    }
89}
90
91/// Indicates the type of this table for metadata/catalog purposes.
92#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
93pub enum TableType {
94    /// An ordinary physical table.
95    Base,
96    /// A non-materialised table that itself uses a query internally to provide data.
97    View,
98    /// A transient table.
99    Temporary,
100}
101
102impl std::fmt::Display for TableType {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            TableType::Base => f.write_str("BASE TABLE"),
106            TableType::Temporary => f.write_str("TEMPORARY"),
107            TableType::View => f.write_str("VIEW"),
108        }
109    }
110}
111
112impl From<TableType> for datafusion::datasource::TableType {
113    fn from(t: TableType) -> datafusion::datasource::TableType {
114        match t {
115            TableType::Base => datafusion::datasource::TableType::Base,
116            TableType::View => datafusion::datasource::TableType::View,
117            TableType::Temporary => datafusion::datasource::TableType::Temporary,
118        }
119    }
120}
121
122/// Identifier of the table.
123#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Default)]
124pub struct TableIdent {
125    /// Unique id of this table.
126    pub table_id: TableId,
127    /// Version of the table, bumped when metadata (such as schema) of the table
128    /// being changed.
129    pub version: TableVersion,
130}
131
132/// The table metadata.
133///
134/// Note: if you add new fields to this struct, please ensure 'new_meta_builder' function works.
135#[derive(Clone, Debug, Builder, PartialEq, Eq, ToMetaBuilder, Serialize)]
136#[builder(pattern = "mutable", custom_constructor)]
137pub struct TableMeta {
138    pub schema: SchemaRef,
139    /// The indices of columns in primary key. Note that the index of timestamp column
140    /// is not included in these indices.
141    pub primary_key_indices: Vec<usize>,
142    #[builder(default = "self.default_value_indices()?")]
143    pub value_indices: Vec<usize>,
144    #[builder(default, setter(into))]
145    pub engine: String,
146    pub next_column_id: ColumnId,
147    /// Table options.
148    #[builder(default)]
149    pub options: TableOptions,
150    #[builder(default = "Utc::now()")]
151    pub created_on: DateTime<Utc>,
152    #[builder(default = "self.default_updated_on()")]
153    pub updated_on: DateTime<Utc>,
154    #[builder(default = "Vec::new()")]
155    pub partition_key_indices: Vec<usize>,
156    #[builder(default = "Vec::new()")]
157    pub column_ids: Vec<ColumnId>,
158}
159
160impl TableMeta {
161    pub fn empty() -> Self {
162        Self {
163            schema: Arc::new(Schema::new(vec![])),
164            primary_key_indices: vec![],
165            value_indices: vec![],
166            engine: "".to_string(),
167            next_column_id: 0,
168            options: TableOptions::default(),
169            created_on: Utc::now(),
170            updated_on: Utc::now(),
171            partition_key_indices: vec![],
172            column_ids: vec![],
173        }
174    }
175}
176
177impl<'de> Deserialize<'de> for TableMeta {
178    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
179    where
180        D: Deserializer<'de>,
181    {
182        #[derive(Deserialize)]
183        struct RawTableMeta {
184            schema: SchemaRef,
185            primary_key_indices: Vec<usize>,
186            value_indices: Vec<usize>,
187            engine: String,
188            next_column_id: ColumnId,
189            options: TableOptions,
190            created_on: DateTime<Utc>,
191            updated_on: Option<DateTime<Utc>>,
192            #[serde(default)]
193            partition_key_indices: Vec<usize>,
194            #[serde(default)]
195            column_ids: Vec<ColumnId>,
196        }
197
198        let RawTableMeta {
199            schema,
200            primary_key_indices,
201            value_indices,
202            engine,
203            next_column_id,
204            options,
205            created_on,
206            updated_on,
207            partition_key_indices,
208            column_ids,
209        } = RawTableMeta::deserialize(deserializer)?;
210
211        Ok(Self {
212            schema,
213            primary_key_indices,
214            value_indices,
215            engine,
216            next_column_id,
217            options,
218            created_on,
219            updated_on: updated_on.unwrap_or(created_on),
220            partition_key_indices,
221            column_ids,
222        })
223    }
224}
225
226impl TableMetaBuilder {
227    /// Note: Please always use [new_meta_builder] to create new [TableMetaBuilder].
228    #[cfg(any(test, feature = "testing"))]
229    pub fn empty() -> Self {
230        Self {
231            schema: None,
232            primary_key_indices: None,
233            value_indices: None,
234            engine: None,
235            next_column_id: None,
236            options: None,
237            created_on: None,
238            updated_on: None,
239            partition_key_indices: None,
240            column_ids: None,
241        }
242    }
243}
244
245impl TableMetaBuilder {
246    fn default_value_indices(&self) -> std::result::Result<Vec<usize>, String> {
247        match (&self.primary_key_indices, &self.schema) {
248            (Some(v), Some(schema)) => {
249                let column_schemas = schema.column_schemas();
250                Ok((0..column_schemas.len())
251                    .filter(|idx| !v.contains(idx))
252                    .collect())
253            }
254            _ => Err("Missing primary_key_indices or schema to create value_indices".to_string()),
255        }
256    }
257
258    fn default_updated_on(&self) -> DateTime<Utc> {
259        self.created_on.unwrap_or_default()
260    }
261
262    pub fn new_external_table() -> Self {
263        Self {
264            schema: None,
265            primary_key_indices: Some(Vec::new()),
266            value_indices: Some(Vec::new()),
267            engine: None,
268            next_column_id: Some(0),
269            options: None,
270            created_on: None,
271            updated_on: None,
272            partition_key_indices: None,
273            column_ids: None,
274        }
275    }
276}
277
278/// The result after splitting requests by column location info.
279struct SplitResult<'a> {
280    /// column requests should be added at first place.
281    columns_at_first: Vec<&'a AddColumnRequest>,
282    /// column requests should be added after already exist columns.
283    columns_at_after: HashMap<String, Vec<&'a AddColumnRequest>>,
284    /// column requests should be added at last place.
285    columns_at_last: Vec<&'a AddColumnRequest>,
286    /// all column names should be added.
287    column_names: Vec<String>,
288}
289
290impl TableMeta {
291    pub fn row_key_column_names(&self) -> impl Iterator<Item = &String> {
292        let columns_schemas = &self.schema.column_schemas();
293        self.primary_key_indices
294            .iter()
295            .map(|idx| &columns_schemas[*idx].name)
296    }
297
298    pub fn field_column_names(&self) -> impl Iterator<Item = &String> {
299        // `value_indices` is wrong under distributed mode. Use the logic copied from DESC TABLE
300        let columns_schemas = self.schema.column_schemas();
301        let primary_key_indices = &self.primary_key_indices;
302        columns_schemas
303            .iter()
304            .enumerate()
305            .filter(|(i, cs)| !primary_key_indices.contains(i) && !cs.is_time_index())
306            .map(|(_, cs)| &cs.name)
307    }
308
309    pub fn partition_column_names(&self) -> impl Iterator<Item = &String> {
310        let columns_schemas = &self.schema.column_schemas();
311        self.partition_key_indices
312            .iter()
313            .map(|idx| &columns_schemas[*idx].name)
314    }
315
316    pub fn partition_columns(&self) -> impl Iterator<Item = &ColumnSchema> {
317        self.partition_key_indices
318            .iter()
319            .map(|idx| &self.schema.column_schemas()[*idx])
320    }
321
322    /// Returns the new [TableMetaBuilder] after applying given `alter_kind`.
323    ///
324    /// The returned builder would derive the next column id of this meta.
325    pub fn builder_with_alter_kind(
326        &self,
327        table_name: &str,
328        alter_kind: &AlterKind,
329    ) -> Result<TableMetaBuilder> {
330        let mut builder = match alter_kind {
331            AlterKind::AddColumns { columns } => self.add_columns(table_name, columns),
332            AlterKind::DropColumns { names } => self.remove_columns(table_name, names),
333            AlterKind::ModifyColumnTypes { columns } => {
334                self.modify_column_types(table_name, columns)
335            }
336            AlterKind::SetJsonSettings { request } => self.set_json_settings(table_name, request),
337            // No need to rebuild table meta when renaming tables.
338            AlterKind::RenameTable { .. } => Ok(self.new_meta_builder()),
339            AlterKind::SetTableOptions { options } => self.set_table_options(options),
340            AlterKind::UnsetTableOptions { keys } => self.unset_table_options(keys),
341            AlterKind::SetAnnotations { family, options } => {
342                self.set_annotations(table_name, *family, options)
343            }
344            AlterKind::UnsetAnnotations { family, keys } => {
345                self.unset_annotations(table_name, *family, keys)
346            }
347            AlterKind::SetIndexes { options } => self.set_indexes(table_name, options),
348            AlterKind::UnsetIndexes { options } => self.unset_indexes(table_name, options),
349            AlterKind::DropDefaults { names } => self.drop_defaults(table_name, names),
350            AlterKind::SetDefaults { defaults } => self.set_defaults(table_name, defaults),
351        }?;
352        let _ = builder.updated_on(Utc::now());
353        Ok(builder)
354    }
355
356    /// Creates a [TableMetaBuilder] with modified table options.
357    fn set_table_options(&self, requests: &[SetRegionOption]) -> Result<TableMetaBuilder> {
358        let mut new_options = self.options.clone();
359
360        for request in requests {
361            match request {
362                SetRegionOption::WriteBufferSize(new_write_buffer_size) => {
363                    new_options.write_buffer_size = *new_write_buffer_size;
364                }
365                SetRegionOption::Ttl(new_ttl) => {
366                    new_options.ttl = *new_ttl;
367                }
368                SetRegionOption::Twsc(key, value) => {
369                    let persisted_key = if matches!(
370                        key.as_str(),
371                        TWCS_TRIGGER_FILE_NUM | TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM
372                    ) {
373                        new_options.extra_options.remove(TWCS_TRIGGER_FILE_NUM);
374                        new_options
375                            .extra_options
376                            .remove(TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM);
377                        TWCS_TRIGGER_FILE_NUM
378                    } else {
379                        key
380                    };
381                    if !value.is_empty() {
382                        new_options
383                            .extra_options
384                            .insert(persisted_key.to_string(), value.clone());
385                        // Ensure node restart correctly.
386                        new_options.extra_options.insert(
387                            COMPACTION_TYPE.to_string(),
388                            COMPACTION_TYPE_TWCS.to_string(),
389                        );
390                    } else {
391                        // Invalidate the previous change option if an empty value has been set.
392                        new_options.extra_options.remove(persisted_key);
393                    }
394                }
395                SetRegionOption::Format(value) => {
396                    new_options
397                        .extra_options
398                        .insert(SST_FORMAT_KEY.to_string(), value.clone());
399                }
400                SetRegionOption::AppendMode(value) => {
401                    new_options
402                        .extra_options
403                        .insert(APPEND_MODE_KEY.to_string(), value.to_string());
404                    if *value {
405                        new_options.extra_options.remove(MERGE_MODE_KEY);
406                    }
407                }
408                SetRegionOption::AutoFlushInterval(new_interval) => {
409                    if let Some(interval) = new_interval {
410                        new_options.extra_options.insert(
411                            AUTO_FLUSH_INTERVAL_KEY.to_string(),
412                            humantime::format_duration(*interval).to_string(),
413                        );
414                    } else {
415                        new_options.extra_options.remove(AUTO_FLUSH_INTERVAL_KEY);
416                    }
417                }
418                SetRegionOption::MaxRowGroupRowCount(row_count) => {
419                    if let Some(row_count) = row_count {
420                        new_options
421                            .extra_options
422                            .insert(MAX_ROW_GROUP_ROW_COUNT.to_string(), row_count.to_string());
423                    } else {
424                        new_options.extra_options.remove(MAX_ROW_GROUP_ROW_COUNT);
425                    }
426                }
427                SetRegionOption::PreserveRowSequence(preserve) => {
428                    if *preserve {
429                        new_options
430                            .extra_options
431                            .insert(PRESERVE_ROW_SEQUENCE.to_string(), preserve.to_string());
432                    } else {
433                        new_options.extra_options.remove(PRESERVE_ROW_SEQUENCE);
434                    }
435                }
436                SetRegionOption::SkipWal(skip_wal) => {
437                    new_options.skip_wal = *skip_wal;
438                    // Keep the explicit table option so it remains distinguishable
439                    // from a value inherited from the schema.
440                    new_options
441                        .extra_options
442                        .insert(SKIP_WAL_KEY.to_string(), skip_wal.to_string());
443                }
444            }
445        }
446        let mut builder = self.new_meta_builder();
447        builder.options(new_options);
448
449        Ok(builder)
450    }
451
452    fn unset_table_options(&self, requests: &[UnsetRegionOption]) -> Result<TableMetaBuilder> {
453        let requests = requests.iter().map(Into::into).collect::<Vec<_>>();
454        self.set_table_options(&requests)
455    }
456
457    /// Applies an annotation SET. Validation lives here, on the mutation
458    /// path, so it runs both at frontend verification and again inside the
459    /// alter procedure's prepare step — under the table lock, against fresh
460    /// metadata.
461    fn set_annotations(
462        &self,
463        table_name: &str,
464        family: AnnotationFamily,
465        options: &[(String, String)],
466    ) -> Result<TableMetaBuilder> {
467        validate_annotation_keys(options.iter().map(|(key, _)| key.as_str())).map_err(|err| {
468            error::InvalidAlterRequestSnafu {
469                table: table_name,
470                err: err.to_string(),
471            }
472            .build()
473        })?;
474        let cx = AnnotationContext {
475            data_model: self.options.data_model(),
476            schema: &self.schema,
477            partition_key_indices: &self.partition_key_indices,
478        };
479        let mut new_options = self.options.clone();
480        for (key, value) in options {
481            ensure!(
482                AnnotationFamily::of_key(key) == Some(family),
483                error::InvalidAlterRequestSnafu {
484                    table: table_name,
485                    err: format!(
486                        "`{key}` is outside the `{}` annotation namespace",
487                        family.namespace()
488                    ),
489                }
490            );
491            let checked = validate_and_normalize_annotation(family, &cx, key, value).map_err(
492                |e| match e {
493                    AnnotationValidationError::ColumnNotFound { column } => {
494                        error::ColumnNotExistsSnafu {
495                            column_name: column,
496                            table_name,
497                        }
498                        .build()
499                    }
500                    other => error::InvalidAlterRequestSnafu {
501                        table: table_name,
502                        err: other.to_string(),
503                    }
504                    .build(),
505                },
506            )?;
507            new_options.extra_options.insert(key.clone(), checked);
508        }
509        let mut builder = self.new_meta_builder();
510        builder.options(new_options);
511        Ok(builder)
512    }
513
514    /// Applies an annotation UNSET. Deliberately lenient inside the family's
515    /// namespace: keys this version does not recognise may still be removed,
516    /// so options left behind by other versions can be cleaned up.
517    fn unset_annotations(
518        &self,
519        table_name: &str,
520        family: AnnotationFamily,
521        keys: &[String],
522    ) -> Result<TableMetaBuilder> {
523        validate_annotation_keys(keys.iter().map(String::as_str)).map_err(|err| {
524            error::InvalidAlterRequestSnafu {
525                table: table_name,
526                err: err.to_string(),
527            }
528            .build()
529        })?;
530        let mut new_options = self.options.clone();
531        for key in keys {
532            ensure!(
533                AnnotationFamily::of_key(key) == Some(family),
534                error::InvalidAlterRequestSnafu {
535                    table: table_name,
536                    err: format!(
537                        "`{key}` is outside the `{}` annotation namespace",
538                        family.namespace()
539                    ),
540                }
541            );
542            new_options.extra_options.remove(key);
543        }
544        let mut builder = self.new_meta_builder();
545        builder.options(new_options);
546        Ok(builder)
547    }
548
549    fn set_indexes(
550        &self,
551        table_name: &str,
552        requests: &[SetIndexOption],
553    ) -> Result<TableMetaBuilder> {
554        let table_schema = &self.schema;
555        let mut set_index_options: HashMap<&str, Vec<_>> = HashMap::new();
556        for request in requests {
557            let column_name = request.column_name();
558            table_schema
559                .column_index_by_name(column_name)
560                .with_context(|| error::ColumnNotExistsSnafu {
561                    column_name,
562                    table_name,
563                })?;
564            set_index_options
565                .entry(column_name)
566                .or_default()
567                .push(request);
568        }
569
570        let mut meta_builder = self.new_meta_builder();
571        let mut columns: Vec<_> = Vec::with_capacity(table_schema.column_schemas().len());
572        for mut column in table_schema.column_schemas().iter().cloned() {
573            if let Some(request) = set_index_options.get(column.name.as_str()) {
574                for request in request {
575                    self.set_index(&mut column, request)?;
576                }
577            }
578            columns.push(column);
579        }
580
581        let mut builder = SchemaBuilder::try_from_columns(columns)
582            .with_context(|_| error::SchemaBuildSnafu {
583                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
584            })?
585            .version(table_schema.version() + 1);
586
587        for (k, v) in table_schema.metadata().iter() {
588            builder = builder.add_metadata(k, v);
589        }
590
591        let new_schema = builder.build().with_context(|_| {
592            let column_names = requests
593                .iter()
594                .map(|request| request.column_name())
595                .collect::<Vec<_>>();
596            error::SchemaBuildSnafu {
597                msg: format!(
598                    "Table {table_name} cannot set index options with columns {column_names:?}",
599                ),
600            }
601        })?;
602        let _ = meta_builder
603            .schema(Arc::new(new_schema))
604            .primary_key_indices(self.primary_key_indices.clone());
605
606        Ok(meta_builder)
607    }
608
609    fn unset_indexes(
610        &self,
611        table_name: &str,
612        requests: &[UnsetIndexOption],
613    ) -> Result<TableMetaBuilder> {
614        let table_schema = &self.schema;
615        let mut set_index_options: HashMap<&str, Vec<_>> = HashMap::new();
616        for request in requests {
617            let column_name = request.column_name();
618            table_schema
619                .column_index_by_name(column_name)
620                .with_context(|| error::ColumnNotExistsSnafu {
621                    column_name,
622                    table_name,
623                })?;
624            set_index_options
625                .entry(column_name)
626                .or_default()
627                .push(request);
628        }
629
630        let mut meta_builder = self.new_meta_builder();
631        let mut columns: Vec<_> = Vec::with_capacity(table_schema.column_schemas().len());
632        for mut column in table_schema.column_schemas().iter().cloned() {
633            if let Some(request) = set_index_options.get(column.name.as_str()) {
634                for request in request {
635                    self.unset_index(&mut column, request)?;
636                }
637            }
638            columns.push(column);
639        }
640
641        let mut builder = SchemaBuilder::try_from_columns(columns)
642            .with_context(|_| error::SchemaBuildSnafu {
643                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
644            })?
645            .version(table_schema.version() + 1);
646
647        for (k, v) in table_schema.metadata().iter() {
648            builder = builder.add_metadata(k, v);
649        }
650
651        let new_schema = builder.build().with_context(|_| {
652            let column_names = requests
653                .iter()
654                .map(|request| request.column_name())
655                .collect::<Vec<_>>();
656            error::SchemaBuildSnafu {
657                msg: format!(
658                    "Table {table_name} cannot set index options with columns {column_names:?}",
659                ),
660            }
661        })?;
662        let _ = meta_builder
663            .schema(Arc::new(new_schema))
664            .primary_key_indices(self.primary_key_indices.clone());
665
666        Ok(meta_builder)
667    }
668
669    fn set_index(&self, column_schema: &mut ColumnSchema, request: &SetIndexOption) -> Result<()> {
670        match request {
671            SetIndexOption::Fulltext {
672                column_name,
673                options,
674            } => {
675                ensure!(
676                    column_schema.data_type.is_string(),
677                    error::InvalidColumnOptionSnafu {
678                        column_name,
679                        msg: "FULLTEXT index only supports string type",
680                    }
681                );
682
683                let current_fulltext_options = column_schema
684                    .fulltext_options()
685                    .context(error::SetFulltextOptionsSnafu { column_name })?;
686                set_column_fulltext_options(
687                    column_schema,
688                    column_name,
689                    options,
690                    current_fulltext_options,
691                )?;
692            }
693            SetIndexOption::Inverted { column_name } => {
694                debug_assert_eq!(column_schema.name, *column_name);
695                column_schema.set_inverted_index(true);
696            }
697            SetIndexOption::Skipping {
698                column_name,
699                options,
700            } => {
701                set_column_skipping_index_options(column_schema, column_name, options)?;
702            }
703        }
704
705        Ok(())
706    }
707
708    fn unset_index(
709        &self,
710        column_schema: &mut ColumnSchema,
711        request: &UnsetIndexOption,
712    ) -> Result<()> {
713        match request {
714            UnsetIndexOption::Fulltext { column_name } => {
715                let current_fulltext_options = column_schema
716                    .fulltext_options()
717                    .context(error::SetFulltextOptionsSnafu { column_name })?;
718                unset_column_fulltext_options(
719                    column_schema,
720                    column_name,
721                    current_fulltext_options.clone(),
722                )?
723            }
724            UnsetIndexOption::Inverted { .. } => {
725                column_schema.set_inverted_index(false);
726            }
727            UnsetIndexOption::Skipping { column_name } => {
728                unset_column_skipping_index_options(column_schema, column_name)?;
729            }
730        }
731
732        Ok(())
733    }
734
735    // TODO(yingwen): Remove this.
736    /// Allocate a new column for the table.
737    ///
738    /// This method would bump the `next_column_id` of the meta.
739    pub fn alloc_new_column(
740        &mut self,
741        table_name: &str,
742        new_column: &ColumnSchema,
743    ) -> Result<ColumnDescriptor> {
744        let desc = ColumnDescriptorBuilder::new(
745            self.next_column_id as ColumnId,
746            &new_column.name,
747            new_column.data_type.clone(),
748        )
749        .is_nullable(new_column.is_nullable())
750        .default_constraint(new_column.default_constraint().cloned())
751        .build()
752        .context(error::BuildColumnDescriptorSnafu {
753            table_name,
754            column_name: &new_column.name,
755        })?;
756
757        // Bump next column id.
758        self.next_column_id += 1;
759
760        Ok(desc)
761    }
762
763    /// Create a [`TableMetaBuilder`] from the current TableMeta.
764    fn new_meta_builder(&self) -> TableMetaBuilder {
765        let mut builder = TableMetaBuilder::from(self);
766        // Manually remove value_indices.
767        builder.value_indices = None;
768        builder
769    }
770
771    // TODO(yingwen): Tests add if not exists.
772    fn add_columns(
773        &self,
774        table_name: &str,
775        requests: &[AddColumnRequest],
776    ) -> Result<TableMetaBuilder> {
777        let table_schema = &self.schema;
778        let mut meta_builder = self.new_meta_builder();
779        let original_primary_key_indices: HashSet<&usize> =
780            self.primary_key_indices.iter().collect();
781
782        let mut names = HashSet::with_capacity(requests.len());
783        let mut new_columns = Vec::with_capacity(requests.len());
784        for col_to_add in requests {
785            if let Some(column_schema) =
786                table_schema.column_schema_by_name(&col_to_add.column_schema.name)
787            {
788                // If the column already exists.
789                ensure!(
790                    col_to_add.add_if_not_exists,
791                    error::ColumnExistsSnafu {
792                        table_name,
793                        column_name: &col_to_add.column_schema.name
794                    },
795                );
796
797                // Checks if the type is the same
798                ensure!(
799                    column_schema.data_type == col_to_add.column_schema.data_type,
800                    error::InvalidAlterRequestSnafu {
801                        table: table_name,
802                        err: format!(
803                            "column {} already exists with different type {:?}",
804                            col_to_add.column_schema.name, column_schema.data_type,
805                        ),
806                    }
807                );
808            } else {
809                // A new column.
810                // Ensures we only add a column once.
811                ensure!(
812                    names.insert(&col_to_add.column_schema.name),
813                    error::InvalidAlterRequestSnafu {
814                        table: table_name,
815                        err: format!(
816                            "add column {} more than once",
817                            col_to_add.column_schema.name
818                        ),
819                    }
820                );
821
822                ensure!(
823                    col_to_add.column_schema.is_nullable()
824                        || col_to_add.column_schema.default_constraint().is_some(),
825                    error::InvalidAlterRequestSnafu {
826                        table: table_name,
827                        err: format!(
828                            "no default value for column {}",
829                            col_to_add.column_schema.name
830                        ),
831                    },
832                );
833
834                // A dropped column may leave a stale entity declaration behind;
835                // re-adding it must not hand the declaration a type without a
836                // stable string form.
837                if !has_stable_string_form(&col_to_add.column_schema.data_type)
838                    && let Some(key) =
839                        entity_option_referencing(&self.options, &col_to_add.column_schema.name)
840                {
841                    return error::InvalidAlterRequestSnafu {
842                        table: table_name,
843                        err: format!(
844                            "column `{}` is referenced by entity option `{key}` and must \
845                             keep a type that renders as a string, got `{}`",
846                            col_to_add.column_schema.name, col_to_add.column_schema.data_type
847                        ),
848                    }
849                    .fail();
850                }
851
852                new_columns.push(col_to_add.clone());
853            }
854        }
855        let requests = &new_columns[..];
856
857        let SplitResult {
858            columns_at_first,
859            columns_at_after,
860            columns_at_last,
861            column_names,
862        } = self.split_requests_by_column_location(table_name, requests)?;
863        let mut primary_key_indices = Vec::with_capacity(self.primary_key_indices.len());
864        let mut columns = Vec::with_capacity(table_schema.num_columns() + requests.len());
865        // add new columns with FIRST, and in reverse order of requests.
866        columns_at_first.iter().rev().for_each(|request| {
867            if request.is_key {
868                // If a key column is added, we also need to store its index in primary_key_indices.
869                primary_key_indices.push(columns.len());
870            }
871            columns.push(request.column_schema.clone());
872        });
873        // add existed columns in original order and handle new columns with AFTER.
874        for (index, column_schema) in table_schema.column_schemas().iter().enumerate() {
875            if original_primary_key_indices.contains(&index) {
876                primary_key_indices.push(columns.len());
877            }
878            columns.push(column_schema.clone());
879            if let Some(requests) = columns_at_after.get(&column_schema.name) {
880                requests.iter().rev().for_each(|request| {
881                    if request.is_key {
882                        // If a key column is added, we also need to store its index in primary_key_indices.
883                        primary_key_indices.push(columns.len());
884                    }
885                    columns.push(request.column_schema.clone());
886                });
887            }
888        }
889        // add new columns without location info to last.
890        columns_at_last.iter().for_each(|request| {
891            if request.is_key {
892                // If a key column is added, we also need to store its index in primary_key_indices.
893                primary_key_indices.push(columns.len());
894            }
895            columns.push(request.column_schema.clone());
896        });
897
898        let mut builder = SchemaBuilder::try_from(columns)
899            .with_context(|_| error::SchemaBuildSnafu {
900                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
901            })?
902            // Also bump the schema version.
903            .version(table_schema.version() + 1);
904        for (k, v) in table_schema.metadata().iter() {
905            builder = builder.add_metadata(k, v);
906        }
907        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
908            msg: format!("Table {table_name} cannot add new columns {column_names:?}"),
909        })?;
910
911        let partition_key_indices = self
912            .partition_key_indices
913            .iter()
914            .map(|idx| table_schema.column_name_by_index(*idx))
915            // This unwrap is safe since we only add new columns.
916            .map(|name| new_schema.column_index_by_name(name).unwrap())
917            .collect();
918
919        // value_indices would be generated automatically.
920        let _ = meta_builder
921            .schema(Arc::new(new_schema))
922            .primary_key_indices(primary_key_indices)
923            .partition_key_indices(partition_key_indices);
924
925        Ok(meta_builder)
926    }
927
928    fn remove_columns(
929        &self,
930        table_name: &str,
931        column_names: &[String],
932    ) -> Result<TableMetaBuilder> {
933        let table_schema = &self.schema;
934        let column_names: HashSet<_> = column_names.iter().collect();
935        let mut meta_builder = self.new_meta_builder();
936
937        let timestamp_index = table_schema.timestamp_index();
938        // Check whether columns are existing and not in primary key index.
939        for column_name in &column_names {
940            if let Some(index) = table_schema.column_index_by_name(column_name) {
941                // This is a linear search, but since there won't be too much columns, the performance should
942                // be acceptable.
943                ensure!(
944                    !self.primary_key_indices.contains(&index),
945                    error::RemoveColumnInIndexSnafu {
946                        column_name: *column_name,
947                        table_name,
948                    }
949                );
950
951                ensure!(
952                    !self.partition_key_indices.contains(&index),
953                    error::RemovePartitionColumnSnafu {
954                        column_name: *column_name,
955                        table_name,
956                    }
957                );
958
959                if let Some(ts_index) = timestamp_index {
960                    // Not allowed to remove column in timestamp index.
961                    ensure!(
962                        index != ts_index,
963                        error::RemoveColumnInIndexSnafu {
964                            column_name: table_schema.column_name_by_index(ts_index),
965                            table_name,
966                        }
967                    );
968                }
969            } else {
970                return error::ColumnNotExistsSnafu {
971                    column_name: *column_name,
972                    table_name,
973                }
974                .fail()?;
975            }
976        }
977
978        // Collect columns after removal.
979        let columns: Vec<_> = table_schema
980            .column_schemas()
981            .iter()
982            .filter(|column_schema| !column_names.contains(&column_schema.name))
983            .cloned()
984            .collect();
985
986        let mut builder = SchemaBuilder::try_from_columns(columns)
987            .with_context(|_| error::SchemaBuildSnafu {
988                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
989            })?
990            // Also bump the schema version.
991            .version(table_schema.version() + 1);
992        for (k, v) in table_schema.metadata().iter() {
993            builder = builder.add_metadata(k, v);
994        }
995        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
996            msg: format!("Table {table_name} cannot add remove columns {column_names:?}"),
997        })?;
998
999        // Rebuild the indices of primary key columns.
1000        let primary_key_indices = self
1001            .primary_key_indices
1002            .iter()
1003            .map(|idx| table_schema.column_name_by_index(*idx))
1004            // This unwrap is safe since we don't allow removing a primary key column.
1005            .map(|name| new_schema.column_index_by_name(name).unwrap())
1006            .collect();
1007
1008        let partition_key_indices = self
1009            .partition_key_indices
1010            .iter()
1011            .map(|idx| table_schema.column_name_by_index(*idx))
1012            // This unwrap is safe since we don't allow removing a partition key column.
1013            .map(|name| new_schema.column_index_by_name(name).unwrap())
1014            .collect();
1015
1016        let _ = meta_builder
1017            .schema(Arc::new(new_schema))
1018            .primary_key_indices(primary_key_indices)
1019            .partition_key_indices(partition_key_indices);
1020
1021        Ok(meta_builder)
1022    }
1023
1024    fn modify_column_types(
1025        &self,
1026        table_name: &str,
1027        requests: &[ModifyColumnTypeRequest],
1028    ) -> Result<TableMetaBuilder> {
1029        let table_schema = &self.schema;
1030        let mut meta_builder = self.new_meta_builder();
1031
1032        let mut modify_column_types = HashMap::with_capacity(requests.len());
1033        let timestamp_index = table_schema.timestamp_index();
1034
1035        for col_to_change in requests {
1036            let change_column_name = &col_to_change.column_name;
1037
1038            let index = table_schema
1039                .column_index_by_name(change_column_name)
1040                .with_context(|| error::ColumnNotExistsSnafu {
1041                    column_name: change_column_name,
1042                    table_name,
1043                })?;
1044
1045            // A column referenced by an entity declaration may be dropped (the
1046            // read-time derivation skips the stale declaration), but must not
1047            // change to a type without a stable string form. Checked after the
1048            // existence lookup so a missing column keeps reporting
1049            // `ColumnNotExists` regardless of stale declarations.
1050            if !has_stable_string_form(&col_to_change.target_type)
1051                && let Some(key) = entity_option_referencing(&self.options, change_column_name)
1052            {
1053                return error::InvalidAlterRequestSnafu {
1054                    table: table_name,
1055                    err: format!(
1056                        "column `{change_column_name}` is referenced by entity option \
1057                         `{key}` and must keep a type that renders as a string, got `{}`",
1058                        col_to_change.target_type
1059                    ),
1060                }
1061                .fail();
1062            }
1063
1064            let column = &table_schema.column_schemas()[index];
1065
1066            ensure!(
1067                !self.primary_key_indices.contains(&index),
1068                error::InvalidAlterRequestSnafu {
1069                    table: table_name,
1070                    err: format!(
1071                        "Not allowed to change primary key index column '{}'",
1072                        column.name
1073                    )
1074                }
1075            );
1076
1077            let is_time_index = timestamp_index == Some(index);
1078            if is_time_index {
1079                // The time index column is NOT NULL by construction and only
1080                // supports widening its unit; historical data in SSTs is cast
1081                // to the new unit on read, so no backfill is needed.
1082                ensure!(
1083                    column
1084                        .data_type
1085                        .is_timestamp_unit_widening_to(&col_to_change.target_type),
1086                    error::InvalidAlterRequestSnafu {
1087                        table: table_name,
1088                        err: time_index_not_widening_error(
1089                            &column.name,
1090                            &column.data_type,
1091                            &col_to_change.target_type,
1092                        ),
1093                    }
1094                );
1095            } else {
1096                ensure!(
1097                    column
1098                        .data_type
1099                        .can_arrow_type_cast_to(&col_to_change.target_type),
1100                    error::InvalidAlterRequestSnafu {
1101                        table: table_name,
1102                        err: format!(
1103                            "column '{}' cannot be cast automatically to type '{}'",
1104                            col_to_change.column_name, col_to_change.target_type,
1105                        ),
1106                    }
1107                );
1108
1109                ensure!(
1110                    column.is_nullable(),
1111                    error::InvalidAlterRequestSnafu {
1112                        table: table_name,
1113                        err: format!(
1114                            "column '{}' must be nullable to ensure safe conversion.",
1115                            col_to_change.column_name,
1116                        ),
1117                    }
1118                );
1119            }
1120            ensure!(
1121                modify_column_types
1122                    .insert(&col_to_change.column_name, col_to_change)
1123                    .is_none(),
1124                error::InvalidAlterRequestSnafu {
1125                    table: table_name,
1126                    err: format!(
1127                        "change column datatype {} more than once",
1128                        col_to_change.column_name
1129                    ),
1130                }
1131            );
1132        }
1133        // Collect columns after changed.
1134
1135        let mut columns: Vec<_> = Vec::with_capacity(table_schema.column_schemas().len());
1136        for mut column in table_schema.column_schemas().iter().cloned() {
1137            if let Some(change_column) = modify_column_types.get(&column.name) {
1138                column.data_type = change_column.target_type.clone();
1139                let new_default = if let Some(default_value) = column.default_constraint() {
1140                    Some(
1141                        default_value
1142                            .cast_to_datatype(&change_column.target_type)
1143                            .with_context(|_| error::CastDefaultValueSnafu {
1144                                reason: format!(
1145                                    "Failed to cast default value from {:?} to type {:?}",
1146                                    default_value, change_column.target_type
1147                                ),
1148                            })?,
1149                    )
1150                } else {
1151                    None
1152                };
1153                column = column
1154                    .clone()
1155                    .with_default_constraint(new_default.clone())
1156                    .with_context(|_| error::CastDefaultValueSnafu {
1157                        reason: format!("Failed to set new default: {:?}", new_default),
1158                    })?;
1159            }
1160            columns.push(column)
1161        }
1162
1163        let mut builder = SchemaBuilder::try_from_columns(columns)
1164            .with_context(|_| error::SchemaBuildSnafu {
1165                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1166            })?
1167            // Also bump the schema version.
1168            .version(table_schema.version() + 1);
1169        for (k, v) in table_schema.metadata().iter() {
1170            builder = builder.add_metadata(k, v);
1171        }
1172        let new_schema = builder.build().with_context(|_| {
1173            let column_names: Vec<_> = requests
1174                .iter()
1175                .map(|request| &request.column_name)
1176                .collect();
1177
1178            error::SchemaBuildSnafu {
1179                msg: format!(
1180                    "Table {table_name} cannot change datatype with columns {column_names:?}"
1181                ),
1182            }
1183        })?;
1184
1185        let _ = meta_builder
1186            .schema(Arc::new(new_schema))
1187            .primary_key_indices(self.primary_key_indices.clone());
1188
1189        Ok(meta_builder)
1190    }
1191
1192    fn set_json_settings(
1193        &self,
1194        table_name: &str,
1195        req: &SetJsonSettingsRequest,
1196    ) -> Result<TableMetaBuilder> {
1197        let table_schema = &self.schema;
1198        let idx = table_schema
1199            .column_index_by_name(&req.column_name)
1200            .with_context(|| error::ColumnNotExistsSnafu {
1201                column_name: &req.column_name,
1202                table_name,
1203            })?;
1204        let col = &table_schema.column_schemas()[idx];
1205
1206        ensure!(
1207            !self.primary_key_indices.contains(&idx) && table_schema.timestamp_index() != Some(idx),
1208            error::InvalidAlterRequestSnafu {
1209                table: table_name,
1210                err: format!(
1211                    "Not allowed to change JSON settings for key or timestamp column '{}'",
1212                    col.name
1213                ),
1214            }
1215        );
1216        ensure!(
1217            col.data_type.is_json2(),
1218            error::InvalidAlterRequestSnafu {
1219                table: table_name,
1220                err: format!("column '{}' is not a JSON2 column", col.name),
1221            }
1222        );
1223
1224        let target_metadata =
1225            json2_metadata_with_updated_settings(col.metadata(), req.settings.clone()).map_err(
1226                |err| {
1227                    error::InvalidAlterRequestSnafu {
1228                        table: table_name,
1229                        err: err.to_string(),
1230                    }
1231                    .build()
1232                },
1233            )?;
1234
1235        let mut cols = table_schema.column_schemas().to_vec();
1236        *cols[idx].mut_metadata() = target_metadata;
1237
1238        let mut builder = SchemaBuilder::try_from_columns(cols)
1239            .with_context(|_| error::SchemaBuildSnafu {
1240                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1241            })?
1242            .version(table_schema.version() + 1);
1243
1244        for (k, v) in table_schema.metadata().iter() {
1245            builder = builder.add_metadata(k, v);
1246        }
1247
1248        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1249            msg: format!(
1250                "Table {table_name} cannot change JSON settings for column {}",
1251                req.column_name
1252            ),
1253        })?;
1254
1255        let mut meta_builder = self.new_meta_builder();
1256        let _ = meta_builder
1257            .schema(Arc::new(new_schema))
1258            .primary_key_indices(self.primary_key_indices.clone());
1259
1260        Ok(meta_builder)
1261    }
1262
1263    /// Split requests into different groups using column location info.
1264    fn split_requests_by_column_location<'a>(
1265        &self,
1266        table_name: &str,
1267        requests: &'a [AddColumnRequest],
1268    ) -> Result<SplitResult<'a>> {
1269        let table_schema = &self.schema;
1270        let mut columns_at_first = Vec::new();
1271        let mut columns_at_after = HashMap::new();
1272        let mut columns_at_last = Vec::new();
1273        let mut column_names = Vec::with_capacity(requests.len());
1274        for request in requests {
1275            // Check whether columns to add are already existing.
1276            let column_name = &request.column_schema.name;
1277            column_names.push(column_name.clone());
1278            ensure!(
1279                table_schema.column_schema_by_name(column_name).is_none(),
1280                error::ColumnExistsSnafu {
1281                    column_name,
1282                    table_name,
1283                }
1284            );
1285            match request.location.as_ref() {
1286                Some(AddColumnLocation::First) => {
1287                    columns_at_first.push(request);
1288                }
1289                Some(AddColumnLocation::After { column_name }) => {
1290                    ensure!(
1291                        table_schema.column_schema_by_name(column_name).is_some(),
1292                        error::ColumnNotExistsSnafu {
1293                            column_name,
1294                            table_name,
1295                        }
1296                    );
1297                    columns_at_after
1298                        .entry(column_name.clone())
1299                        .or_insert(Vec::new())
1300                        .push(request);
1301                }
1302                None => {
1303                    columns_at_last.push(request);
1304                }
1305            }
1306        }
1307        Ok(SplitResult {
1308            columns_at_first,
1309            columns_at_after,
1310            columns_at_last,
1311            column_names,
1312        })
1313    }
1314
1315    fn drop_defaults(&self, table_name: &str, column_names: &[String]) -> Result<TableMetaBuilder> {
1316        let table_schema = &self.schema;
1317        let mut meta_builder = self.new_meta_builder();
1318        let mut columns = Vec::with_capacity(table_schema.num_columns());
1319        for column_schema in table_schema.column_schemas() {
1320            if let Some(name) = column_names.iter().find(|s| **s == column_schema.name) {
1321                // Drop default constraint.
1322                ensure!(
1323                    column_schema.default_constraint().is_some(),
1324                    error::InvalidAlterRequestSnafu {
1325                        table: table_name,
1326                        err: format!("column {name} does not have a default value"),
1327                    }
1328                );
1329                if !column_schema.is_nullable() {
1330                    return error::InvalidAlterRequestSnafu {
1331                        table: table_name,
1332                        err: format!(
1333                            "column {name} is not nullable and `default` cannot be dropped",
1334                        ),
1335                    }
1336                    .fail();
1337                }
1338                let new_column_schema = column_schema.clone();
1339                let new_column_schema = new_column_schema
1340                    .with_default_constraint(None)
1341                    .with_context(|_| error::SchemaBuildSnafu {
1342                        msg: format!("Table {table_name} cannot drop default values"),
1343                    })?;
1344                columns.push(new_column_schema);
1345            } else {
1346                columns.push(column_schema.clone());
1347            }
1348        }
1349
1350        let mut builder = SchemaBuilder::try_from_columns(columns)
1351            .with_context(|_| error::SchemaBuildSnafu {
1352                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1353            })?
1354            // Also bump the schema version.
1355            .version(table_schema.version() + 1);
1356        for (k, v) in table_schema.metadata().iter() {
1357            builder = builder.add_metadata(k, v);
1358        }
1359        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1360            msg: format!("Table {table_name} cannot drop default values"),
1361        })?;
1362
1363        let _ = meta_builder.schema(Arc::new(new_schema));
1364
1365        Ok(meta_builder)
1366    }
1367
1368    fn set_defaults(
1369        &self,
1370        table_name: &str,
1371        set_defaults: &[SetDefaultRequest],
1372    ) -> Result<TableMetaBuilder> {
1373        let table_schema = &self.schema;
1374        let mut meta_builder = self.new_meta_builder();
1375        let mut columns = Vec::with_capacity(table_schema.num_columns());
1376        for column_schema in table_schema.column_schemas() {
1377            if let Some(set_default) = set_defaults
1378                .iter()
1379                .find(|s| s.column_name == column_schema.name)
1380            {
1381                let new_column_schema = column_schema.clone();
1382                let new_column_schema = new_column_schema
1383                    .with_default_constraint(set_default.default_constraint.clone())
1384                    .with_context(|_| error::SchemaBuildSnafu {
1385                        msg: format!("Table {table_name} cannot set default values"),
1386                    })?;
1387                columns.push(new_column_schema);
1388            } else {
1389                columns.push(column_schema.clone());
1390            }
1391        }
1392
1393        let mut builder = SchemaBuilder::try_from_columns(columns)
1394            .with_context(|_| error::SchemaBuildSnafu {
1395                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1396            })?
1397            // Also bump the schema version.
1398            .version(table_schema.version() + 1);
1399        for (k, v) in table_schema.metadata().iter() {
1400            builder = builder.add_metadata(k, v);
1401        }
1402        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1403            msg: format!("Table {table_name} cannot set default values"),
1404        })?;
1405
1406        let _ = meta_builder.schema(Arc::new(new_schema));
1407
1408        Ok(meta_builder)
1409    }
1410}
1411
1412#[derive(Clone, Debug, PartialEq, Eq, Builder, Serialize, Deserialize)]
1413#[builder(pattern = "owned")]
1414pub struct TableInfo {
1415    /// Id and version of the table.
1416    #[builder(default, setter(into))]
1417    pub ident: TableIdent,
1418    /// Name of the table.
1419    #[builder(setter(into))]
1420    pub name: String,
1421    /// Comment of the table.
1422    #[builder(default, setter(into))]
1423    pub desc: Option<String>,
1424    #[builder(default = "DEFAULT_CATALOG_NAME.to_string()", setter(into))]
1425    pub catalog_name: String,
1426    #[builder(default = "DEFAULT_SCHEMA_NAME.to_string()", setter(into))]
1427    pub schema_name: String,
1428    pub meta: TableMeta,
1429    #[builder(default = "TableType::Base")]
1430    pub table_type: TableType,
1431}
1432
1433pub type TableInfoRef = Arc<TableInfo>;
1434
1435impl TableInfo {
1436    pub fn table_id(&self) -> TableId {
1437        self.ident.table_id
1438    }
1439
1440    /// Returns the full table name in the form of `{catalog}.{schema}.{table}`.
1441    pub fn full_table_name(&self) -> String {
1442        common_catalog::format_full_table_name(&self.catalog_name, &self.schema_name, &self.name)
1443    }
1444
1445    pub fn get_db_string(&self) -> String {
1446        common_catalog::build_db_string(&self.catalog_name, &self.schema_name)
1447    }
1448
1449    /// Returns true when the table is the metric engine's physical table.
1450    pub fn is_physical_table(&self) -> bool {
1451        self.meta
1452            .options
1453            .extra_options
1454            .contains_key(PHYSICAL_TABLE_METADATA_KEY)
1455    }
1456
1457    /// Return true if the table's TTL is `instant`.
1458    pub fn is_ttl_instant_table(&self) -> bool {
1459        self.meta
1460            .options
1461            .ttl
1462            .map(|t| t.is_instant())
1463            .unwrap_or(false)
1464    }
1465}
1466
1467impl TableInfoBuilder {
1468    pub fn new<S: Into<String>>(name: S, meta: TableMeta) -> Self {
1469        Self {
1470            name: Some(name.into()),
1471            meta: Some(meta),
1472            ..Default::default()
1473        }
1474    }
1475
1476    pub fn table_id(mut self, id: TableId) -> Self {
1477        let ident = self.ident.get_or_insert_with(TableIdent::default);
1478        ident.table_id = id;
1479        self
1480    }
1481
1482    pub fn table_version(mut self, version: TableVersion) -> Self {
1483        let ident = self.ident.get_or_insert_with(TableIdent::default);
1484        ident.version = version;
1485        self
1486    }
1487}
1488
1489impl TableIdent {
1490    pub fn new(table_id: TableId) -> Self {
1491        Self {
1492            table_id,
1493            version: 0,
1494        }
1495    }
1496}
1497
1498impl From<TableId> for TableIdent {
1499    fn from(table_id: TableId) -> Self {
1500        Self::new(table_id)
1501    }
1502}
1503
1504impl TableInfo {
1505    /// Returns the map of column name to column id.
1506    ///
1507    /// Note: This method may return an empty map for older versions that did not include this field.
1508    pub fn name_to_ids(&self) -> Option<HashMap<String, ColumnId>> {
1509        let column_schemas = self.meta.schema.column_schemas();
1510        if self.meta.column_ids.len() != column_schemas.len() {
1511            None
1512        } else {
1513            Some(
1514                self.meta
1515                    .column_ids
1516                    .iter()
1517                    .enumerate()
1518                    .map(|(index, id)| (column_schemas[index].name.clone(), *id))
1519                    .collect(),
1520            )
1521        }
1522    }
1523
1524    /// Sort the columns in [TableInfo], logical tables require it.
1525    pub fn sort_columns(&mut self) {
1526        let column_schemas = self.meta.schema.column_schemas();
1527        let primary_keys = self
1528            .meta
1529            .primary_key_indices
1530            .iter()
1531            .map(|index| column_schemas[*index].name.clone())
1532            .collect::<HashSet<_>>();
1533
1534        let name_to_ids = self.name_to_ids().unwrap_or_default();
1535        let mut column_schemas = column_schemas.to_vec();
1536        column_schemas.sort_unstable_by(|a, b| a.name.cmp(&b.name));
1537
1538        // Compute new indices of sorted columns
1539        let mut primary_key_indices = Vec::with_capacity(primary_keys.len());
1540        let mut value_indices = Vec::with_capacity(column_schemas.len() - primary_keys.len());
1541        let mut column_ids = Vec::with_capacity(column_schemas.len());
1542        for (index, column_schema) in column_schemas.iter().enumerate() {
1543            if primary_keys.contains(&column_schema.name) {
1544                primary_key_indices.push(index);
1545            } else {
1546                value_indices.push(index);
1547            }
1548            if let Some(id) = name_to_ids.get(&column_schema.name) {
1549                column_ids.push(*id);
1550            }
1551        }
1552
1553        // Overwrite table meta
1554        self.meta.schema = Arc::new(Schema::new_with_version(
1555            column_schemas,
1556            self.meta.schema.version(),
1557        ));
1558        self.meta.primary_key_indices = primary_key_indices;
1559        self.meta.value_indices = value_indices;
1560        self.meta.column_ids = column_ids;
1561    }
1562
1563    /// Extracts region options from table info.
1564    ///
1565    /// All "region options" are actually a copy of table options for redundancy.
1566    pub fn to_region_options(&self) -> HashMap<String, String> {
1567        let mut options = HashMap::from(&self.meta.options);
1568        options.remove(REPARTITION_COLUMN_HINT_KEY);
1569        options.remove(REPARTITION_PARTITION_NUM_HINT_KEY);
1570        options
1571    }
1572
1573    /// Returns the table reference.
1574    pub fn table_ref(&self) -> TableReference<'_> {
1575        TableReference::full(
1576            self.catalog_name.as_str(),
1577            self.schema_name.as_str(),
1578            self.name.as_str(),
1579        )
1580    }
1581}
1582
1583fn entity_option_referencing<'a>(options: &'a TableOptions, column: &str) -> Option<&'a str> {
1584    options.extra_options.iter().find_map(|(key, value)| {
1585        (parse_entity_option_key(key).is_some()
1586            && parse_entity_columns(value).iter().any(|c| c == column))
1587        .then_some(key.as_str())
1588    })
1589}
1590
1591/// Set column fulltext options if it passed the validation.
1592///
1593/// Options allowed to modify:
1594/// * backend
1595///
1596/// Options not allowed to modify:
1597/// * analyzer
1598/// * case_sensitive
1599fn set_column_fulltext_options(
1600    column_schema: &mut ColumnSchema,
1601    column_name: &str,
1602    options: &FulltextOptions,
1603    current_options: Option<FulltextOptions>,
1604) -> Result<()> {
1605    if let Some(current_options) = current_options {
1606        ensure!(
1607            current_options.analyzer == options.analyzer
1608                && current_options.case_sensitive == options.case_sensitive,
1609            error::InvalidColumnOptionSnafu {
1610                column_name,
1611                msg: format!(
1612                    "Cannot change analyzer or case_sensitive if FULLTEXT index is set before. Previous analyzer: {}, previous case_sensitive: {}",
1613                    current_options.analyzer, current_options.case_sensitive
1614                ),
1615            }
1616        );
1617    }
1618
1619    column_schema
1620        .set_fulltext_options(options)
1621        .context(error::SetFulltextOptionsSnafu { column_name })?;
1622
1623    Ok(())
1624}
1625
1626fn unset_column_fulltext_options(
1627    column_schema: &mut ColumnSchema,
1628    column_name: &str,
1629    current_options: Option<FulltextOptions>,
1630) -> Result<()> {
1631    ensure!(
1632        current_options
1633            .as_ref()
1634            .is_some_and(|options| options.enable),
1635        error::InvalidColumnOptionSnafu {
1636            column_name,
1637            msg: "FULLTEXT index already disabled".to_string(),
1638        }
1639    );
1640
1641    let mut options = current_options.unwrap();
1642    options.enable = false;
1643    column_schema
1644        .set_fulltext_options(&options)
1645        .context(error::SetFulltextOptionsSnafu { column_name })?;
1646
1647    Ok(())
1648}
1649
1650fn set_column_skipping_index_options(
1651    column_schema: &mut ColumnSchema,
1652    column_name: &str,
1653    options: &SkippingIndexOptions,
1654) -> Result<()> {
1655    column_schema
1656        .set_skipping_options(options)
1657        .context(error::SetSkippingOptionsSnafu { column_name })?;
1658
1659    Ok(())
1660}
1661
1662fn unset_column_skipping_index_options(
1663    column_schema: &mut ColumnSchema,
1664    column_name: &str,
1665) -> Result<()> {
1666    column_schema
1667        .unset_skipping_options()
1668        .context(error::UnsetSkippingOptionsSnafu { column_name })?;
1669    Ok(())
1670}
1671
1672#[cfg(test)]
1673mod tests {
1674    use std::assert_matches;
1675
1676    use common_error::ext::ErrorExt;
1677    use common_error::status_code::StatusCode;
1678    use datatypes::data_type::ConcreteDataType;
1679    use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
1680    use datatypes::json::JsonSettings;
1681    use datatypes::schema::{
1682        ColumnSchema, FulltextAnalyzer, FulltextBackend, Schema, SchemaBuilder,
1683    };
1684    use datatypes::types::JsonType;
1685
1686    use super::*;
1687    use crate::Error;
1688
1689    /// Create a test schema with 3 columns: `[col1 int32, ts timestampmills, col2 int32]`.
1690    fn new_test_schema() -> Schema {
1691        let column_schemas = vec![
1692            ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
1693            ColumnSchema::new(
1694                "ts",
1695                ConcreteDataType::timestamp_millisecond_datatype(),
1696                false,
1697            )
1698            .with_time_index(true),
1699            ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
1700        ];
1701        SchemaBuilder::try_from(column_schemas)
1702            .unwrap()
1703            .version(123)
1704            .build()
1705            .unwrap()
1706    }
1707
1708    fn add_columns_to_meta(meta: &TableMeta) -> TableMeta {
1709        let new_tag = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
1710        let new_field = ColumnSchema::new("my_field", ConcreteDataType::string_datatype(), true);
1711        let alter_kind = AlterKind::AddColumns {
1712            columns: vec![
1713                AddColumnRequest {
1714                    column_schema: new_tag,
1715                    is_key: true,
1716                    location: None,
1717                    add_if_not_exists: false,
1718                },
1719                AddColumnRequest {
1720                    column_schema: new_field,
1721                    is_key: false,
1722                    location: None,
1723                    add_if_not_exists: false,
1724                },
1725            ],
1726        };
1727
1728        let builder = meta
1729            .builder_with_alter_kind("my_table", &alter_kind)
1730            .unwrap();
1731        builder.build().unwrap()
1732    }
1733
1734    fn add_columns_to_meta_with_location(meta: &TableMeta) -> TableMeta {
1735        let new_tag = ColumnSchema::new("my_tag_first", ConcreteDataType::string_datatype(), true);
1736        let new_field = ColumnSchema::new(
1737            "my_field_after_ts",
1738            ConcreteDataType::string_datatype(),
1739            true,
1740        );
1741        let yet_another_field = ColumnSchema::new(
1742            "yet_another_field_after_ts",
1743            ConcreteDataType::int64_datatype(),
1744            true,
1745        );
1746        let alter_kind = AlterKind::AddColumns {
1747            columns: vec![
1748                AddColumnRequest {
1749                    column_schema: new_tag,
1750                    is_key: true,
1751                    location: Some(AddColumnLocation::First),
1752                    add_if_not_exists: false,
1753                },
1754                AddColumnRequest {
1755                    column_schema: new_field,
1756                    is_key: false,
1757                    location: Some(AddColumnLocation::After {
1758                        column_name: "ts".to_string(),
1759                    }),
1760                    add_if_not_exists: false,
1761                },
1762                AddColumnRequest {
1763                    column_schema: yet_another_field,
1764                    is_key: true,
1765                    location: Some(AddColumnLocation::After {
1766                        column_name: "ts".to_string(),
1767                    }),
1768                    add_if_not_exists: false,
1769                },
1770            ],
1771        };
1772
1773        let builder = meta
1774            .builder_with_alter_kind("my_table", &alter_kind)
1775            .unwrap();
1776        builder.build().unwrap()
1777    }
1778
1779    #[test]
1780    fn test_set_json_settings() {
1781        let schema = Arc::new(
1782            SchemaBuilder::try_from_columns(vec![
1783                ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
1784                ColumnSchema::new(
1785                    "ts",
1786                    ConcreteDataType::timestamp_millisecond_datatype(),
1787                    false,
1788                )
1789                .with_time_index(true),
1790                json2_column_schema_v1("payload", JsonSettings::new_v2()),
1791            ])
1792            .unwrap()
1793            .version(123)
1794            .build()
1795            .unwrap(),
1796        );
1797        let meta = TableMetaBuilder::empty()
1798            .schema(schema)
1799            .primary_key_indices(vec![0])
1800            .engine("engine")
1801            .next_column_id(3)
1802            .build()
1803            .unwrap();
1804
1805        let alter_kind = AlterKind::SetJsonSettings {
1806            request: SetJsonSettingsRequest {
1807                column_name: "payload".to_string(),
1808                settings: JsonSettings::try_new(vec![], Some(10)).unwrap(),
1809            },
1810        };
1811        let new_meta = meta
1812            .builder_with_alter_kind("my_table", &alter_kind)
1813            .unwrap()
1814            .build()
1815            .unwrap();
1816
1817        let payload = new_meta.schema.column_schema_by_name("payload").unwrap();
1818        let json_metadata: JsonMetadata =
1819            serde_json::from_str(payload.metadata().get("ARROW:extension:metadata").unwrap())
1820                .unwrap();
1821        assert!(json_metadata.is_version_2());
1822        assert_eq!(
1823            Some(10),
1824            json_metadata.json_settings().max_auto_expanded_paths()
1825        );
1826        assert_eq!(124, new_meta.schema.version());
1827        assert_eq!(meta.primary_key_indices, new_meta.primary_key_indices);
1828    }
1829
1830    fn json2_column_schema_v1(name: &str, settings: JsonSettings) -> ColumnSchema {
1831        let mut column_schema =
1832            ColumnSchema::new(name, ConcreteDataType::Json(JsonType::null()), true);
1833        column_schema.with_extension_type(&Json2ExtensionType::new(Arc::new(
1834            JsonMetadata::new_v1(settings),
1835        )));
1836        column_schema
1837    }
1838
1839    #[test]
1840    fn test_set_json_settings_rejects_non_json2_column() {
1841        let schema = Arc::new(new_test_schema());
1842        let meta = TableMetaBuilder::empty()
1843            .schema(schema)
1844            .primary_key_indices(vec![0])
1845            .engine("engine")
1846            .next_column_id(3)
1847            .build()
1848            .unwrap();
1849        let alter_kind = AlterKind::SetJsonSettings {
1850            request: SetJsonSettingsRequest {
1851                column_name: "col2".to_string(),
1852                settings: JsonSettings::new_v2(),
1853            },
1854        };
1855
1856        let err = match meta.builder_with_alter_kind("my_table", &alter_kind) {
1857            Ok(_) => panic!("expected modifying JSON settings on a non-JSON2 column to fail"),
1858            Err(err) => err,
1859        };
1860        assert!(err.to_string().contains("is not a JSON2 column"));
1861    }
1862
1863    #[test]
1864    fn test_modify_time_index_column_type() {
1865        let schema = Arc::new(new_test_schema());
1866        let meta = TableMetaBuilder::empty()
1867            .schema(schema)
1868            .primary_key_indices(vec![0])
1869            .engine("engine")
1870            .next_column_id(3)
1871            .build()
1872            .unwrap();
1873
1874        // Widening the time index unit is allowed.
1875        let alter_kind = AlterKind::ModifyColumnTypes {
1876            columns: vec![ModifyColumnTypeRequest {
1877                column_name: "ts".to_string(),
1878                target_type: ConcreteDataType::timestamp_microsecond_datatype(),
1879            }],
1880        };
1881        let new_meta = meta
1882            .builder_with_alter_kind("my_table", &alter_kind)
1883            .unwrap()
1884            .build()
1885            .unwrap();
1886        let ts_column = new_meta.schema.column_schema_by_name("ts").unwrap();
1887        assert_eq!(
1888            ConcreteDataType::timestamp_microsecond_datatype(),
1889            ts_column.data_type
1890        );
1891        assert!(ts_column.is_time_index());
1892        assert!(!ts_column.is_nullable());
1893        assert_eq!(new_meta.schema.version(), 124);
1894        assert_eq!(&[0], &new_meta.primary_key_indices[..]);
1895
1896        // Any non-widening change (narrowing, same type, non-timestamp) is
1897        // rejected.
1898        for target in [
1899            ConcreteDataType::timestamp_second_datatype(),
1900            ConcreteDataType::timestamp_millisecond_datatype(),
1901            ConcreteDataType::string_datatype(),
1902        ] {
1903            let alter_kind = AlterKind::ModifyColumnTypes {
1904                columns: vec![ModifyColumnTypeRequest {
1905                    column_name: "ts".to_string(),
1906                    target_type: target.clone(),
1907                }],
1908            };
1909            let res = meta.builder_with_alter_kind("my_table", &alter_kind);
1910            assert!(res.is_err(), "expected rejection for {target}");
1911        }
1912    }
1913
1914    #[test]
1915    fn test_add_columns() {
1916        let schema = Arc::new(new_test_schema());
1917        let meta = TableMetaBuilder::empty()
1918            .schema(schema)
1919            .primary_key_indices(vec![0])
1920            .engine("engine")
1921            .next_column_id(3)
1922            .build()
1923            .unwrap();
1924
1925        let new_meta = add_columns_to_meta(&meta);
1926        let names: Vec<String> = new_meta
1927            .schema
1928            .column_schemas()
1929            .iter()
1930            .map(|column_schema| column_schema.name.clone())
1931            .collect();
1932        assert_eq!(&["col1", "ts", "col2", "my_tag", "my_field"], &names[..]);
1933        assert_eq!(&[0, 3], &new_meta.primary_key_indices[..]);
1934        assert_eq!(&[1, 2, 4], &new_meta.value_indices[..]);
1935    }
1936
1937    #[test]
1938    fn test_set_append_mode_true_clears_merge_mode_option() {
1939        let schema = Arc::new(new_test_schema());
1940        let mut table_options = TableOptions::default();
1941        table_options
1942            .extra_options
1943            .insert(MERGE_MODE_KEY.to_string(), "last_non_null".to_string());
1944        let meta = TableMetaBuilder::empty()
1945            .schema(schema)
1946            .primary_key_indices(vec![0])
1947            .engine("engine")
1948            .next_column_id(3)
1949            .options(table_options)
1950            .build()
1951            .unwrap();
1952
1953        let alter_kind = AlterKind::SetTableOptions {
1954            options: vec![SetRegionOption::AppendMode(true)],
1955        };
1956        let new_meta = meta
1957            .builder_with_alter_kind("my_table", &alter_kind)
1958            .unwrap()
1959            .build()
1960            .unwrap();
1961
1962        assert_eq!(
1963            Some("true"),
1964            new_meta
1965                .options
1966                .extra_options
1967                .get(APPEND_MODE_KEY)
1968                .map(String::as_str)
1969        );
1970        assert!(!new_meta.options.extra_options.contains_key(MERGE_MODE_KEY));
1971    }
1972
1973    #[test]
1974    fn test_set_append_mode_false_keeps_merge_mode_option() {
1975        let schema = Arc::new(new_test_schema());
1976        let mut table_options = TableOptions::default();
1977        table_options
1978            .extra_options
1979            .insert(MERGE_MODE_KEY.to_string(), "last_non_null".to_string());
1980        let meta = TableMetaBuilder::empty()
1981            .schema(schema)
1982            .primary_key_indices(vec![0])
1983            .engine("engine")
1984            .next_column_id(3)
1985            .options(table_options)
1986            .build()
1987            .unwrap();
1988
1989        let alter_kind = AlterKind::SetTableOptions {
1990            options: vec![SetRegionOption::AppendMode(false)],
1991        };
1992        let new_meta = meta
1993            .builder_with_alter_kind("my_table", &alter_kind)
1994            .unwrap()
1995            .build()
1996            .unwrap();
1997
1998        assert_eq!(
1999            Some("false"),
2000            new_meta
2001                .options
2002                .extra_options
2003                .get(APPEND_MODE_KEY)
2004                .map(String::as_str)
2005        );
2006        assert_eq!(
2007            Some("last_non_null"),
2008            new_meta
2009                .options
2010                .extra_options
2011                .get(MERGE_MODE_KEY)
2012                .map(String::as_str)
2013        );
2014    }
2015
2016    #[test]
2017    fn test_set_preserve_row_sequence_option() {
2018        let schema = Arc::new(new_test_schema());
2019        let meta = TableMetaBuilder::empty()
2020            .schema(schema)
2021            .primary_key_indices(vec![0])
2022            .engine("engine")
2023            .next_column_id(3)
2024            .build()
2025            .unwrap();
2026
2027        let apply = |meta: &TableMeta, kind: &AlterKind| {
2028            meta.builder_with_alter_kind("my_table", kind)
2029                .unwrap()
2030                .build()
2031                .unwrap()
2032        };
2033
2034        let with_true = apply(
2035            &meta,
2036            &AlterKind::SetTableOptions {
2037                options: vec![SetRegionOption::PreserveRowSequence(true)],
2038            },
2039        );
2040        assert_eq!(
2041            Some("true"),
2042            with_true
2043                .options
2044                .extra_options
2045                .get(PRESERVE_ROW_SEQUENCE)
2046                .map(String::as_str)
2047        );
2048
2049        let set_false = apply(
2050            &with_true,
2051            &AlterKind::SetTableOptions {
2052                options: vec![SetRegionOption::PreserveRowSequence(false)],
2053            },
2054        );
2055        assert!(
2056            !set_false
2057                .options
2058                .extra_options
2059                .contains_key(PRESERVE_ROW_SEQUENCE)
2060        );
2061
2062        let unset = apply(
2063            &with_true,
2064            &AlterKind::UnsetTableOptions {
2065                keys: vec![UnsetRegionOption::PreserveRowSequence],
2066            },
2067        );
2068        assert!(
2069            !unset
2070                .options
2071                .extra_options
2072                .contains_key(PRESERVE_ROW_SEQUENCE)
2073        );
2074    }
2075
2076    #[test]
2077    fn test_set_skip_wal_updates_typed_and_extra_options() {
2078        let mut meta = TableMetaBuilder::empty()
2079            .schema(Arc::new(new_test_schema()))
2080            .primary_key_indices(vec![0])
2081            .engine("engine")
2082            .next_column_id(3)
2083            .build()
2084            .unwrap();
2085        meta.options
2086            .extra_options
2087            .insert(SKIP_WAL_KEY.to_string(), false.to_string());
2088
2089        let alter_kind = AlterKind::SetTableOptions {
2090            options: vec![SetRegionOption::SkipWal(true)],
2091        };
2092        let new_meta = meta
2093            .builder_with_alter_kind("my_table", &alter_kind)
2094            .unwrap()
2095            .build()
2096            .unwrap();
2097
2098        assert!(new_meta.options.skip_wal);
2099        assert_eq!(
2100            Some("true"),
2101            new_meta
2102                .options
2103                .extra_options
2104                .get(SKIP_WAL_KEY)
2105                .map(String::as_str)
2106        );
2107
2108        let alter_kind = AlterKind::SetTableOptions {
2109            options: vec![SetRegionOption::SkipWal(false)],
2110        };
2111        let new_meta = new_meta
2112            .builder_with_alter_kind("my_table", &alter_kind)
2113            .unwrap()
2114            .build()
2115            .unwrap();
2116
2117        assert!(!new_meta.options.skip_wal);
2118        assert_eq!(
2119            Some("false"),
2120            new_meta
2121                .options
2122                .extra_options
2123                .get(SKIP_WAL_KEY)
2124                .map(String::as_str)
2125        );
2126    }
2127
2128    #[test]
2129    fn test_set_unset_repartition_hints_together() {
2130        let meta = TableMetaBuilder::empty()
2131            .schema(Arc::new(new_test_schema()))
2132            .primary_key_indices(vec![0])
2133            .engine("engine")
2134            .next_column_id(3)
2135            .build()
2136            .unwrap();
2137        let options = vec![
2138            (REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string()),
2139            (
2140                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2141                "10".to_string(),
2142            ),
2143        ];
2144        for reverse in [false, true] {
2145            let mut options = options.clone();
2146            if reverse {
2147                options.reverse();
2148            }
2149            let updated = meta
2150                .builder_with_alter_kind(
2151                    "t",
2152                    &AlterKind::SetAnnotations {
2153                        family: AnnotationFamily::RepartitionHint,
2154                        options: options.clone(),
2155                    },
2156                )
2157                .unwrap()
2158                .build()
2159                .unwrap();
2160            assert_eq!(
2161                updated.options.extra_options,
2162                options.iter().cloned().collect()
2163            );
2164            for (key, value) in [
2165                (REPARTITION_COLUMN_HINT_KEY, "missing"),
2166                (REPARTITION_PARTITION_NUM_HINT_KEY, "0"),
2167            ] {
2168                let invalid = options
2169                    .iter()
2170                    .map(|(k, v)| {
2171                        (
2172                            k.clone(),
2173                            if k == key {
2174                                value.to_string()
2175                            } else {
2176                                v.clone()
2177                            },
2178                        )
2179                    })
2180                    .collect();
2181                assert!(
2182                    updated
2183                        .builder_with_alter_kind(
2184                            "t",
2185                            &AlterKind::SetAnnotations {
2186                                family: AnnotationFamily::RepartitionHint,
2187                                options: invalid,
2188                            }
2189                        )
2190                        .is_err()
2191                );
2192                assert_eq!(
2193                    updated.options.extra_options,
2194                    options.iter().cloned().collect()
2195                );
2196            }
2197            let cleared = updated
2198                .builder_with_alter_kind(
2199                    "t",
2200                    &AlterKind::UnsetAnnotations {
2201                        family: AnnotationFamily::RepartitionHint,
2202                        keys: options.into_iter().map(|(key, _)| key).collect(),
2203                    },
2204                )
2205                .unwrap()
2206                .build()
2207                .unwrap();
2208            assert!(cleared.options.extra_options.is_empty());
2209        }
2210    }
2211
2212    #[test]
2213    fn test_repartition_partition_num_hint() {
2214        for partition_key_indices in [vec![], vec![0]] {
2215            let mut meta = TableMetaBuilder::empty()
2216                .schema(Arc::new(new_test_schema()))
2217                .primary_key_indices(vec![0])
2218                .partition_key_indices(partition_key_indices)
2219                .engine("engine")
2220                .next_column_id(3)
2221                .build()
2222                .unwrap();
2223            for value in ["", " ", "0", "-1", "1.5", "abc", "4294967296"] {
2224                let err = meta
2225                    .builder_with_alter_kind(
2226                        "t",
2227                        &AlterKind::SetAnnotations {
2228                            family: AnnotationFamily::RepartitionHint,
2229                            options: vec![(
2230                                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2231                                value.to_string(),
2232                            )],
2233                        },
2234                    )
2235                    .err()
2236                    .unwrap();
2237                assert!(
2238                    err.to_string().contains("expects a positive integer"),
2239                    "{err}"
2240                );
2241            }
2242            for (value, expected) in [(" 8 ", "8"), ("1", "1"), ("4294967295", "4294967295")] {
2243                meta = meta
2244                    .builder_with_alter_kind(
2245                        "t",
2246                        &AlterKind::SetAnnotations {
2247                            family: AnnotationFamily::RepartitionHint,
2248                            options: vec![(
2249                                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2250                                value.to_string(),
2251                            )],
2252                        },
2253                    )
2254                    .unwrap()
2255                    .build()
2256                    .unwrap();
2257                assert_eq!(
2258                    meta.options.extra_options,
2259                    HashMap::from([(
2260                        REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2261                        expected.to_string()
2262                    )])
2263                );
2264            }
2265            for _ in 0..2 {
2266                meta = meta
2267                    .builder_with_alter_kind(
2268                        "t",
2269                        &AlterKind::UnsetAnnotations {
2270                            family: AnnotationFamily::RepartitionHint,
2271                            keys: vec![REPARTITION_PARTITION_NUM_HINT_KEY.to_string()],
2272                        },
2273                    )
2274                    .unwrap()
2275                    .build()
2276                    .unwrap();
2277                assert!(meta.options.extra_options.is_empty());
2278            }
2279        }
2280    }
2281
2282    #[test]
2283    fn test_set_repartition_column_hint() {
2284        let meta = TableMetaBuilder::empty()
2285            .schema(Arc::new(new_test_schema()))
2286            .primary_key_indices(vec![0])
2287            .engine("engine")
2288            .next_column_id(3)
2289            .build()
2290            .unwrap();
2291
2292        let alter_kind = AlterKind::SetAnnotations {
2293            family: AnnotationFamily::RepartitionHint,
2294            options: vec![(
2295                REPARTITION_COLUMN_HINT_KEY.to_string(),
2296                " col1 ".to_string(),
2297            )],
2298        };
2299        let new_meta = meta
2300            .builder_with_alter_kind("my_table", &alter_kind)
2301            .unwrap()
2302            .build()
2303            .unwrap();
2304
2305        assert_eq!(
2306            Some("col1"),
2307            new_meta
2308                .options
2309                .extra_options
2310                .get(REPARTITION_COLUMN_HINT_KEY)
2311                .map(String::as_str)
2312        );
2313    }
2314
2315    #[test]
2316    fn test_set_repartition_column_hint_rejects_empty_column() {
2317        let meta = TableMetaBuilder::empty()
2318            .schema(Arc::new(new_test_schema()))
2319            .primary_key_indices(vec![0])
2320            .engine("engine")
2321            .next_column_id(3)
2322            .build()
2323            .unwrap();
2324
2325        let alter_kind = AlterKind::SetAnnotations {
2326            family: AnnotationFamily::RepartitionHint,
2327            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), " ".to_string())],
2328        };
2329        let err = meta
2330            .builder_with_alter_kind("my_table", &alter_kind)
2331            .err()
2332            .unwrap();
2333
2334        assert!(
2335            err.to_string()
2336                .contains("repartition.column.hint expects exactly one column name")
2337        );
2338    }
2339
2340    #[test]
2341    fn test_set_repartition_column_hint_rejects_multiple_columns() {
2342        let meta = TableMetaBuilder::empty()
2343            .schema(Arc::new(new_test_schema()))
2344            .primary_key_indices(vec![0])
2345            .engine("engine")
2346            .next_column_id(3)
2347            .build()
2348            .unwrap();
2349
2350        let alter_kind = AlterKind::SetAnnotations {
2351            family: AnnotationFamily::RepartitionHint,
2352            options: vec![(
2353                REPARTITION_COLUMN_HINT_KEY.to_string(),
2354                "col1,col2".to_string(),
2355            )],
2356        };
2357        let err = meta
2358            .builder_with_alter_kind("my_table", &alter_kind)
2359            .err()
2360            .unwrap();
2361
2362        assert!(
2363            err.to_string()
2364                .contains("repartition.column.hint expects exactly one column name")
2365        );
2366    }
2367
2368    #[test]
2369    fn test_set_repartition_column_hint_rejects_missing_column() {
2370        let meta = TableMetaBuilder::empty()
2371            .schema(Arc::new(new_test_schema()))
2372            .primary_key_indices(vec![0])
2373            .engine("engine")
2374            .next_column_id(3)
2375            .build()
2376            .unwrap();
2377
2378        let alter_kind = AlterKind::SetAnnotations {
2379            family: AnnotationFamily::RepartitionHint,
2380            options: vec![(
2381                REPARTITION_COLUMN_HINT_KEY.to_string(),
2382                "missing".to_string(),
2383            )],
2384        };
2385        let err = meta
2386            .builder_with_alter_kind("my_table", &alter_kind)
2387            .err()
2388            .unwrap();
2389
2390        assert!(err.to_string().contains("Column missing not exists"));
2391    }
2392
2393    #[test]
2394    fn test_set_repartition_column_hint_rejects_time_index_column() {
2395        let meta = TableMetaBuilder::empty()
2396            .schema(Arc::new(new_test_schema()))
2397            .primary_key_indices(vec![0])
2398            .engine("engine")
2399            .next_column_id(3)
2400            .build()
2401            .unwrap();
2402
2403        let alter_kind = AlterKind::SetAnnotations {
2404            family: AnnotationFamily::RepartitionHint,
2405            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "ts".to_string())],
2406        };
2407        let err = meta
2408            .builder_with_alter_kind("my_table", &alter_kind)
2409            .err()
2410            .unwrap();
2411
2412        assert!(
2413            err.to_string()
2414                .contains("cannot set repartition.column.hint to the time index column")
2415        );
2416    }
2417
2418    #[test]
2419    fn test_set_repartition_column_hint_rejects_partitioned_table() {
2420        let meta = TableMetaBuilder::empty()
2421            .schema(Arc::new(new_test_schema()))
2422            .primary_key_indices(vec![0])
2423            .engine("engine")
2424            .next_column_id(3)
2425            .partition_key_indices(vec![0])
2426            .build()
2427            .unwrap();
2428
2429        let alter_kind = AlterKind::SetAnnotations {
2430            family: AnnotationFamily::RepartitionHint,
2431            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string())],
2432        };
2433        let err = meta
2434            .builder_with_alter_kind("my_table", &alter_kind)
2435            .err()
2436            .unwrap();
2437
2438        assert!(
2439            err.to_string()
2440                .contains("cannot set repartition.column.hint on a table with partition metadata")
2441        );
2442    }
2443
2444    #[test]
2445    fn test_unset_repartition_column_hint() {
2446        let mut table_options = TableOptions::default();
2447        table_options
2448            .extra_options
2449            .insert(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string());
2450        let meta = TableMetaBuilder::empty()
2451            .schema(Arc::new(new_test_schema()))
2452            .primary_key_indices(vec![0])
2453            .engine("engine")
2454            .next_column_id(3)
2455            .options(table_options)
2456            .build()
2457            .unwrap();
2458
2459        let new_meta = meta
2460            .builder_with_alter_kind(
2461                "my_table",
2462                &AlterKind::UnsetAnnotations {
2463                    family: AnnotationFamily::RepartitionHint,
2464                    keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
2465                },
2466            )
2467            .unwrap()
2468            .build()
2469            .unwrap();
2470
2471        assert!(
2472            !new_meta
2473                .options
2474                .extra_options
2475                .contains_key(REPARTITION_COLUMN_HINT_KEY)
2476        );
2477    }
2478
2479    #[test]
2480    fn test_repartition_hints_are_not_region_options() {
2481        let mut table_options = TableOptions::default();
2482        table_options
2483            .extra_options
2484            .insert(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string());
2485        table_options.extra_options.insert(
2486            REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2487            "8".to_string(),
2488        );
2489        let table_info = TableInfoBuilder::default()
2490            .table_id(1)
2491            .table_version(0)
2492            .name("my_table")
2493            .catalog_name(DEFAULT_CATALOG_NAME)
2494            .schema_name(DEFAULT_SCHEMA_NAME)
2495            .meta(
2496                TableMetaBuilder::empty()
2497                    .schema(Arc::new(new_test_schema()))
2498                    .primary_key_indices(vec![0])
2499                    .engine("engine")
2500                    .next_column_id(3)
2501                    .options(table_options)
2502                    .build()
2503                    .unwrap(),
2504            )
2505            .build()
2506            .unwrap();
2507
2508        assert!(
2509            !table_info
2510                .to_region_options()
2511                .contains_key(REPARTITION_PARTITION_NUM_HINT_KEY)
2512        );
2513        assert!(
2514            !table_info
2515                .to_region_options()
2516                .contains_key(REPARTITION_COLUMN_HINT_KEY)
2517        );
2518    }
2519
2520    #[test]
2521    fn test_set_auto_flush_interval() {
2522        let schema = Arc::new(new_test_schema());
2523        let table_options = TableOptions::default();
2524        let meta = TableMetaBuilder::empty()
2525            .schema(schema)
2526            .primary_key_indices(vec![0])
2527            .engine("engine")
2528            .next_column_id(3)
2529            .options(table_options)
2530            .build()
2531            .unwrap();
2532
2533        let alter_kind = AlterKind::SetTableOptions {
2534            options: vec![SetRegionOption::AutoFlushInterval(Some(
2535                std::time::Duration::from_secs(300),
2536            ))],
2537        };
2538        let new_meta = meta
2539            .builder_with_alter_kind("my_table", &alter_kind)
2540            .unwrap()
2541            .build()
2542            .unwrap();
2543
2544        assert_eq!(
2545            Some("5m"),
2546            new_meta
2547                .options
2548                .extra_options
2549                .get(AUTO_FLUSH_INTERVAL_KEY)
2550                .map(String::as_str)
2551        );
2552    }
2553
2554    #[test]
2555    fn test_set_auto_flush_interval_none_removes_existing() {
2556        let schema = Arc::new(new_test_schema());
2557        let mut table_options = TableOptions::default();
2558        table_options
2559            .extra_options
2560            .insert(AUTO_FLUSH_INTERVAL_KEY.to_string(), "5m".to_string());
2561        let meta = TableMetaBuilder::empty()
2562            .schema(schema)
2563            .primary_key_indices(vec![0])
2564            .engine("engine")
2565            .next_column_id(3)
2566            .options(table_options)
2567            .build()
2568            .unwrap();
2569
2570        let alter_kind = AlterKind::SetTableOptions {
2571            options: vec![SetRegionOption::AutoFlushInterval(None)],
2572        };
2573        let new_meta = meta
2574            .builder_with_alter_kind("my_table", &alter_kind)
2575            .unwrap()
2576            .build()
2577            .unwrap();
2578
2579        assert!(
2580            !new_meta
2581                .options
2582                .extra_options
2583                .contains_key(AUTO_FLUSH_INTERVAL_KEY)
2584        );
2585    }
2586
2587    #[test]
2588    fn test_set_twcs_trigger_persists_legacy_key() {
2589        for key in [TWCS_TRIGGER_FILE_NUM, TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM] {
2590            let mut table_options = TableOptions::default();
2591            table_options.extra_options.insert(
2592                TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM.to_string(),
2593                "4".to_string(),
2594            );
2595            let meta = TableMetaBuilder::empty()
2596                .schema(Arc::new(new_test_schema()))
2597                .primary_key_indices(vec![0])
2598                .engine("engine")
2599                .next_column_id(3)
2600                .options(table_options)
2601                .build()
2602                .unwrap();
2603            let alter_kind = AlterKind::SetTableOptions {
2604                options: vec![SetRegionOption::Twsc(key.to_string(), "8".to_string())],
2605            };
2606
2607            let new_meta = meta
2608                .builder_with_alter_kind("my_table", &alter_kind)
2609                .unwrap()
2610                .build()
2611                .unwrap();
2612
2613            assert_eq!(
2614                Some("8"),
2615                new_meta
2616                    .options
2617                    .extra_options
2618                    .get(TWCS_TRIGGER_FILE_NUM)
2619                    .map(String::as_str)
2620            );
2621            assert!(
2622                !new_meta
2623                    .options
2624                    .extra_options
2625                    .contains_key(TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM)
2626            );
2627        }
2628    }
2629
2630    #[test]
2631    fn test_set_and_unset_max_row_group_row_count() {
2632        let meta = TableMetaBuilder::empty()
2633            .schema(Arc::new(new_test_schema()))
2634            .primary_key_indices(vec![0])
2635            .engine("engine")
2636            .next_column_id(3)
2637            .options(TableOptions::default())
2638            .build()
2639            .unwrap();
2640
2641        let alter_kind = AlterKind::SetTableOptions {
2642            options: vec![SetRegionOption::MaxRowGroupRowCount(Some(512))],
2643        };
2644        let new_meta = meta
2645            .builder_with_alter_kind("my_table", &alter_kind)
2646            .unwrap()
2647            .build()
2648            .unwrap();
2649        assert_eq!(
2650            Some("512"),
2651            new_meta
2652                .options
2653                .extra_options
2654                .get(MAX_ROW_GROUP_ROW_COUNT)
2655                .map(String::as_str)
2656        );
2657
2658        let alter_kind = AlterKind::UnsetTableOptions {
2659            keys: vec![UnsetRegionOption::MaxRowGroupRowCount],
2660        };
2661        let new_meta = new_meta
2662            .builder_with_alter_kind("my_table", &alter_kind)
2663            .unwrap()
2664            .build()
2665            .unwrap();
2666        assert!(
2667            !new_meta
2668                .options
2669                .extra_options
2670                .contains_key(MAX_ROW_GROUP_ROW_COUNT)
2671        );
2672    }
2673
2674    #[test]
2675    fn test_add_columns_multiple_times() {
2676        let schema = Arc::new(new_test_schema());
2677        let meta = TableMetaBuilder::empty()
2678            .schema(schema)
2679            .primary_key_indices(vec![0])
2680            .engine("engine")
2681            .next_column_id(3)
2682            .build()
2683            .unwrap();
2684
2685        let alter_kind = AlterKind::AddColumns {
2686            columns: vec![
2687                AddColumnRequest {
2688                    column_schema: ColumnSchema::new(
2689                        "col3",
2690                        ConcreteDataType::int32_datatype(),
2691                        true,
2692                    ),
2693                    is_key: true,
2694                    location: None,
2695                    add_if_not_exists: true,
2696                },
2697                AddColumnRequest {
2698                    column_schema: ColumnSchema::new(
2699                        "col3",
2700                        ConcreteDataType::int32_datatype(),
2701                        true,
2702                    ),
2703                    is_key: true,
2704                    location: None,
2705                    add_if_not_exists: true,
2706                },
2707            ],
2708        };
2709        let err = meta
2710            .builder_with_alter_kind("my_table", &alter_kind)
2711            .err()
2712            .unwrap();
2713        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2714    }
2715
2716    #[test]
2717    fn test_remove_columns() {
2718        let schema = Arc::new(new_test_schema());
2719        let meta = TableMetaBuilder::empty()
2720            .schema(schema.clone())
2721            .primary_key_indices(vec![0])
2722            .engine("engine")
2723            .next_column_id(3)
2724            .build()
2725            .unwrap();
2726        // Add more columns so we have enough candidate columns to remove.
2727        let meta = add_columns_to_meta(&meta);
2728
2729        let alter_kind = AlterKind::DropColumns {
2730            names: vec![String::from("col2"), String::from("my_field")],
2731        };
2732        let new_meta = meta
2733            .builder_with_alter_kind("my_table", &alter_kind)
2734            .unwrap()
2735            .build()
2736            .unwrap();
2737
2738        let names: Vec<String> = new_meta
2739            .schema
2740            .column_schemas()
2741            .iter()
2742            .map(|column_schema| column_schema.name.clone())
2743            .collect();
2744        assert_eq!(&["col1", "ts", "my_tag"], &names[..]);
2745        assert_eq!(&[0, 2], &new_meta.primary_key_indices[..]);
2746        assert_eq!(&[1], &new_meta.value_indices[..]);
2747        assert_eq!(
2748            schema.timestamp_column(),
2749            new_meta.schema.timestamp_column()
2750        );
2751    }
2752
2753    #[test]
2754    fn test_remove_multiple_columns_before_timestamp() {
2755        let column_schemas = vec![
2756            ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
2757            ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
2758            ColumnSchema::new("col3", ConcreteDataType::int32_datatype(), true),
2759            ColumnSchema::new(
2760                "ts",
2761                ConcreteDataType::timestamp_millisecond_datatype(),
2762                false,
2763            )
2764            .with_time_index(true),
2765        ];
2766        let schema = Arc::new(
2767            SchemaBuilder::try_from(column_schemas)
2768                .unwrap()
2769                .version(123)
2770                .build()
2771                .unwrap(),
2772        );
2773        let meta = TableMetaBuilder::empty()
2774            .schema(schema.clone())
2775            .primary_key_indices(vec![1])
2776            .engine("engine")
2777            .next_column_id(4)
2778            .build()
2779            .unwrap();
2780
2781        // Remove columns in reverse order to test whether timestamp index is valid.
2782        let alter_kind = AlterKind::DropColumns {
2783            names: vec![String::from("col3"), String::from("col1")],
2784        };
2785        let new_meta = meta
2786            .builder_with_alter_kind("my_table", &alter_kind)
2787            .unwrap()
2788            .build()
2789            .unwrap();
2790
2791        let names: Vec<String> = new_meta
2792            .schema
2793            .column_schemas()
2794            .iter()
2795            .map(|column_schema| column_schema.name.clone())
2796            .collect();
2797        assert_eq!(&["col2", "ts"], &names[..]);
2798        assert_eq!(&[0], &new_meta.primary_key_indices[..]);
2799        assert_eq!(&[1], &new_meta.value_indices[..]);
2800        assert_eq!(
2801            schema.timestamp_column(),
2802            new_meta.schema.timestamp_column()
2803        );
2804    }
2805
2806    #[test]
2807    fn test_add_existing_column() {
2808        let schema = Arc::new(new_test_schema());
2809        let meta = TableMetaBuilder::empty()
2810            .schema(schema)
2811            .primary_key_indices(vec![0])
2812            .engine("engine")
2813            .next_column_id(3)
2814            .build()
2815            .unwrap();
2816
2817        let alter_kind = AlterKind::AddColumns {
2818            columns: vec![AddColumnRequest {
2819                column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
2820                is_key: false,
2821                location: None,
2822                add_if_not_exists: false,
2823            }],
2824        };
2825
2826        let err = meta
2827            .builder_with_alter_kind("my_table", &alter_kind)
2828            .err()
2829            .unwrap();
2830        assert_eq!(StatusCode::TableColumnExists, err.status_code());
2831
2832        // Add if not exists
2833        let alter_kind = AlterKind::AddColumns {
2834            columns: vec![AddColumnRequest {
2835                column_schema: ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
2836                is_key: true,
2837                location: None,
2838                add_if_not_exists: true,
2839            }],
2840        };
2841        let new_meta = meta
2842            .builder_with_alter_kind("my_table", &alter_kind)
2843            .unwrap()
2844            .build()
2845            .unwrap();
2846        assert_eq!(
2847            meta.schema.column_schemas(),
2848            new_meta.schema.column_schemas()
2849        );
2850        assert_eq!(meta.schema.version() + 1, new_meta.schema.version());
2851    }
2852
2853    #[test]
2854    fn test_add_different_type_column() {
2855        let schema = Arc::new(new_test_schema());
2856        let meta = TableMetaBuilder::empty()
2857            .schema(schema)
2858            .primary_key_indices(vec![0])
2859            .engine("engine")
2860            .next_column_id(3)
2861            .build()
2862            .unwrap();
2863
2864        // Add if not exists, but different type.
2865        let alter_kind = AlterKind::AddColumns {
2866            columns: vec![AddColumnRequest {
2867                column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
2868                is_key: false,
2869                location: None,
2870                add_if_not_exists: true,
2871            }],
2872        };
2873        let err = meta
2874            .builder_with_alter_kind("my_table", &alter_kind)
2875            .err()
2876            .unwrap();
2877        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2878    }
2879
2880    #[test]
2881    fn test_add_invalid_column() {
2882        let schema = Arc::new(new_test_schema());
2883        let meta = TableMetaBuilder::empty()
2884            .schema(schema)
2885            .primary_key_indices(vec![0])
2886            .engine("engine")
2887            .next_column_id(3)
2888            .build()
2889            .unwrap();
2890
2891        // Not nullable and no default value.
2892        let alter_kind = AlterKind::AddColumns {
2893            columns: vec![AddColumnRequest {
2894                column_schema: ColumnSchema::new(
2895                    "weny",
2896                    ConcreteDataType::string_datatype(),
2897                    false,
2898                ),
2899                is_key: false,
2900                location: None,
2901                add_if_not_exists: false,
2902            }],
2903        };
2904
2905        let err = meta
2906            .builder_with_alter_kind("my_table", &alter_kind)
2907            .err()
2908            .unwrap();
2909        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2910    }
2911
2912    #[test]
2913    fn test_remove_unknown_column() {
2914        let schema = Arc::new(new_test_schema());
2915        let meta = TableMetaBuilder::empty()
2916            .schema(schema)
2917            .primary_key_indices(vec![0])
2918            .engine("engine")
2919            .next_column_id(3)
2920            .build()
2921            .unwrap();
2922
2923        let alter_kind = AlterKind::DropColumns {
2924            names: vec![String::from("unknown")],
2925        };
2926
2927        let err = meta
2928            .builder_with_alter_kind("my_table", &alter_kind)
2929            .err()
2930            .unwrap();
2931        assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
2932    }
2933
2934    #[test]
2935    fn test_change_unknown_column_data_type() {
2936        let schema = Arc::new(new_test_schema());
2937        let meta = TableMetaBuilder::empty()
2938            .schema(schema)
2939            .primary_key_indices(vec![0])
2940            .engine("engine")
2941            .next_column_id(3)
2942            .build()
2943            .unwrap();
2944
2945        let alter_kind = AlterKind::ModifyColumnTypes {
2946            columns: vec![ModifyColumnTypeRequest {
2947                column_name: "unknown".to_string(),
2948                target_type: ConcreteDataType::string_datatype(),
2949            }],
2950        };
2951
2952        let err = meta
2953            .builder_with_alter_kind("my_table", &alter_kind)
2954            .err()
2955            .unwrap();
2956        assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
2957    }
2958
2959    #[test]
2960    fn test_remove_key_column() {
2961        let schema = Arc::new(new_test_schema());
2962        let meta = TableMetaBuilder::empty()
2963            .schema(schema)
2964            .primary_key_indices(vec![0])
2965            .engine("engine")
2966            .next_column_id(3)
2967            .build()
2968            .unwrap();
2969
2970        // Remove column in primary key.
2971        let alter_kind = AlterKind::DropColumns {
2972            names: vec![String::from("col1")],
2973        };
2974
2975        let err = meta
2976            .builder_with_alter_kind("my_table", &alter_kind)
2977            .err()
2978            .unwrap();
2979        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2980
2981        // Remove timestamp column.
2982        let alter_kind = AlterKind::DropColumns {
2983            names: vec![String::from("ts")],
2984        };
2985
2986        let err = meta
2987            .builder_with_alter_kind("my_table", &alter_kind)
2988            .err()
2989            .unwrap();
2990        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2991    }
2992
2993    #[test]
2994    fn test_remove_partition_column() {
2995        let schema = Arc::new(new_test_schema());
2996        let meta = TableMetaBuilder::empty()
2997            .schema(schema)
2998            .primary_key_indices(vec![])
2999            .partition_key_indices(vec![0])
3000            .engine("engine")
3001            .next_column_id(3)
3002            .build()
3003            .unwrap();
3004        // Remove column in primary key.
3005        let alter_kind = AlterKind::DropColumns {
3006            names: vec![String::from("col1")],
3007        };
3008
3009        let err = meta
3010            .builder_with_alter_kind("my_table", &alter_kind)
3011            .err()
3012            .unwrap();
3013        assert_matches!(err, Error::RemovePartitionColumn { .. });
3014    }
3015
3016    #[test]
3017    fn test_change_key_column_data_type() {
3018        let schema = Arc::new(new_test_schema());
3019        let meta = TableMetaBuilder::empty()
3020            .schema(schema)
3021            .primary_key_indices(vec![0])
3022            .engine("engine")
3023            .next_column_id(3)
3024            .build()
3025            .unwrap();
3026
3027        // Remove column in primary key.
3028        let alter_kind = AlterKind::ModifyColumnTypes {
3029            columns: vec![ModifyColumnTypeRequest {
3030                column_name: "col1".to_string(),
3031                target_type: ConcreteDataType::string_datatype(),
3032            }],
3033        };
3034
3035        let err = meta
3036            .builder_with_alter_kind("my_table", &alter_kind)
3037            .err()
3038            .unwrap();
3039        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3040
3041        // Remove timestamp column.
3042        let alter_kind = AlterKind::ModifyColumnTypes {
3043            columns: vec![ModifyColumnTypeRequest {
3044                column_name: "ts".to_string(),
3045                target_type: ConcreteDataType::string_datatype(),
3046            }],
3047        };
3048
3049        let err = meta
3050            .builder_with_alter_kind("my_table", &alter_kind)
3051            .err()
3052            .unwrap();
3053        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3054    }
3055
3056    #[test]
3057    fn test_alloc_new_column() {
3058        let schema = Arc::new(new_test_schema());
3059        let mut meta = TableMetaBuilder::empty()
3060            .schema(schema)
3061            .primary_key_indices(vec![0])
3062            .engine("engine")
3063            .next_column_id(3)
3064            .build()
3065            .unwrap();
3066        assert_eq!(3, meta.next_column_id);
3067
3068        let column_schema = ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true);
3069        let desc = meta.alloc_new_column("test_table", &column_schema).unwrap();
3070
3071        assert_eq!(4, meta.next_column_id);
3072        assert_eq!(column_schema.name, desc.name);
3073    }
3074
3075    #[test]
3076    fn test_add_columns_with_location() {
3077        let schema = Arc::new(new_test_schema());
3078        let meta = TableMetaBuilder::empty()
3079            .schema(schema)
3080            .primary_key_indices(vec![0])
3081            // partition col: col1, col2
3082            .partition_key_indices(vec![0, 2])
3083            .engine("engine")
3084            .next_column_id(3)
3085            .build()
3086            .unwrap();
3087
3088        let new_meta = add_columns_to_meta_with_location(&meta);
3089        let names: Vec<String> = new_meta
3090            .schema
3091            .column_schemas()
3092            .iter()
3093            .map(|column_schema| column_schema.name.clone())
3094            .collect();
3095        assert_eq!(
3096            &[
3097                "my_tag_first",               // primary key column
3098                "col1",                       // partition column
3099                "ts",                         // timestamp column
3100                "yet_another_field_after_ts", // primary key column
3101                "my_field_after_ts",          // value column
3102                "col2",                       // partition column
3103            ],
3104            &names[..]
3105        );
3106        assert_eq!(&[0, 1, 3], &new_meta.primary_key_indices[..]);
3107        assert_eq!(&[2, 4, 5], &new_meta.value_indices[..]);
3108        assert_eq!(&[1, 5], &new_meta.partition_key_indices[..]);
3109    }
3110
3111    #[test]
3112    fn test_modify_column_fulltext_options() {
3113        let schema = Arc::new(new_test_schema());
3114        let meta = TableMetaBuilder::empty()
3115            .schema(schema)
3116            .primary_key_indices(vec![0])
3117            .engine("engine")
3118            .next_column_id(3)
3119            .build()
3120            .unwrap();
3121
3122        let alter_kind = AlterKind::SetIndexes {
3123            options: vec![SetIndexOption::Fulltext {
3124                column_name: "col1".to_string(),
3125                options: FulltextOptions::default(),
3126            }],
3127        };
3128        let err = meta
3129            .builder_with_alter_kind("my_table", &alter_kind)
3130            .err()
3131            .unwrap();
3132        assert_eq!(
3133            "Invalid column option, column name: col1, error: FULLTEXT index only supports string type",
3134            err.to_string()
3135        );
3136
3137        // Add a string column and make it fulltext indexed
3138        let new_meta = add_columns_to_meta_with_location(&meta);
3139        let alter_kind = AlterKind::SetIndexes {
3140            options: vec![SetIndexOption::Fulltext {
3141                column_name: "my_tag_first".to_string(),
3142                options: FulltextOptions::new_unchecked(
3143                    true,
3144                    FulltextAnalyzer::Chinese,
3145                    true,
3146                    FulltextBackend::Bloom,
3147                    1000,
3148                    0.01,
3149                ),
3150            }],
3151        };
3152        let new_meta = new_meta
3153            .builder_with_alter_kind("my_table", &alter_kind)
3154            .unwrap()
3155            .build()
3156            .unwrap();
3157        let column_schema = new_meta
3158            .schema
3159            .column_schema_by_name("my_tag_first")
3160            .unwrap();
3161        let fulltext_options = column_schema.fulltext_options().unwrap().unwrap();
3162        assert!(fulltext_options.enable);
3163        assert_eq!(
3164            datatypes::schema::FulltextAnalyzer::Chinese,
3165            fulltext_options.analyzer
3166        );
3167        assert!(fulltext_options.case_sensitive);
3168
3169        let alter_kind = AlterKind::UnsetIndexes {
3170            options: vec![UnsetIndexOption::Fulltext {
3171                column_name: "my_tag_first".to_string(),
3172            }],
3173        };
3174        let new_meta = new_meta
3175            .builder_with_alter_kind("my_table", &alter_kind)
3176            .unwrap()
3177            .build()
3178            .unwrap();
3179        let column_schema = new_meta
3180            .schema
3181            .column_schema_by_name("my_tag_first")
3182            .unwrap();
3183        let fulltext_options = column_schema.fulltext_options().unwrap().unwrap();
3184        assert!(!fulltext_options.enable);
3185    }
3186
3187    #[test]
3188    fn test_table_info_serde_compatibility() {
3189        // "serialized" is generated by the following codes before this refactor (PR 7626):
3190        //
3191        // ```Rust
3192        // serde_json::to_string(&RawTableInfo::from(TableInfo {
3193        //     ident: TableIdent {
3194        //         table_id: 1024,
3195        //         version: 1,
3196        //     },
3197        //     name: "foo".to_string(),
3198        //     desc: Some("my table".to_string()),
3199        //     catalog_name: "greptime".to_string(),
3200        //     schema_name: "public".to_string(),
3201        //     meta: TableMeta {
3202        //         schema: Arc::new(new_test_schema()),
3203        //         primary_key_indices: vec![0],
3204        //         value_indices: vec![1, 2],
3205        //         engine: "mito".to_string(),
3206        //         next_column_id: 3,
3207        //         options: TableOptions {
3208        //             ttl: Some(common_time::TimeToLive::Duration(
3209        //                 std::time::Duration::from_secs(3600),
3210        //             )),
3211        //             ..Default::default()
3212        //         },
3213        //         created_on: DateTime::<Utc>::MIN_UTC,
3214        //         updated_on: DateTime::<Utc>::MAX_UTC,
3215        //         partition_key_indices: vec![2],
3216        //         column_ids: vec![0, 1, 2],
3217        //     },
3218        //     table_type: TableType::Base,
3219        // }))
3220        // ```
3221        let serialized = r#"{"ident":{"table_id":1024,"version":1},"name":"foo","desc":"my table","catalog_name":"greptime","schema_name":"public","meta":{"schema":{"column_schemas":[{"name":"col1","data_type":{"Int32":{}},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"ts","data_type":{"Timestamp":{"Millisecond":null}},"is_nullable":false,"is_time_index":true,"default_constraint":null,"metadata":{"greptime:time_index":"true"}},{"name":"col2","data_type":{"Int32":{}},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}}],"timestamp_index":1,"version":123},"primary_key_indices":[0],"value_indices":[1,2],"engine":"mito","next_column_id":3,"options":{"write_buffer_size":null,"ttl":"1h","skip_wal":false,"extra_options":{}},"created_on":"-262143-01-01T00:00:00Z","updated_on":"+262142-12-31T23:59:59.999999999Z","partition_key_indices":[2],"column_ids":[0,1,2]},"table_type":"Base"}"#;
3222
3223        let actual: TableInfo = serde_json::from_str(serialized).unwrap();
3224        let expected = TableInfo {
3225            ident: TableIdent {
3226                table_id: 1024,
3227                version: 1,
3228            },
3229            name: "foo".to_string(),
3230            desc: Some("my table".to_string()),
3231            catalog_name: "greptime".to_string(),
3232            schema_name: "public".to_string(),
3233            meta: TableMeta {
3234                schema: Arc::new(new_test_schema()),
3235                primary_key_indices: vec![0],
3236                value_indices: vec![1, 2],
3237                engine: "mito".to_string(),
3238                next_column_id: 3,
3239                options: TableOptions {
3240                    ttl: Some(common_time::TimeToLive::Duration(
3241                        std::time::Duration::from_secs(3600),
3242                    )),
3243                    ..Default::default()
3244                },
3245                created_on: DateTime::<Utc>::MIN_UTC,
3246                updated_on: DateTime::<Utc>::MAX_UTC,
3247                partition_key_indices: vec![2],
3248                column_ids: vec![0, 1, 2],
3249            },
3250            table_type: TableType::Base,
3251        };
3252        assert_eq!(actual, expected);
3253    }
3254
3255    use crate::requests::{SEMANTIC_METRIC_UNIT, SEMANTIC_SIGNAL_TYPE};
3256
3257    /// `host` is a tag, `service` and `note` are string fields, `payload` is a
3258    /// binary field.
3259    fn semantic_test_meta() -> TableMeta {
3260        let column_schemas = vec![
3261            ColumnSchema::new("host", ConcreteDataType::string_datatype(), true),
3262            ColumnSchema::new(
3263                "ts",
3264                ConcreteDataType::timestamp_millisecond_datatype(),
3265                false,
3266            )
3267            .with_time_index(true),
3268            ColumnSchema::new("payload", ConcreteDataType::binary_datatype(), true),
3269            ColumnSchema::new("service", ConcreteDataType::string_datatype(), true),
3270            ColumnSchema::new("note", ConcreteDataType::string_datatype(), true),
3271        ];
3272        let schema = Arc::new(
3273            SchemaBuilder::try_from(column_schemas)
3274                .unwrap()
3275                .build()
3276                .unwrap(),
3277        );
3278        TableMetaBuilder::empty()
3279            .schema(schema)
3280            .primary_key_indices(vec![0])
3281            .engine("engine")
3282            .next_column_id(5)
3283            .build()
3284            .unwrap()
3285    }
3286
3287    #[test]
3288    fn test_set_semantic_annotations() {
3289        let meta = semantic_test_meta();
3290        // `service` is a plain field: entity columns may be tags or fields.
3291        let alter_kind = AlterKind::SetAnnotations {
3292            family: AnnotationFamily::Semantic,
3293            options: vec![
3294                (SEMANTIC_SIGNAL_TYPE.to_string(), "trace".to_string()),
3295                (
3296                    "greptime.semantic.entity.service.id".to_string(),
3297                    "service".to_string(),
3298                ),
3299            ],
3300        };
3301        let new_meta = meta
3302            .builder_with_alter_kind("my_table", &alter_kind)
3303            .unwrap()
3304            .build()
3305            .unwrap();
3306        assert_eq!(
3307            new_meta.options.extra_options.get(SEMANTIC_SIGNAL_TYPE),
3308            Some(&"trace".to_string())
3309        );
3310        assert_eq!(
3311            new_meta
3312                .options
3313                .extra_options
3314                .get("greptime.semantic.entity.service.id"),
3315            Some(&"service".to_string())
3316        );
3317    }
3318
3319    #[test]
3320    fn test_repartition_hint_batch_rejects_duplicate_keys() {
3321        let meta = TableMetaBuilder::empty()
3322            .schema(Arc::new(new_test_schema()))
3323            .primary_key_indices(vec![0])
3324            .engine("engine")
3325            .next_column_id(3)
3326            .build()
3327            .unwrap();
3328
3329        // Direct gRPC can hand the mutation layer a duplicated batch the
3330        // converter never saw; last-write-wins must not silently apply.
3331        let dup_set = AlterKind::SetAnnotations {
3332            family: AnnotationFamily::RepartitionHint,
3333            options: vec![
3334                (REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string()),
3335                (REPARTITION_COLUMN_HINT_KEY.to_string(), "col2".to_string()),
3336            ],
3337        };
3338        let err = meta
3339            .builder_with_alter_kind("my_table", &dup_set)
3340            .err()
3341            .unwrap();
3342        assert!(
3343            err.to_string().contains("duplicate repartition hint keys"),
3344            "{err}"
3345        );
3346
3347        let dup_unset = AlterKind::UnsetAnnotations {
3348            family: AnnotationFamily::RepartitionHint,
3349            keys: vec![
3350                REPARTITION_COLUMN_HINT_KEY.to_string(),
3351                REPARTITION_COLUMN_HINT_KEY.to_string(),
3352            ],
3353        };
3354        let err = meta
3355            .builder_with_alter_kind("my_table", &dup_unset)
3356            .err()
3357            .unwrap();
3358        assert!(
3359            err.to_string().contains("duplicate repartition hint keys"),
3360            "{err}"
3361        );
3362    }
3363
3364    #[test]
3365    fn test_set_semantic_annotations_rejects_invalid() {
3366        let meta = semantic_test_meta();
3367        let cases = [
3368            (
3369                "greptime.semantic.unknown_key",
3370                "x",
3371                "unknown semantic option",
3372            ),
3373            (SEMANTIC_SIGNAL_TYPE, "garbage", "invalid value"),
3374            (
3375                "greptime.semantic.entity.host.id",
3376                "no_such_column",
3377                "no_such_column",
3378            ),
3379            (
3380                "greptime.semantic.entity.host.id",
3381                "payload",
3382                "cannot render as a string",
3383            ),
3384        ];
3385        for (key, value, needle) in cases {
3386            let alter_kind = AlterKind::SetAnnotations {
3387                family: AnnotationFamily::Semantic,
3388                options: vec![(key.to_string(), value.to_string())],
3389            };
3390            let err = meta
3391                .builder_with_alter_kind("my_table", &alter_kind)
3392                .err()
3393                .unwrap();
3394            assert!(
3395                err.to_string().contains(needle),
3396                "key `{key}`: unexpected error `{err}`"
3397            );
3398        }
3399
3400        // ALTER keeps missing columns on the 4002 contract (pinned by sqlness);
3401        // the shared validator must not collapse it into InvalidArguments.
3402        let missing = meta
3403            .builder_with_alter_kind(
3404                "my_table",
3405                &AlterKind::SetAnnotations {
3406                    family: AnnotationFamily::Semantic,
3407                    options: vec![(
3408                        "greptime.semantic.entity.host.id".to_string(),
3409                        "no_such_column".to_string(),
3410                    )],
3411                },
3412            )
3413            .err()
3414            .unwrap();
3415        assert_eq!(
3416            common_error::status_code::StatusCode::TableColumnNotFound,
3417            common_error::ext::ErrorExt::status_code(&missing)
3418        );
3419    }
3420
3421    #[test]
3422    fn test_unset_semantic_annotations() {
3423        let mut meta = semantic_test_meta();
3424        meta.options
3425            .extra_options
3426            .insert(SEMANTIC_SIGNAL_TYPE.to_string(), "trace".to_string());
3427        // A key this version does not recognise, still inside the namespace.
3428        meta.options
3429            .extra_options
3430            .insert("greptime.semantic.future_key".to_string(), "x".to_string());
3431
3432        let alter_kind = AlterKind::UnsetAnnotations {
3433            family: AnnotationFamily::Semantic,
3434            keys: vec![
3435                SEMANTIC_SIGNAL_TYPE.to_string(),
3436                "greptime.semantic.future_key".to_string(),
3437                // Absent key: removal is a no-op, not an error.
3438                SEMANTIC_METRIC_UNIT.to_string(),
3439            ],
3440        };
3441        let new_meta = meta
3442            .builder_with_alter_kind("my_table", &alter_kind)
3443            .unwrap()
3444            .build()
3445            .unwrap();
3446        assert!(
3447            !new_meta
3448                .options
3449                .extra_options
3450                .contains_key(SEMANTIC_SIGNAL_TYPE)
3451        );
3452        assert!(
3453            !new_meta
3454                .options
3455                .extra_options
3456                .contains_key("greptime.semantic.future_key")
3457        );
3458
3459        let outside = AlterKind::UnsetAnnotations {
3460            family: AnnotationFamily::Semantic,
3461            keys: vec!["ttl".to_string()],
3462        };
3463        let err = meta
3464            .builder_with_alter_kind("my_table", &outside)
3465            .err()
3466            .unwrap();
3467        assert!(err.to_string().contains("annotation namespace"), "{err}");
3468    }
3469
3470    #[test]
3471    fn test_modify_entity_column_type_keeps_string_form() {
3472        let mut meta = semantic_test_meta();
3473        meta.options.extra_options.insert(
3474            "greptime.semantic.entity.service.id".to_string(),
3475            "service".to_string(),
3476        );
3477
3478        let alter_kind = AlterKind::ModifyColumnTypes {
3479            columns: vec![ModifyColumnTypeRequest {
3480                column_name: "service".to_string(),
3481                target_type: ConcreteDataType::binary_datatype(),
3482            }],
3483        };
3484        let err = meta
3485            .builder_with_alter_kind("my_table", &alter_kind)
3486            .err()
3487            .unwrap();
3488        assert!(
3489            err.to_string()
3490                .contains("must keep a type that renders as a string"),
3491            "{err}"
3492        );
3493
3494        // An unreferenced column may still change to a non-string form.
3495        let alter_kind = AlterKind::ModifyColumnTypes {
3496            columns: vec![ModifyColumnTypeRequest {
3497                column_name: "note".to_string(),
3498                target_type: ConcreteDataType::binary_datatype(),
3499            }],
3500        };
3501        meta.builder_with_alter_kind("my_table", &alter_kind)
3502            .unwrap();
3503    }
3504
3505    #[test]
3506    fn test_stale_entity_declaration_guards_readd_and_reports_missing_column() {
3507        // A stale declaration: `gone` was dropped after being declared.
3508        let mut meta = semantic_test_meta();
3509        meta.options.extra_options.insert(
3510            "greptime.semantic.entity.service.id".to_string(),
3511            "gone".to_string(),
3512        );
3513
3514        // MODIFY on the missing column keeps the ColumnNotExists contract
3515        // (4002), stale declaration or not.
3516        let modify = AlterKind::ModifyColumnTypes {
3517            columns: vec![ModifyColumnTypeRequest {
3518                column_name: "gone".to_string(),
3519                target_type: ConcreteDataType::binary_datatype(),
3520            }],
3521        };
3522        let err = meta
3523            .builder_with_alter_kind("my_table", &modify)
3524            .err()
3525            .unwrap();
3526        assert_eq!(
3527            common_error::status_code::StatusCode::TableColumnNotFound,
3528            common_error::ext::ErrorExt::status_code(&err)
3529        );
3530
3531        // Re-adding the declared column must not hand the declaration a
3532        // non-string type...
3533        let add = |ty: ConcreteDataType| AlterKind::AddColumns {
3534            columns: vec![AddColumnRequest {
3535                column_schema: ColumnSchema::new("gone", ty, true),
3536                is_key: false,
3537                location: None,
3538                add_if_not_exists: false,
3539            }],
3540        };
3541        let err = meta
3542            .builder_with_alter_kind("my_table", &add(ConcreteDataType::binary_datatype()))
3543            .err()
3544            .unwrap();
3545        assert!(
3546            err.to_string()
3547                .contains("must keep a type that renders as a string"),
3548            "{err}"
3549        );
3550
3551        // ...while a string re-add re-satisfies it.
3552        meta.builder_with_alter_kind("my_table", &add(ConcreteDataType::string_datatype()))
3553            .unwrap();
3554    }
3555}