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                // Partition rules keep bounds typed as the column, and functions such as
1097                // `iceberg_hash` route by the typed value, so a new type would reroute or
1098                // break routing. Time index widening above keeps timestamps comparable.
1099                ensure!(
1100                    !self.partition_key_indices.contains(&index),
1101                    error::InvalidAlterRequestSnafu {
1102                        table: table_name,
1103                        err: format!("Not allowed to change partition column '{}'", column.name)
1104                    }
1105                );
1106                ensure!(
1107                    column
1108                        .data_type
1109                        .can_arrow_type_cast_to(&col_to_change.target_type),
1110                    error::InvalidAlterRequestSnafu {
1111                        table: table_name,
1112                        err: format!(
1113                            "column '{}' cannot be cast automatically to type '{}'",
1114                            col_to_change.column_name, col_to_change.target_type,
1115                        ),
1116                    }
1117                );
1118
1119                ensure!(
1120                    column.is_nullable(),
1121                    error::InvalidAlterRequestSnafu {
1122                        table: table_name,
1123                        err: format!(
1124                            "column '{}' must be nullable to ensure safe conversion.",
1125                            col_to_change.column_name,
1126                        ),
1127                    }
1128                );
1129            }
1130            ensure!(
1131                modify_column_types
1132                    .insert(&col_to_change.column_name, col_to_change)
1133                    .is_none(),
1134                error::InvalidAlterRequestSnafu {
1135                    table: table_name,
1136                    err: format!(
1137                        "change column datatype {} more than once",
1138                        col_to_change.column_name
1139                    ),
1140                }
1141            );
1142        }
1143        // Collect columns after changed.
1144
1145        let mut columns: Vec<_> = Vec::with_capacity(table_schema.column_schemas().len());
1146        for mut column in table_schema.column_schemas().iter().cloned() {
1147            if let Some(change_column) = modify_column_types.get(&column.name) {
1148                column.data_type = change_column.target_type.clone();
1149                let new_default = if let Some(default_value) = column.default_constraint() {
1150                    Some(
1151                        default_value
1152                            .cast_to_datatype(&change_column.target_type)
1153                            .with_context(|_| error::CastDefaultValueSnafu {
1154                                reason: format!(
1155                                    "Failed to cast default value from {:?} to type {:?}",
1156                                    default_value, change_column.target_type
1157                                ),
1158                            })?,
1159                    )
1160                } else {
1161                    None
1162                };
1163                column = column
1164                    .clone()
1165                    .with_default_constraint(new_default.clone())
1166                    .with_context(|_| error::CastDefaultValueSnafu {
1167                        reason: format!("Failed to set new default: {:?}", new_default),
1168                    })?;
1169            }
1170            columns.push(column)
1171        }
1172
1173        let mut builder = SchemaBuilder::try_from_columns(columns)
1174            .with_context(|_| error::SchemaBuildSnafu {
1175                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1176            })?
1177            // Also bump the schema version.
1178            .version(table_schema.version() + 1);
1179        for (k, v) in table_schema.metadata().iter() {
1180            builder = builder.add_metadata(k, v);
1181        }
1182        let new_schema = builder.build().with_context(|_| {
1183            let column_names: Vec<_> = requests
1184                .iter()
1185                .map(|request| &request.column_name)
1186                .collect();
1187
1188            error::SchemaBuildSnafu {
1189                msg: format!(
1190                    "Table {table_name} cannot change datatype with columns {column_names:?}"
1191                ),
1192            }
1193        })?;
1194
1195        let _ = meta_builder
1196            .schema(Arc::new(new_schema))
1197            .primary_key_indices(self.primary_key_indices.clone());
1198
1199        Ok(meta_builder)
1200    }
1201
1202    fn set_json_settings(
1203        &self,
1204        table_name: &str,
1205        req: &SetJsonSettingsRequest,
1206    ) -> Result<TableMetaBuilder> {
1207        let table_schema = &self.schema;
1208        let idx = table_schema
1209            .column_index_by_name(&req.column_name)
1210            .with_context(|| error::ColumnNotExistsSnafu {
1211                column_name: &req.column_name,
1212                table_name,
1213            })?;
1214        let col = &table_schema.column_schemas()[idx];
1215
1216        ensure!(
1217            !self.primary_key_indices.contains(&idx) && table_schema.timestamp_index() != Some(idx),
1218            error::InvalidAlterRequestSnafu {
1219                table: table_name,
1220                err: format!(
1221                    "Not allowed to change JSON settings for key or timestamp column '{}'",
1222                    col.name
1223                ),
1224            }
1225        );
1226        ensure!(
1227            col.data_type.is_json2(),
1228            error::InvalidAlterRequestSnafu {
1229                table: table_name,
1230                err: format!("column '{}' is not a JSON2 column", col.name),
1231            }
1232        );
1233
1234        let target_metadata =
1235            json2_metadata_with_updated_settings(col.metadata(), req.settings.clone()).map_err(
1236                |err| {
1237                    error::InvalidAlterRequestSnafu {
1238                        table: table_name,
1239                        err: err.to_string(),
1240                    }
1241                    .build()
1242                },
1243            )?;
1244
1245        let mut cols = table_schema.column_schemas().to_vec();
1246        *cols[idx].mut_metadata() = target_metadata;
1247
1248        let mut builder = SchemaBuilder::try_from_columns(cols)
1249            .with_context(|_| error::SchemaBuildSnafu {
1250                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1251            })?
1252            .version(table_schema.version() + 1);
1253
1254        for (k, v) in table_schema.metadata().iter() {
1255            builder = builder.add_metadata(k, v);
1256        }
1257
1258        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1259            msg: format!(
1260                "Table {table_name} cannot change JSON settings for column {}",
1261                req.column_name
1262            ),
1263        })?;
1264
1265        let mut meta_builder = self.new_meta_builder();
1266        let _ = meta_builder
1267            .schema(Arc::new(new_schema))
1268            .primary_key_indices(self.primary_key_indices.clone());
1269
1270        Ok(meta_builder)
1271    }
1272
1273    /// Split requests into different groups using column location info.
1274    fn split_requests_by_column_location<'a>(
1275        &self,
1276        table_name: &str,
1277        requests: &'a [AddColumnRequest],
1278    ) -> Result<SplitResult<'a>> {
1279        let table_schema = &self.schema;
1280        let mut columns_at_first = Vec::new();
1281        let mut columns_at_after = HashMap::new();
1282        let mut columns_at_last = Vec::new();
1283        let mut column_names = Vec::with_capacity(requests.len());
1284        for request in requests {
1285            // Check whether columns to add are already existing.
1286            let column_name = &request.column_schema.name;
1287            column_names.push(column_name.clone());
1288            ensure!(
1289                table_schema.column_schema_by_name(column_name).is_none(),
1290                error::ColumnExistsSnafu {
1291                    column_name,
1292                    table_name,
1293                }
1294            );
1295            match request.location.as_ref() {
1296                Some(AddColumnLocation::First) => {
1297                    columns_at_first.push(request);
1298                }
1299                Some(AddColumnLocation::After { column_name }) => {
1300                    ensure!(
1301                        table_schema.column_schema_by_name(column_name).is_some(),
1302                        error::ColumnNotExistsSnafu {
1303                            column_name,
1304                            table_name,
1305                        }
1306                    );
1307                    columns_at_after
1308                        .entry(column_name.clone())
1309                        .or_insert(Vec::new())
1310                        .push(request);
1311                }
1312                None => {
1313                    columns_at_last.push(request);
1314                }
1315            }
1316        }
1317        Ok(SplitResult {
1318            columns_at_first,
1319            columns_at_after,
1320            columns_at_last,
1321            column_names,
1322        })
1323    }
1324
1325    fn drop_defaults(&self, table_name: &str, column_names: &[String]) -> Result<TableMetaBuilder> {
1326        let table_schema = &self.schema;
1327        let mut meta_builder = self.new_meta_builder();
1328        let mut columns = Vec::with_capacity(table_schema.num_columns());
1329        for column_schema in table_schema.column_schemas() {
1330            if let Some(name) = column_names.iter().find(|s| **s == column_schema.name) {
1331                // Drop default constraint.
1332                ensure!(
1333                    column_schema.default_constraint().is_some(),
1334                    error::InvalidAlterRequestSnafu {
1335                        table: table_name,
1336                        err: format!("column {name} does not have a default value"),
1337                    }
1338                );
1339                if !column_schema.is_nullable() {
1340                    return error::InvalidAlterRequestSnafu {
1341                        table: table_name,
1342                        err: format!(
1343                            "column {name} is not nullable and `default` cannot be dropped",
1344                        ),
1345                    }
1346                    .fail();
1347                }
1348                let new_column_schema = column_schema.clone();
1349                let new_column_schema = new_column_schema
1350                    .with_default_constraint(None)
1351                    .with_context(|_| error::SchemaBuildSnafu {
1352                        msg: format!("Table {table_name} cannot drop default values"),
1353                    })?;
1354                columns.push(new_column_schema);
1355            } else {
1356                columns.push(column_schema.clone());
1357            }
1358        }
1359
1360        let mut builder = SchemaBuilder::try_from_columns(columns)
1361            .with_context(|_| error::SchemaBuildSnafu {
1362                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1363            })?
1364            // Also bump the schema version.
1365            .version(table_schema.version() + 1);
1366        for (k, v) in table_schema.metadata().iter() {
1367            builder = builder.add_metadata(k, v);
1368        }
1369        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1370            msg: format!("Table {table_name} cannot drop default values"),
1371        })?;
1372
1373        let _ = meta_builder.schema(Arc::new(new_schema));
1374
1375        Ok(meta_builder)
1376    }
1377
1378    fn set_defaults(
1379        &self,
1380        table_name: &str,
1381        set_defaults: &[SetDefaultRequest],
1382    ) -> Result<TableMetaBuilder> {
1383        let table_schema = &self.schema;
1384        let mut meta_builder = self.new_meta_builder();
1385        let mut columns = Vec::with_capacity(table_schema.num_columns());
1386        for column_schema in table_schema.column_schemas() {
1387            if let Some(set_default) = set_defaults
1388                .iter()
1389                .find(|s| s.column_name == column_schema.name)
1390            {
1391                let new_column_schema = column_schema.clone();
1392                let new_column_schema = new_column_schema
1393                    .with_default_constraint(set_default.default_constraint.clone())
1394                    .with_context(|_| error::SchemaBuildSnafu {
1395                        msg: format!("Table {table_name} cannot set default values"),
1396                    })?;
1397                columns.push(new_column_schema);
1398            } else {
1399                columns.push(column_schema.clone());
1400            }
1401        }
1402
1403        let mut builder = SchemaBuilder::try_from_columns(columns)
1404            .with_context(|_| error::SchemaBuildSnafu {
1405                msg: format!("Failed to convert column schemas into schema for table {table_name}"),
1406            })?
1407            // Also bump the schema version.
1408            .version(table_schema.version() + 1);
1409        for (k, v) in table_schema.metadata().iter() {
1410            builder = builder.add_metadata(k, v);
1411        }
1412        let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
1413            msg: format!("Table {table_name} cannot set default values"),
1414        })?;
1415
1416        let _ = meta_builder.schema(Arc::new(new_schema));
1417
1418        Ok(meta_builder)
1419    }
1420}
1421
1422#[derive(Clone, Debug, PartialEq, Eq, Builder, Serialize, Deserialize)]
1423#[builder(pattern = "owned")]
1424pub struct TableInfo {
1425    /// Id and version of the table.
1426    #[builder(default, setter(into))]
1427    pub ident: TableIdent,
1428    /// Name of the table.
1429    #[builder(setter(into))]
1430    pub name: String,
1431    /// Comment of the table.
1432    #[builder(default, setter(into))]
1433    pub desc: Option<String>,
1434    #[builder(default = "DEFAULT_CATALOG_NAME.to_string()", setter(into))]
1435    pub catalog_name: String,
1436    #[builder(default = "DEFAULT_SCHEMA_NAME.to_string()", setter(into))]
1437    pub schema_name: String,
1438    pub meta: TableMeta,
1439    #[builder(default = "TableType::Base")]
1440    pub table_type: TableType,
1441}
1442
1443pub type TableInfoRef = Arc<TableInfo>;
1444
1445impl TableInfo {
1446    pub fn table_id(&self) -> TableId {
1447        self.ident.table_id
1448    }
1449
1450    /// Returns the full table name in the form of `{catalog}.{schema}.{table}`.
1451    pub fn full_table_name(&self) -> String {
1452        common_catalog::format_full_table_name(&self.catalog_name, &self.schema_name, &self.name)
1453    }
1454
1455    pub fn get_db_string(&self) -> String {
1456        common_catalog::build_db_string(&self.catalog_name, &self.schema_name)
1457    }
1458
1459    /// Returns true when the table is the metric engine's physical table.
1460    pub fn is_physical_table(&self) -> bool {
1461        self.meta
1462            .options
1463            .extra_options
1464            .contains_key(PHYSICAL_TABLE_METADATA_KEY)
1465    }
1466
1467    /// Return true if the table's TTL is `instant`.
1468    pub fn is_ttl_instant_table(&self) -> bool {
1469        self.meta
1470            .options
1471            .ttl
1472            .map(|t| t.is_instant())
1473            .unwrap_or(false)
1474    }
1475}
1476
1477impl TableInfoBuilder {
1478    pub fn new<S: Into<String>>(name: S, meta: TableMeta) -> Self {
1479        Self {
1480            name: Some(name.into()),
1481            meta: Some(meta),
1482            ..Default::default()
1483        }
1484    }
1485
1486    pub fn table_id(mut self, id: TableId) -> Self {
1487        let ident = self.ident.get_or_insert_with(TableIdent::default);
1488        ident.table_id = id;
1489        self
1490    }
1491
1492    pub fn table_version(mut self, version: TableVersion) -> Self {
1493        let ident = self.ident.get_or_insert_with(TableIdent::default);
1494        ident.version = version;
1495        self
1496    }
1497}
1498
1499impl TableIdent {
1500    pub fn new(table_id: TableId) -> Self {
1501        Self {
1502            table_id,
1503            version: 0,
1504        }
1505    }
1506}
1507
1508impl From<TableId> for TableIdent {
1509    fn from(table_id: TableId) -> Self {
1510        Self::new(table_id)
1511    }
1512}
1513
1514impl TableInfo {
1515    /// Returns the map of column name to column id.
1516    ///
1517    /// Note: This method may return an empty map for older versions that did not include this field.
1518    pub fn name_to_ids(&self) -> Option<HashMap<String, ColumnId>> {
1519        let column_schemas = self.meta.schema.column_schemas();
1520        if self.meta.column_ids.len() != column_schemas.len() {
1521            None
1522        } else {
1523            Some(
1524                self.meta
1525                    .column_ids
1526                    .iter()
1527                    .enumerate()
1528                    .map(|(index, id)| (column_schemas[index].name.clone(), *id))
1529                    .collect(),
1530            )
1531        }
1532    }
1533
1534    /// Sort the columns in [TableInfo], logical tables require it.
1535    pub fn sort_columns(&mut self) {
1536        let column_schemas = self.meta.schema.column_schemas();
1537        let primary_keys = self
1538            .meta
1539            .primary_key_indices
1540            .iter()
1541            .map(|index| column_schemas[*index].name.clone())
1542            .collect::<HashSet<_>>();
1543
1544        let name_to_ids = self.name_to_ids().unwrap_or_default();
1545        let mut column_schemas = column_schemas.to_vec();
1546        column_schemas.sort_unstable_by(|a, b| a.name.cmp(&b.name));
1547
1548        // Compute new indices of sorted columns
1549        let mut primary_key_indices = Vec::with_capacity(primary_keys.len());
1550        let mut value_indices = Vec::with_capacity(column_schemas.len() - primary_keys.len());
1551        let mut column_ids = Vec::with_capacity(column_schemas.len());
1552        for (index, column_schema) in column_schemas.iter().enumerate() {
1553            if primary_keys.contains(&column_schema.name) {
1554                primary_key_indices.push(index);
1555            } else {
1556                value_indices.push(index);
1557            }
1558            if let Some(id) = name_to_ids.get(&column_schema.name) {
1559                column_ids.push(*id);
1560            }
1561        }
1562
1563        // Overwrite table meta
1564        self.meta.schema = Arc::new(Schema::new_with_version(
1565            column_schemas,
1566            self.meta.schema.version(),
1567        ));
1568        self.meta.primary_key_indices = primary_key_indices;
1569        self.meta.value_indices = value_indices;
1570        self.meta.column_ids = column_ids;
1571    }
1572
1573    /// Extracts region options from table info.
1574    ///
1575    /// All "region options" are actually a copy of table options for redundancy.
1576    pub fn to_region_options(&self) -> HashMap<String, String> {
1577        let mut options = HashMap::from(&self.meta.options);
1578        options.remove(REPARTITION_COLUMN_HINT_KEY);
1579        options.remove(REPARTITION_PARTITION_NUM_HINT_KEY);
1580        options
1581    }
1582
1583    /// Returns the table reference.
1584    pub fn table_ref(&self) -> TableReference<'_> {
1585        TableReference::full(
1586            self.catalog_name.as_str(),
1587            self.schema_name.as_str(),
1588            self.name.as_str(),
1589        )
1590    }
1591}
1592
1593fn entity_option_referencing<'a>(options: &'a TableOptions, column: &str) -> Option<&'a str> {
1594    options.extra_options.iter().find_map(|(key, value)| {
1595        (parse_entity_option_key(key).is_some()
1596            && parse_entity_columns(value).iter().any(|c| c == column))
1597        .then_some(key.as_str())
1598    })
1599}
1600
1601/// Set column fulltext options if it passed the validation.
1602///
1603/// Options allowed to modify:
1604/// * backend
1605///
1606/// Options not allowed to modify:
1607/// * analyzer
1608/// * case_sensitive
1609fn set_column_fulltext_options(
1610    column_schema: &mut ColumnSchema,
1611    column_name: &str,
1612    options: &FulltextOptions,
1613    current_options: Option<FulltextOptions>,
1614) -> Result<()> {
1615    if let Some(current_options) = current_options {
1616        ensure!(
1617            current_options.analyzer == options.analyzer
1618                && current_options.case_sensitive == options.case_sensitive,
1619            error::InvalidColumnOptionSnafu {
1620                column_name,
1621                msg: format!(
1622                    "Cannot change analyzer or case_sensitive if FULLTEXT index is set before. Previous analyzer: {}, previous case_sensitive: {}",
1623                    current_options.analyzer, current_options.case_sensitive
1624                ),
1625            }
1626        );
1627    }
1628
1629    column_schema
1630        .set_fulltext_options(options)
1631        .context(error::SetFulltextOptionsSnafu { column_name })?;
1632
1633    Ok(())
1634}
1635
1636fn unset_column_fulltext_options(
1637    column_schema: &mut ColumnSchema,
1638    column_name: &str,
1639    current_options: Option<FulltextOptions>,
1640) -> Result<()> {
1641    ensure!(
1642        current_options
1643            .as_ref()
1644            .is_some_and(|options| options.enable),
1645        error::InvalidColumnOptionSnafu {
1646            column_name,
1647            msg: "FULLTEXT index already disabled".to_string(),
1648        }
1649    );
1650
1651    let mut options = current_options.unwrap();
1652    options.enable = false;
1653    column_schema
1654        .set_fulltext_options(&options)
1655        .context(error::SetFulltextOptionsSnafu { column_name })?;
1656
1657    Ok(())
1658}
1659
1660fn set_column_skipping_index_options(
1661    column_schema: &mut ColumnSchema,
1662    column_name: &str,
1663    options: &SkippingIndexOptions,
1664) -> Result<()> {
1665    column_schema
1666        .set_skipping_options(options)
1667        .context(error::SetSkippingOptionsSnafu { column_name })?;
1668
1669    Ok(())
1670}
1671
1672fn unset_column_skipping_index_options(
1673    column_schema: &mut ColumnSchema,
1674    column_name: &str,
1675) -> Result<()> {
1676    column_schema
1677        .unset_skipping_options()
1678        .context(error::UnsetSkippingOptionsSnafu { column_name })?;
1679    Ok(())
1680}
1681
1682#[cfg(test)]
1683mod tests {
1684    use std::assert_matches;
1685
1686    use common_error::ext::ErrorExt;
1687    use common_error::status_code::StatusCode;
1688    use datatypes::data_type::ConcreteDataType;
1689    use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
1690    use datatypes::json::JsonSettings;
1691    use datatypes::schema::{
1692        ColumnSchema, FulltextAnalyzer, FulltextBackend, Schema, SchemaBuilder,
1693    };
1694    use datatypes::types::JsonType;
1695
1696    use super::*;
1697    use crate::Error;
1698
1699    /// Create a test schema with 3 columns: `[col1 int32, ts timestampmills, col2 int32]`.
1700    fn new_test_schema() -> Schema {
1701        let column_schemas = vec![
1702            ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
1703            ColumnSchema::new(
1704                "ts",
1705                ConcreteDataType::timestamp_millisecond_datatype(),
1706                false,
1707            )
1708            .with_time_index(true),
1709            ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
1710        ];
1711        SchemaBuilder::try_from(column_schemas)
1712            .unwrap()
1713            .version(123)
1714            .build()
1715            .unwrap()
1716    }
1717
1718    fn add_columns_to_meta(meta: &TableMeta) -> TableMeta {
1719        let new_tag = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
1720        let new_field = ColumnSchema::new("my_field", ConcreteDataType::string_datatype(), true);
1721        let alter_kind = AlterKind::AddColumns {
1722            columns: vec![
1723                AddColumnRequest {
1724                    column_schema: new_tag,
1725                    is_key: true,
1726                    location: None,
1727                    add_if_not_exists: false,
1728                },
1729                AddColumnRequest {
1730                    column_schema: new_field,
1731                    is_key: false,
1732                    location: None,
1733                    add_if_not_exists: false,
1734                },
1735            ],
1736        };
1737
1738        let builder = meta
1739            .builder_with_alter_kind("my_table", &alter_kind)
1740            .unwrap();
1741        builder.build().unwrap()
1742    }
1743
1744    fn add_columns_to_meta_with_location(meta: &TableMeta) -> TableMeta {
1745        let new_tag = ColumnSchema::new("my_tag_first", ConcreteDataType::string_datatype(), true);
1746        let new_field = ColumnSchema::new(
1747            "my_field_after_ts",
1748            ConcreteDataType::string_datatype(),
1749            true,
1750        );
1751        let yet_another_field = ColumnSchema::new(
1752            "yet_another_field_after_ts",
1753            ConcreteDataType::int64_datatype(),
1754            true,
1755        );
1756        let alter_kind = AlterKind::AddColumns {
1757            columns: vec![
1758                AddColumnRequest {
1759                    column_schema: new_tag,
1760                    is_key: true,
1761                    location: Some(AddColumnLocation::First),
1762                    add_if_not_exists: false,
1763                },
1764                AddColumnRequest {
1765                    column_schema: new_field,
1766                    is_key: false,
1767                    location: Some(AddColumnLocation::After {
1768                        column_name: "ts".to_string(),
1769                    }),
1770                    add_if_not_exists: false,
1771                },
1772                AddColumnRequest {
1773                    column_schema: yet_another_field,
1774                    is_key: true,
1775                    location: Some(AddColumnLocation::After {
1776                        column_name: "ts".to_string(),
1777                    }),
1778                    add_if_not_exists: false,
1779                },
1780            ],
1781        };
1782
1783        let builder = meta
1784            .builder_with_alter_kind("my_table", &alter_kind)
1785            .unwrap();
1786        builder.build().unwrap()
1787    }
1788
1789    #[test]
1790    fn test_set_json_settings() {
1791        let schema = Arc::new(
1792            SchemaBuilder::try_from_columns(vec![
1793                ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
1794                ColumnSchema::new(
1795                    "ts",
1796                    ConcreteDataType::timestamp_millisecond_datatype(),
1797                    false,
1798                )
1799                .with_time_index(true),
1800                json2_column_schema_v1("payload", JsonSettings::new_v2()),
1801            ])
1802            .unwrap()
1803            .version(123)
1804            .build()
1805            .unwrap(),
1806        );
1807        let meta = TableMetaBuilder::empty()
1808            .schema(schema)
1809            .primary_key_indices(vec![0])
1810            .engine("engine")
1811            .next_column_id(3)
1812            .build()
1813            .unwrap();
1814
1815        let alter_kind = AlterKind::SetJsonSettings {
1816            request: SetJsonSettingsRequest {
1817                column_name: "payload".to_string(),
1818                settings: JsonSettings::try_new(vec![], Some(10)).unwrap(),
1819            },
1820        };
1821        let new_meta = meta
1822            .builder_with_alter_kind("my_table", &alter_kind)
1823            .unwrap()
1824            .build()
1825            .unwrap();
1826
1827        let payload = new_meta.schema.column_schema_by_name("payload").unwrap();
1828        let json_metadata: JsonMetadata =
1829            serde_json::from_str(payload.metadata().get("ARROW:extension:metadata").unwrap())
1830                .unwrap();
1831        assert!(json_metadata.is_version_2());
1832        assert_eq!(
1833            Some(10),
1834            json_metadata.json_settings().max_auto_expanded_paths()
1835        );
1836        assert_eq!(124, new_meta.schema.version());
1837        assert_eq!(meta.primary_key_indices, new_meta.primary_key_indices);
1838    }
1839
1840    fn json2_column_schema_v1(name: &str, settings: JsonSettings) -> ColumnSchema {
1841        let mut column_schema =
1842            ColumnSchema::new(name, ConcreteDataType::Json(JsonType::null()), true);
1843        column_schema.with_extension_type(&Json2ExtensionType::new(Arc::new(
1844            JsonMetadata::new_v1(settings),
1845        )));
1846        column_schema
1847    }
1848
1849    #[test]
1850    fn test_set_json_settings_rejects_non_json2_column() {
1851        let schema = Arc::new(new_test_schema());
1852        let meta = TableMetaBuilder::empty()
1853            .schema(schema)
1854            .primary_key_indices(vec![0])
1855            .engine("engine")
1856            .next_column_id(3)
1857            .build()
1858            .unwrap();
1859        let alter_kind = AlterKind::SetJsonSettings {
1860            request: SetJsonSettingsRequest {
1861                column_name: "col2".to_string(),
1862                settings: JsonSettings::new_v2(),
1863            },
1864        };
1865
1866        let err = match meta.builder_with_alter_kind("my_table", &alter_kind) {
1867            Ok(_) => panic!("expected modifying JSON settings on a non-JSON2 column to fail"),
1868            Err(err) => err,
1869        };
1870        assert!(err.to_string().contains("is not a JSON2 column"));
1871    }
1872
1873    #[test]
1874    fn test_modify_time_index_column_type() {
1875        let schema = Arc::new(new_test_schema());
1876        let meta = TableMetaBuilder::empty()
1877            .schema(schema)
1878            .primary_key_indices(vec![0])
1879            .engine("engine")
1880            .next_column_id(3)
1881            .build()
1882            .unwrap();
1883
1884        // Widening the time index unit is allowed.
1885        let alter_kind = AlterKind::ModifyColumnTypes {
1886            columns: vec![ModifyColumnTypeRequest {
1887                column_name: "ts".to_string(),
1888                target_type: ConcreteDataType::timestamp_microsecond_datatype(),
1889            }],
1890        };
1891        let new_meta = meta
1892            .builder_with_alter_kind("my_table", &alter_kind)
1893            .unwrap()
1894            .build()
1895            .unwrap();
1896        let ts_column = new_meta.schema.column_schema_by_name("ts").unwrap();
1897        assert_eq!(
1898            ConcreteDataType::timestamp_microsecond_datatype(),
1899            ts_column.data_type
1900        );
1901        assert!(ts_column.is_time_index());
1902        assert!(!ts_column.is_nullable());
1903        assert_eq!(new_meta.schema.version(), 124);
1904        assert_eq!(&[0], &new_meta.primary_key_indices[..]);
1905
1906        // Any non-widening change (narrowing, same type, non-timestamp) is
1907        // rejected.
1908        for target in [
1909            ConcreteDataType::timestamp_second_datatype(),
1910            ConcreteDataType::timestamp_millisecond_datatype(),
1911            ConcreteDataType::string_datatype(),
1912        ] {
1913            let alter_kind = AlterKind::ModifyColumnTypes {
1914                columns: vec![ModifyColumnTypeRequest {
1915                    column_name: "ts".to_string(),
1916                    target_type: target.clone(),
1917                }],
1918            };
1919            let res = meta.builder_with_alter_kind("my_table", &alter_kind);
1920            assert!(res.is_err(), "expected rejection for {target}");
1921        }
1922    }
1923
1924    #[test]
1925    fn test_modify_partition_column_type() {
1926        // `col2` is nullable and not in the primary key, so only its partition role
1927        // decides whether the change is allowed.
1928        let alter_kind = AlterKind::ModifyColumnTypes {
1929            columns: vec![ModifyColumnTypeRequest {
1930                column_name: "col2".to_string(),
1931                target_type: ConcreteDataType::float64_datatype(),
1932            }],
1933        };
1934        for (partition_key_indices, allowed) in [(vec![], true), (vec![2], false)] {
1935            let meta = TableMetaBuilder::empty()
1936                .schema(Arc::new(new_test_schema()))
1937                .primary_key_indices(vec![0])
1938                .partition_key_indices(partition_key_indices)
1939                .engine("engine")
1940                .next_column_id(3)
1941                .build()
1942                .unwrap();
1943            let result = meta.builder_with_alter_kind("my_table", &alter_kind);
1944            assert_eq!(result.is_ok(), allowed);
1945        }
1946
1947        // Widening the unit of a time index that is also a partition column is allowed.
1948        let meta = TableMetaBuilder::empty()
1949            .schema(Arc::new(new_test_schema()))
1950            .primary_key_indices(vec![0])
1951            .partition_key_indices(vec![1])
1952            .engine("engine")
1953            .next_column_id(3)
1954            .build()
1955            .unwrap();
1956        let alter_kind = AlterKind::ModifyColumnTypes {
1957            columns: vec![ModifyColumnTypeRequest {
1958                column_name: "ts".to_string(),
1959                target_type: ConcreteDataType::timestamp_microsecond_datatype(),
1960            }],
1961        };
1962        assert!(
1963            meta.builder_with_alter_kind("my_table", &alter_kind)
1964                .is_ok()
1965        );
1966    }
1967
1968    #[test]
1969    fn test_add_columns() {
1970        let schema = Arc::new(new_test_schema());
1971        let meta = TableMetaBuilder::empty()
1972            .schema(schema)
1973            .primary_key_indices(vec![0])
1974            .engine("engine")
1975            .next_column_id(3)
1976            .build()
1977            .unwrap();
1978
1979        let new_meta = add_columns_to_meta(&meta);
1980        let names: Vec<String> = new_meta
1981            .schema
1982            .column_schemas()
1983            .iter()
1984            .map(|column_schema| column_schema.name.clone())
1985            .collect();
1986        assert_eq!(&["col1", "ts", "col2", "my_tag", "my_field"], &names[..]);
1987        assert_eq!(&[0, 3], &new_meta.primary_key_indices[..]);
1988        assert_eq!(&[1, 2, 4], &new_meta.value_indices[..]);
1989    }
1990
1991    #[test]
1992    fn test_set_append_mode_true_clears_merge_mode_option() {
1993        let schema = Arc::new(new_test_schema());
1994        let mut table_options = TableOptions::default();
1995        table_options
1996            .extra_options
1997            .insert(MERGE_MODE_KEY.to_string(), "last_non_null".to_string());
1998        let meta = TableMetaBuilder::empty()
1999            .schema(schema)
2000            .primary_key_indices(vec![0])
2001            .engine("engine")
2002            .next_column_id(3)
2003            .options(table_options)
2004            .build()
2005            .unwrap();
2006
2007        let alter_kind = AlterKind::SetTableOptions {
2008            options: vec![SetRegionOption::AppendMode(true)],
2009        };
2010        let new_meta = meta
2011            .builder_with_alter_kind("my_table", &alter_kind)
2012            .unwrap()
2013            .build()
2014            .unwrap();
2015
2016        assert_eq!(
2017            Some("true"),
2018            new_meta
2019                .options
2020                .extra_options
2021                .get(APPEND_MODE_KEY)
2022                .map(String::as_str)
2023        );
2024        assert!(!new_meta.options.extra_options.contains_key(MERGE_MODE_KEY));
2025    }
2026
2027    #[test]
2028    fn test_set_append_mode_false_keeps_merge_mode_option() {
2029        let schema = Arc::new(new_test_schema());
2030        let mut table_options = TableOptions::default();
2031        table_options
2032            .extra_options
2033            .insert(MERGE_MODE_KEY.to_string(), "last_non_null".to_string());
2034        let meta = TableMetaBuilder::empty()
2035            .schema(schema)
2036            .primary_key_indices(vec![0])
2037            .engine("engine")
2038            .next_column_id(3)
2039            .options(table_options)
2040            .build()
2041            .unwrap();
2042
2043        let alter_kind = AlterKind::SetTableOptions {
2044            options: vec![SetRegionOption::AppendMode(false)],
2045        };
2046        let new_meta = meta
2047            .builder_with_alter_kind("my_table", &alter_kind)
2048            .unwrap()
2049            .build()
2050            .unwrap();
2051
2052        assert_eq!(
2053            Some("false"),
2054            new_meta
2055                .options
2056                .extra_options
2057                .get(APPEND_MODE_KEY)
2058                .map(String::as_str)
2059        );
2060        assert_eq!(
2061            Some("last_non_null"),
2062            new_meta
2063                .options
2064                .extra_options
2065                .get(MERGE_MODE_KEY)
2066                .map(String::as_str)
2067        );
2068    }
2069
2070    #[test]
2071    fn test_set_preserve_row_sequence_option() {
2072        let schema = Arc::new(new_test_schema());
2073        let meta = TableMetaBuilder::empty()
2074            .schema(schema)
2075            .primary_key_indices(vec![0])
2076            .engine("engine")
2077            .next_column_id(3)
2078            .build()
2079            .unwrap();
2080
2081        let apply = |meta: &TableMeta, kind: &AlterKind| {
2082            meta.builder_with_alter_kind("my_table", kind)
2083                .unwrap()
2084                .build()
2085                .unwrap()
2086        };
2087
2088        let with_true = apply(
2089            &meta,
2090            &AlterKind::SetTableOptions {
2091                options: vec![SetRegionOption::PreserveRowSequence(true)],
2092            },
2093        );
2094        assert_eq!(
2095            Some("true"),
2096            with_true
2097                .options
2098                .extra_options
2099                .get(PRESERVE_ROW_SEQUENCE)
2100                .map(String::as_str)
2101        );
2102
2103        let set_false = apply(
2104            &with_true,
2105            &AlterKind::SetTableOptions {
2106                options: vec![SetRegionOption::PreserveRowSequence(false)],
2107            },
2108        );
2109        assert!(
2110            !set_false
2111                .options
2112                .extra_options
2113                .contains_key(PRESERVE_ROW_SEQUENCE)
2114        );
2115
2116        let unset = apply(
2117            &with_true,
2118            &AlterKind::UnsetTableOptions {
2119                keys: vec![UnsetRegionOption::PreserveRowSequence],
2120            },
2121        );
2122        assert!(
2123            !unset
2124                .options
2125                .extra_options
2126                .contains_key(PRESERVE_ROW_SEQUENCE)
2127        );
2128    }
2129
2130    #[test]
2131    fn test_set_skip_wal_updates_typed_and_extra_options() {
2132        let mut meta = TableMetaBuilder::empty()
2133            .schema(Arc::new(new_test_schema()))
2134            .primary_key_indices(vec![0])
2135            .engine("engine")
2136            .next_column_id(3)
2137            .build()
2138            .unwrap();
2139        meta.options
2140            .extra_options
2141            .insert(SKIP_WAL_KEY.to_string(), false.to_string());
2142
2143        let alter_kind = AlterKind::SetTableOptions {
2144            options: vec![SetRegionOption::SkipWal(true)],
2145        };
2146        let new_meta = meta
2147            .builder_with_alter_kind("my_table", &alter_kind)
2148            .unwrap()
2149            .build()
2150            .unwrap();
2151
2152        assert!(new_meta.options.skip_wal);
2153        assert_eq!(
2154            Some("true"),
2155            new_meta
2156                .options
2157                .extra_options
2158                .get(SKIP_WAL_KEY)
2159                .map(String::as_str)
2160        );
2161
2162        let alter_kind = AlterKind::SetTableOptions {
2163            options: vec![SetRegionOption::SkipWal(false)],
2164        };
2165        let new_meta = new_meta
2166            .builder_with_alter_kind("my_table", &alter_kind)
2167            .unwrap()
2168            .build()
2169            .unwrap();
2170
2171        assert!(!new_meta.options.skip_wal);
2172        assert_eq!(
2173            Some("false"),
2174            new_meta
2175                .options
2176                .extra_options
2177                .get(SKIP_WAL_KEY)
2178                .map(String::as_str)
2179        );
2180    }
2181
2182    #[test]
2183    fn test_set_unset_repartition_hints_together() {
2184        let meta = TableMetaBuilder::empty()
2185            .schema(Arc::new(new_test_schema()))
2186            .primary_key_indices(vec![0])
2187            .engine("engine")
2188            .next_column_id(3)
2189            .build()
2190            .unwrap();
2191        let options = vec![
2192            (REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string()),
2193            (
2194                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2195                "10".to_string(),
2196            ),
2197        ];
2198        for reverse in [false, true] {
2199            let mut options = options.clone();
2200            if reverse {
2201                options.reverse();
2202            }
2203            let updated = meta
2204                .builder_with_alter_kind(
2205                    "t",
2206                    &AlterKind::SetAnnotations {
2207                        family: AnnotationFamily::RepartitionHint,
2208                        options: options.clone(),
2209                    },
2210                )
2211                .unwrap()
2212                .build()
2213                .unwrap();
2214            assert_eq!(
2215                updated.options.extra_options,
2216                options.iter().cloned().collect()
2217            );
2218            for (key, value) in [
2219                (REPARTITION_COLUMN_HINT_KEY, "missing"),
2220                (REPARTITION_PARTITION_NUM_HINT_KEY, "0"),
2221            ] {
2222                let invalid = options
2223                    .iter()
2224                    .map(|(k, v)| {
2225                        (
2226                            k.clone(),
2227                            if k == key {
2228                                value.to_string()
2229                            } else {
2230                                v.clone()
2231                            },
2232                        )
2233                    })
2234                    .collect();
2235                assert!(
2236                    updated
2237                        .builder_with_alter_kind(
2238                            "t",
2239                            &AlterKind::SetAnnotations {
2240                                family: AnnotationFamily::RepartitionHint,
2241                                options: invalid,
2242                            }
2243                        )
2244                        .is_err()
2245                );
2246                assert_eq!(
2247                    updated.options.extra_options,
2248                    options.iter().cloned().collect()
2249                );
2250            }
2251            let cleared = updated
2252                .builder_with_alter_kind(
2253                    "t",
2254                    &AlterKind::UnsetAnnotations {
2255                        family: AnnotationFamily::RepartitionHint,
2256                        keys: options.into_iter().map(|(key, _)| key).collect(),
2257                    },
2258                )
2259                .unwrap()
2260                .build()
2261                .unwrap();
2262            assert!(cleared.options.extra_options.is_empty());
2263        }
2264    }
2265
2266    #[test]
2267    fn test_repartition_partition_num_hint() {
2268        for partition_key_indices in [vec![], vec![0]] {
2269            let mut meta = TableMetaBuilder::empty()
2270                .schema(Arc::new(new_test_schema()))
2271                .primary_key_indices(vec![0])
2272                .partition_key_indices(partition_key_indices)
2273                .engine("engine")
2274                .next_column_id(3)
2275                .build()
2276                .unwrap();
2277            for value in ["", " ", "0", "-1", "1.5", "abc", "4294967296"] {
2278                let err = meta
2279                    .builder_with_alter_kind(
2280                        "t",
2281                        &AlterKind::SetAnnotations {
2282                            family: AnnotationFamily::RepartitionHint,
2283                            options: vec![(
2284                                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2285                                value.to_string(),
2286                            )],
2287                        },
2288                    )
2289                    .err()
2290                    .unwrap();
2291                assert!(
2292                    err.to_string().contains("expects a positive integer"),
2293                    "{err}"
2294                );
2295            }
2296            for (value, expected) in [(" 8 ", "8"), ("1", "1"), ("4294967295", "4294967295")] {
2297                meta = meta
2298                    .builder_with_alter_kind(
2299                        "t",
2300                        &AlterKind::SetAnnotations {
2301                            family: AnnotationFamily::RepartitionHint,
2302                            options: vec![(
2303                                REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2304                                value.to_string(),
2305                            )],
2306                        },
2307                    )
2308                    .unwrap()
2309                    .build()
2310                    .unwrap();
2311                assert_eq!(
2312                    meta.options.extra_options,
2313                    HashMap::from([(
2314                        REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2315                        expected.to_string()
2316                    )])
2317                );
2318            }
2319            for _ in 0..2 {
2320                meta = meta
2321                    .builder_with_alter_kind(
2322                        "t",
2323                        &AlterKind::UnsetAnnotations {
2324                            family: AnnotationFamily::RepartitionHint,
2325                            keys: vec![REPARTITION_PARTITION_NUM_HINT_KEY.to_string()],
2326                        },
2327                    )
2328                    .unwrap()
2329                    .build()
2330                    .unwrap();
2331                assert!(meta.options.extra_options.is_empty());
2332            }
2333        }
2334    }
2335
2336    #[test]
2337    fn test_set_repartition_column_hint() {
2338        let meta = TableMetaBuilder::empty()
2339            .schema(Arc::new(new_test_schema()))
2340            .primary_key_indices(vec![0])
2341            .engine("engine")
2342            .next_column_id(3)
2343            .build()
2344            .unwrap();
2345
2346        let alter_kind = AlterKind::SetAnnotations {
2347            family: AnnotationFamily::RepartitionHint,
2348            options: vec![(
2349                REPARTITION_COLUMN_HINT_KEY.to_string(),
2350                " col1 ".to_string(),
2351            )],
2352        };
2353        let new_meta = meta
2354            .builder_with_alter_kind("my_table", &alter_kind)
2355            .unwrap()
2356            .build()
2357            .unwrap();
2358
2359        assert_eq!(
2360            Some("col1"),
2361            new_meta
2362                .options
2363                .extra_options
2364                .get(REPARTITION_COLUMN_HINT_KEY)
2365                .map(String::as_str)
2366        );
2367    }
2368
2369    #[test]
2370    fn test_set_repartition_column_hint_rejects_empty_column() {
2371        let meta = TableMetaBuilder::empty()
2372            .schema(Arc::new(new_test_schema()))
2373            .primary_key_indices(vec![0])
2374            .engine("engine")
2375            .next_column_id(3)
2376            .build()
2377            .unwrap();
2378
2379        let alter_kind = AlterKind::SetAnnotations {
2380            family: AnnotationFamily::RepartitionHint,
2381            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), " ".to_string())],
2382        };
2383        let err = meta
2384            .builder_with_alter_kind("my_table", &alter_kind)
2385            .err()
2386            .unwrap();
2387
2388        assert!(
2389            err.to_string()
2390                .contains("repartition.column.hint expects exactly one column name")
2391        );
2392    }
2393
2394    #[test]
2395    fn test_set_repartition_column_hint_rejects_multiple_columns() {
2396        let meta = TableMetaBuilder::empty()
2397            .schema(Arc::new(new_test_schema()))
2398            .primary_key_indices(vec![0])
2399            .engine("engine")
2400            .next_column_id(3)
2401            .build()
2402            .unwrap();
2403
2404        let alter_kind = AlterKind::SetAnnotations {
2405            family: AnnotationFamily::RepartitionHint,
2406            options: vec![(
2407                REPARTITION_COLUMN_HINT_KEY.to_string(),
2408                "col1,col2".to_string(),
2409            )],
2410        };
2411        let err = meta
2412            .builder_with_alter_kind("my_table", &alter_kind)
2413            .err()
2414            .unwrap();
2415
2416        assert!(
2417            err.to_string()
2418                .contains("repartition.column.hint expects exactly one column name")
2419        );
2420    }
2421
2422    #[test]
2423    fn test_set_repartition_column_hint_rejects_missing_column() {
2424        let meta = TableMetaBuilder::empty()
2425            .schema(Arc::new(new_test_schema()))
2426            .primary_key_indices(vec![0])
2427            .engine("engine")
2428            .next_column_id(3)
2429            .build()
2430            .unwrap();
2431
2432        let alter_kind = AlterKind::SetAnnotations {
2433            family: AnnotationFamily::RepartitionHint,
2434            options: vec![(
2435                REPARTITION_COLUMN_HINT_KEY.to_string(),
2436                "missing".to_string(),
2437            )],
2438        };
2439        let err = meta
2440            .builder_with_alter_kind("my_table", &alter_kind)
2441            .err()
2442            .unwrap();
2443
2444        assert!(err.to_string().contains("Column missing not exists"));
2445    }
2446
2447    #[test]
2448    fn test_set_repartition_column_hint_rejects_time_index_column() {
2449        let meta = TableMetaBuilder::empty()
2450            .schema(Arc::new(new_test_schema()))
2451            .primary_key_indices(vec![0])
2452            .engine("engine")
2453            .next_column_id(3)
2454            .build()
2455            .unwrap();
2456
2457        let alter_kind = AlterKind::SetAnnotations {
2458            family: AnnotationFamily::RepartitionHint,
2459            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "ts".to_string())],
2460        };
2461        let err = meta
2462            .builder_with_alter_kind("my_table", &alter_kind)
2463            .err()
2464            .unwrap();
2465
2466        assert!(
2467            err.to_string()
2468                .contains("cannot set repartition.column.hint to the time index column")
2469        );
2470    }
2471
2472    #[test]
2473    fn test_set_repartition_column_hint_rejects_partitioned_table() {
2474        let meta = TableMetaBuilder::empty()
2475            .schema(Arc::new(new_test_schema()))
2476            .primary_key_indices(vec![0])
2477            .engine("engine")
2478            .next_column_id(3)
2479            .partition_key_indices(vec![0])
2480            .build()
2481            .unwrap();
2482
2483        let alter_kind = AlterKind::SetAnnotations {
2484            family: AnnotationFamily::RepartitionHint,
2485            options: vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string())],
2486        };
2487        let err = meta
2488            .builder_with_alter_kind("my_table", &alter_kind)
2489            .err()
2490            .unwrap();
2491
2492        assert!(
2493            err.to_string()
2494                .contains("cannot set repartition.column.hint on a table with partition metadata")
2495        );
2496    }
2497
2498    #[test]
2499    fn test_unset_repartition_column_hint() {
2500        let mut table_options = TableOptions::default();
2501        table_options
2502            .extra_options
2503            .insert(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string());
2504        let meta = TableMetaBuilder::empty()
2505            .schema(Arc::new(new_test_schema()))
2506            .primary_key_indices(vec![0])
2507            .engine("engine")
2508            .next_column_id(3)
2509            .options(table_options)
2510            .build()
2511            .unwrap();
2512
2513        let new_meta = meta
2514            .builder_with_alter_kind(
2515                "my_table",
2516                &AlterKind::UnsetAnnotations {
2517                    family: AnnotationFamily::RepartitionHint,
2518                    keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
2519                },
2520            )
2521            .unwrap()
2522            .build()
2523            .unwrap();
2524
2525        assert!(
2526            !new_meta
2527                .options
2528                .extra_options
2529                .contains_key(REPARTITION_COLUMN_HINT_KEY)
2530        );
2531    }
2532
2533    #[test]
2534    fn test_repartition_hints_are_not_region_options() {
2535        let mut table_options = TableOptions::default();
2536        table_options
2537            .extra_options
2538            .insert(REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string());
2539        table_options.extra_options.insert(
2540            REPARTITION_PARTITION_NUM_HINT_KEY.to_string(),
2541            "8".to_string(),
2542        );
2543        let table_info = TableInfoBuilder::default()
2544            .table_id(1)
2545            .table_version(0)
2546            .name("my_table")
2547            .catalog_name(DEFAULT_CATALOG_NAME)
2548            .schema_name(DEFAULT_SCHEMA_NAME)
2549            .meta(
2550                TableMetaBuilder::empty()
2551                    .schema(Arc::new(new_test_schema()))
2552                    .primary_key_indices(vec![0])
2553                    .engine("engine")
2554                    .next_column_id(3)
2555                    .options(table_options)
2556                    .build()
2557                    .unwrap(),
2558            )
2559            .build()
2560            .unwrap();
2561
2562        assert!(
2563            !table_info
2564                .to_region_options()
2565                .contains_key(REPARTITION_PARTITION_NUM_HINT_KEY)
2566        );
2567        assert!(
2568            !table_info
2569                .to_region_options()
2570                .contains_key(REPARTITION_COLUMN_HINT_KEY)
2571        );
2572    }
2573
2574    #[test]
2575    fn test_set_auto_flush_interval() {
2576        let schema = Arc::new(new_test_schema());
2577        let table_options = TableOptions::default();
2578        let meta = TableMetaBuilder::empty()
2579            .schema(schema)
2580            .primary_key_indices(vec![0])
2581            .engine("engine")
2582            .next_column_id(3)
2583            .options(table_options)
2584            .build()
2585            .unwrap();
2586
2587        let alter_kind = AlterKind::SetTableOptions {
2588            options: vec![SetRegionOption::AutoFlushInterval(Some(
2589                std::time::Duration::from_secs(300),
2590            ))],
2591        };
2592        let new_meta = meta
2593            .builder_with_alter_kind("my_table", &alter_kind)
2594            .unwrap()
2595            .build()
2596            .unwrap();
2597
2598        assert_eq!(
2599            Some("5m"),
2600            new_meta
2601                .options
2602                .extra_options
2603                .get(AUTO_FLUSH_INTERVAL_KEY)
2604                .map(String::as_str)
2605        );
2606    }
2607
2608    #[test]
2609    fn test_set_auto_flush_interval_none_removes_existing() {
2610        let schema = Arc::new(new_test_schema());
2611        let mut table_options = TableOptions::default();
2612        table_options
2613            .extra_options
2614            .insert(AUTO_FLUSH_INTERVAL_KEY.to_string(), "5m".to_string());
2615        let meta = TableMetaBuilder::empty()
2616            .schema(schema)
2617            .primary_key_indices(vec![0])
2618            .engine("engine")
2619            .next_column_id(3)
2620            .options(table_options)
2621            .build()
2622            .unwrap();
2623
2624        let alter_kind = AlterKind::SetTableOptions {
2625            options: vec![SetRegionOption::AutoFlushInterval(None)],
2626        };
2627        let new_meta = meta
2628            .builder_with_alter_kind("my_table", &alter_kind)
2629            .unwrap()
2630            .build()
2631            .unwrap();
2632
2633        assert!(
2634            !new_meta
2635                .options
2636                .extra_options
2637                .contains_key(AUTO_FLUSH_INTERVAL_KEY)
2638        );
2639    }
2640
2641    #[test]
2642    fn test_set_twcs_trigger_persists_legacy_key() {
2643        for key in [TWCS_TRIGGER_FILE_NUM, TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM] {
2644            let mut table_options = TableOptions::default();
2645            table_options.extra_options.insert(
2646                TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM.to_string(),
2647                "4".to_string(),
2648            );
2649            let meta = TableMetaBuilder::empty()
2650                .schema(Arc::new(new_test_schema()))
2651                .primary_key_indices(vec![0])
2652                .engine("engine")
2653                .next_column_id(3)
2654                .options(table_options)
2655                .build()
2656                .unwrap();
2657            let alter_kind = AlterKind::SetTableOptions {
2658                options: vec![SetRegionOption::Twsc(key.to_string(), "8".to_string())],
2659            };
2660
2661            let new_meta = meta
2662                .builder_with_alter_kind("my_table", &alter_kind)
2663                .unwrap()
2664                .build()
2665                .unwrap();
2666
2667            assert_eq!(
2668                Some("8"),
2669                new_meta
2670                    .options
2671                    .extra_options
2672                    .get(TWCS_TRIGGER_FILE_NUM)
2673                    .map(String::as_str)
2674            );
2675            assert!(
2676                !new_meta
2677                    .options
2678                    .extra_options
2679                    .contains_key(TWCS_ACTIVE_WINDOW_TRIGGER_FILE_NUM)
2680            );
2681        }
2682    }
2683
2684    #[test]
2685    fn test_set_and_unset_max_row_group_row_count() {
2686        let meta = TableMetaBuilder::empty()
2687            .schema(Arc::new(new_test_schema()))
2688            .primary_key_indices(vec![0])
2689            .engine("engine")
2690            .next_column_id(3)
2691            .options(TableOptions::default())
2692            .build()
2693            .unwrap();
2694
2695        let alter_kind = AlterKind::SetTableOptions {
2696            options: vec![SetRegionOption::MaxRowGroupRowCount(Some(512))],
2697        };
2698        let new_meta = meta
2699            .builder_with_alter_kind("my_table", &alter_kind)
2700            .unwrap()
2701            .build()
2702            .unwrap();
2703        assert_eq!(
2704            Some("512"),
2705            new_meta
2706                .options
2707                .extra_options
2708                .get(MAX_ROW_GROUP_ROW_COUNT)
2709                .map(String::as_str)
2710        );
2711
2712        let alter_kind = AlterKind::UnsetTableOptions {
2713            keys: vec![UnsetRegionOption::MaxRowGroupRowCount],
2714        };
2715        let new_meta = new_meta
2716            .builder_with_alter_kind("my_table", &alter_kind)
2717            .unwrap()
2718            .build()
2719            .unwrap();
2720        assert!(
2721            !new_meta
2722                .options
2723                .extra_options
2724                .contains_key(MAX_ROW_GROUP_ROW_COUNT)
2725        );
2726    }
2727
2728    #[test]
2729    fn test_add_columns_multiple_times() {
2730        let schema = Arc::new(new_test_schema());
2731        let meta = TableMetaBuilder::empty()
2732            .schema(schema)
2733            .primary_key_indices(vec![0])
2734            .engine("engine")
2735            .next_column_id(3)
2736            .build()
2737            .unwrap();
2738
2739        let alter_kind = AlterKind::AddColumns {
2740            columns: vec![
2741                AddColumnRequest {
2742                    column_schema: ColumnSchema::new(
2743                        "col3",
2744                        ConcreteDataType::int32_datatype(),
2745                        true,
2746                    ),
2747                    is_key: true,
2748                    location: None,
2749                    add_if_not_exists: true,
2750                },
2751                AddColumnRequest {
2752                    column_schema: ColumnSchema::new(
2753                        "col3",
2754                        ConcreteDataType::int32_datatype(),
2755                        true,
2756                    ),
2757                    is_key: true,
2758                    location: None,
2759                    add_if_not_exists: true,
2760                },
2761            ],
2762        };
2763        let err = meta
2764            .builder_with_alter_kind("my_table", &alter_kind)
2765            .err()
2766            .unwrap();
2767        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2768    }
2769
2770    #[test]
2771    fn test_remove_columns() {
2772        let schema = Arc::new(new_test_schema());
2773        let meta = TableMetaBuilder::empty()
2774            .schema(schema.clone())
2775            .primary_key_indices(vec![0])
2776            .engine("engine")
2777            .next_column_id(3)
2778            .build()
2779            .unwrap();
2780        // Add more columns so we have enough candidate columns to remove.
2781        let meta = add_columns_to_meta(&meta);
2782
2783        let alter_kind = AlterKind::DropColumns {
2784            names: vec![String::from("col2"), String::from("my_field")],
2785        };
2786        let new_meta = meta
2787            .builder_with_alter_kind("my_table", &alter_kind)
2788            .unwrap()
2789            .build()
2790            .unwrap();
2791
2792        let names: Vec<String> = new_meta
2793            .schema
2794            .column_schemas()
2795            .iter()
2796            .map(|column_schema| column_schema.name.clone())
2797            .collect();
2798        assert_eq!(&["col1", "ts", "my_tag"], &names[..]);
2799        assert_eq!(&[0, 2], &new_meta.primary_key_indices[..]);
2800        assert_eq!(&[1], &new_meta.value_indices[..]);
2801        assert_eq!(
2802            schema.timestamp_column(),
2803            new_meta.schema.timestamp_column()
2804        );
2805    }
2806
2807    #[test]
2808    fn test_remove_multiple_columns_before_timestamp() {
2809        let column_schemas = vec![
2810            ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
2811            ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
2812            ColumnSchema::new("col3", ConcreteDataType::int32_datatype(), true),
2813            ColumnSchema::new(
2814                "ts",
2815                ConcreteDataType::timestamp_millisecond_datatype(),
2816                false,
2817            )
2818            .with_time_index(true),
2819        ];
2820        let schema = Arc::new(
2821            SchemaBuilder::try_from(column_schemas)
2822                .unwrap()
2823                .version(123)
2824                .build()
2825                .unwrap(),
2826        );
2827        let meta = TableMetaBuilder::empty()
2828            .schema(schema.clone())
2829            .primary_key_indices(vec![1])
2830            .engine("engine")
2831            .next_column_id(4)
2832            .build()
2833            .unwrap();
2834
2835        // Remove columns in reverse order to test whether timestamp index is valid.
2836        let alter_kind = AlterKind::DropColumns {
2837            names: vec![String::from("col3"), String::from("col1")],
2838        };
2839        let new_meta = meta
2840            .builder_with_alter_kind("my_table", &alter_kind)
2841            .unwrap()
2842            .build()
2843            .unwrap();
2844
2845        let names: Vec<String> = new_meta
2846            .schema
2847            .column_schemas()
2848            .iter()
2849            .map(|column_schema| column_schema.name.clone())
2850            .collect();
2851        assert_eq!(&["col2", "ts"], &names[..]);
2852        assert_eq!(&[0], &new_meta.primary_key_indices[..]);
2853        assert_eq!(&[1], &new_meta.value_indices[..]);
2854        assert_eq!(
2855            schema.timestamp_column(),
2856            new_meta.schema.timestamp_column()
2857        );
2858    }
2859
2860    #[test]
2861    fn test_add_existing_column() {
2862        let schema = Arc::new(new_test_schema());
2863        let meta = TableMetaBuilder::empty()
2864            .schema(schema)
2865            .primary_key_indices(vec![0])
2866            .engine("engine")
2867            .next_column_id(3)
2868            .build()
2869            .unwrap();
2870
2871        let alter_kind = AlterKind::AddColumns {
2872            columns: vec![AddColumnRequest {
2873                column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
2874                is_key: false,
2875                location: None,
2876                add_if_not_exists: false,
2877            }],
2878        };
2879
2880        let err = meta
2881            .builder_with_alter_kind("my_table", &alter_kind)
2882            .err()
2883            .unwrap();
2884        assert_eq!(StatusCode::TableColumnExists, err.status_code());
2885
2886        // Add if not exists
2887        let alter_kind = AlterKind::AddColumns {
2888            columns: vec![AddColumnRequest {
2889                column_schema: ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
2890                is_key: true,
2891                location: None,
2892                add_if_not_exists: true,
2893            }],
2894        };
2895        let new_meta = meta
2896            .builder_with_alter_kind("my_table", &alter_kind)
2897            .unwrap()
2898            .build()
2899            .unwrap();
2900        assert_eq!(
2901            meta.schema.column_schemas(),
2902            new_meta.schema.column_schemas()
2903        );
2904        assert_eq!(meta.schema.version() + 1, new_meta.schema.version());
2905    }
2906
2907    #[test]
2908    fn test_add_different_type_column() {
2909        let schema = Arc::new(new_test_schema());
2910        let meta = TableMetaBuilder::empty()
2911            .schema(schema)
2912            .primary_key_indices(vec![0])
2913            .engine("engine")
2914            .next_column_id(3)
2915            .build()
2916            .unwrap();
2917
2918        // Add if not exists, but different type.
2919        let alter_kind = AlterKind::AddColumns {
2920            columns: vec![AddColumnRequest {
2921                column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
2922                is_key: false,
2923                location: None,
2924                add_if_not_exists: true,
2925            }],
2926        };
2927        let err = meta
2928            .builder_with_alter_kind("my_table", &alter_kind)
2929            .err()
2930            .unwrap();
2931        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2932    }
2933
2934    #[test]
2935    fn test_add_invalid_column() {
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        // Not nullable and no default value.
2946        let alter_kind = AlterKind::AddColumns {
2947            columns: vec![AddColumnRequest {
2948                column_schema: ColumnSchema::new(
2949                    "weny",
2950                    ConcreteDataType::string_datatype(),
2951                    false,
2952                ),
2953                is_key: false,
2954                location: None,
2955                add_if_not_exists: false,
2956            }],
2957        };
2958
2959        let err = meta
2960            .builder_with_alter_kind("my_table", &alter_kind)
2961            .err()
2962            .unwrap();
2963        assert_eq!(StatusCode::InvalidArguments, err.status_code());
2964    }
2965
2966    #[test]
2967    fn test_remove_unknown_column() {
2968        let schema = Arc::new(new_test_schema());
2969        let meta = TableMetaBuilder::empty()
2970            .schema(schema)
2971            .primary_key_indices(vec![0])
2972            .engine("engine")
2973            .next_column_id(3)
2974            .build()
2975            .unwrap();
2976
2977        let alter_kind = AlterKind::DropColumns {
2978            names: vec![String::from("unknown")],
2979        };
2980
2981        let err = meta
2982            .builder_with_alter_kind("my_table", &alter_kind)
2983            .err()
2984            .unwrap();
2985        assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
2986    }
2987
2988    #[test]
2989    fn test_change_unknown_column_data_type() {
2990        let schema = Arc::new(new_test_schema());
2991        let meta = TableMetaBuilder::empty()
2992            .schema(schema)
2993            .primary_key_indices(vec![0])
2994            .engine("engine")
2995            .next_column_id(3)
2996            .build()
2997            .unwrap();
2998
2999        let alter_kind = AlterKind::ModifyColumnTypes {
3000            columns: vec![ModifyColumnTypeRequest {
3001                column_name: "unknown".to_string(),
3002                target_type: ConcreteDataType::string_datatype(),
3003            }],
3004        };
3005
3006        let err = meta
3007            .builder_with_alter_kind("my_table", &alter_kind)
3008            .err()
3009            .unwrap();
3010        assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
3011    }
3012
3013    #[test]
3014    fn test_remove_key_column() {
3015        let schema = Arc::new(new_test_schema());
3016        let meta = TableMetaBuilder::empty()
3017            .schema(schema)
3018            .primary_key_indices(vec![0])
3019            .engine("engine")
3020            .next_column_id(3)
3021            .build()
3022            .unwrap();
3023
3024        // Remove column in primary key.
3025        let alter_kind = AlterKind::DropColumns {
3026            names: vec![String::from("col1")],
3027        };
3028
3029        let err = meta
3030            .builder_with_alter_kind("my_table", &alter_kind)
3031            .err()
3032            .unwrap();
3033        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3034
3035        // Remove timestamp column.
3036        let alter_kind = AlterKind::DropColumns {
3037            names: vec![String::from("ts")],
3038        };
3039
3040        let err = meta
3041            .builder_with_alter_kind("my_table", &alter_kind)
3042            .err()
3043            .unwrap();
3044        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3045    }
3046
3047    #[test]
3048    fn test_remove_partition_column() {
3049        let schema = Arc::new(new_test_schema());
3050        let meta = TableMetaBuilder::empty()
3051            .schema(schema)
3052            .primary_key_indices(vec![])
3053            .partition_key_indices(vec![0])
3054            .engine("engine")
3055            .next_column_id(3)
3056            .build()
3057            .unwrap();
3058        // Remove column in primary key.
3059        let alter_kind = AlterKind::DropColumns {
3060            names: vec![String::from("col1")],
3061        };
3062
3063        let err = meta
3064            .builder_with_alter_kind("my_table", &alter_kind)
3065            .err()
3066            .unwrap();
3067        assert_matches!(err, Error::RemovePartitionColumn { .. });
3068    }
3069
3070    #[test]
3071    fn test_change_key_column_data_type() {
3072        let schema = Arc::new(new_test_schema());
3073        let meta = TableMetaBuilder::empty()
3074            .schema(schema)
3075            .primary_key_indices(vec![0])
3076            .engine("engine")
3077            .next_column_id(3)
3078            .build()
3079            .unwrap();
3080
3081        // Remove column in primary key.
3082        let alter_kind = AlterKind::ModifyColumnTypes {
3083            columns: vec![ModifyColumnTypeRequest {
3084                column_name: "col1".to_string(),
3085                target_type: ConcreteDataType::string_datatype(),
3086            }],
3087        };
3088
3089        let err = meta
3090            .builder_with_alter_kind("my_table", &alter_kind)
3091            .err()
3092            .unwrap();
3093        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3094
3095        // Remove timestamp column.
3096        let alter_kind = AlterKind::ModifyColumnTypes {
3097            columns: vec![ModifyColumnTypeRequest {
3098                column_name: "ts".to_string(),
3099                target_type: ConcreteDataType::string_datatype(),
3100            }],
3101        };
3102
3103        let err = meta
3104            .builder_with_alter_kind("my_table", &alter_kind)
3105            .err()
3106            .unwrap();
3107        assert_eq!(StatusCode::InvalidArguments, err.status_code());
3108    }
3109
3110    #[test]
3111    fn test_alloc_new_column() {
3112        let schema = Arc::new(new_test_schema());
3113        let mut meta = TableMetaBuilder::empty()
3114            .schema(schema)
3115            .primary_key_indices(vec![0])
3116            .engine("engine")
3117            .next_column_id(3)
3118            .build()
3119            .unwrap();
3120        assert_eq!(3, meta.next_column_id);
3121
3122        let column_schema = ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true);
3123        let desc = meta.alloc_new_column("test_table", &column_schema).unwrap();
3124
3125        assert_eq!(4, meta.next_column_id);
3126        assert_eq!(column_schema.name, desc.name);
3127    }
3128
3129    #[test]
3130    fn test_add_columns_with_location() {
3131        let schema = Arc::new(new_test_schema());
3132        let meta = TableMetaBuilder::empty()
3133            .schema(schema)
3134            .primary_key_indices(vec![0])
3135            // partition col: col1, col2
3136            .partition_key_indices(vec![0, 2])
3137            .engine("engine")
3138            .next_column_id(3)
3139            .build()
3140            .unwrap();
3141
3142        let new_meta = add_columns_to_meta_with_location(&meta);
3143        let names: Vec<String> = new_meta
3144            .schema
3145            .column_schemas()
3146            .iter()
3147            .map(|column_schema| column_schema.name.clone())
3148            .collect();
3149        assert_eq!(
3150            &[
3151                "my_tag_first",               // primary key column
3152                "col1",                       // partition column
3153                "ts",                         // timestamp column
3154                "yet_another_field_after_ts", // primary key column
3155                "my_field_after_ts",          // value column
3156                "col2",                       // partition column
3157            ],
3158            &names[..]
3159        );
3160        assert_eq!(&[0, 1, 3], &new_meta.primary_key_indices[..]);
3161        assert_eq!(&[2, 4, 5], &new_meta.value_indices[..]);
3162        assert_eq!(&[1, 5], &new_meta.partition_key_indices[..]);
3163    }
3164
3165    #[test]
3166    fn test_modify_column_fulltext_options() {
3167        let schema = Arc::new(new_test_schema());
3168        let meta = TableMetaBuilder::empty()
3169            .schema(schema)
3170            .primary_key_indices(vec![0])
3171            .engine("engine")
3172            .next_column_id(3)
3173            .build()
3174            .unwrap();
3175
3176        let alter_kind = AlterKind::SetIndexes {
3177            options: vec![SetIndexOption::Fulltext {
3178                column_name: "col1".to_string(),
3179                options: FulltextOptions::default(),
3180            }],
3181        };
3182        let err = meta
3183            .builder_with_alter_kind("my_table", &alter_kind)
3184            .err()
3185            .unwrap();
3186        assert_eq!(
3187            "Invalid column option, column name: col1, error: FULLTEXT index only supports string type",
3188            err.to_string()
3189        );
3190
3191        // Add a string column and make it fulltext indexed
3192        let new_meta = add_columns_to_meta_with_location(&meta);
3193        let alter_kind = AlterKind::SetIndexes {
3194            options: vec![SetIndexOption::Fulltext {
3195                column_name: "my_tag_first".to_string(),
3196                options: FulltextOptions::new_unchecked(
3197                    true,
3198                    FulltextAnalyzer::Chinese,
3199                    true,
3200                    FulltextBackend::Bloom,
3201                    1000,
3202                    0.01,
3203                ),
3204            }],
3205        };
3206        let new_meta = new_meta
3207            .builder_with_alter_kind("my_table", &alter_kind)
3208            .unwrap()
3209            .build()
3210            .unwrap();
3211        let column_schema = new_meta
3212            .schema
3213            .column_schema_by_name("my_tag_first")
3214            .unwrap();
3215        let fulltext_options = column_schema.fulltext_options().unwrap().unwrap();
3216        assert!(fulltext_options.enable);
3217        assert_eq!(
3218            datatypes::schema::FulltextAnalyzer::Chinese,
3219            fulltext_options.analyzer
3220        );
3221        assert!(fulltext_options.case_sensitive);
3222
3223        let alter_kind = AlterKind::UnsetIndexes {
3224            options: vec![UnsetIndexOption::Fulltext {
3225                column_name: "my_tag_first".to_string(),
3226            }],
3227        };
3228        let new_meta = new_meta
3229            .builder_with_alter_kind("my_table", &alter_kind)
3230            .unwrap()
3231            .build()
3232            .unwrap();
3233        let column_schema = new_meta
3234            .schema
3235            .column_schema_by_name("my_tag_first")
3236            .unwrap();
3237        let fulltext_options = column_schema.fulltext_options().unwrap().unwrap();
3238        assert!(!fulltext_options.enable);
3239    }
3240
3241    #[test]
3242    fn test_table_info_serde_compatibility() {
3243        // "serialized" is generated by the following codes before this refactor (PR 7626):
3244        //
3245        // ```Rust
3246        // serde_json::to_string(&RawTableInfo::from(TableInfo {
3247        //     ident: TableIdent {
3248        //         table_id: 1024,
3249        //         version: 1,
3250        //     },
3251        //     name: "foo".to_string(),
3252        //     desc: Some("my table".to_string()),
3253        //     catalog_name: "greptime".to_string(),
3254        //     schema_name: "public".to_string(),
3255        //     meta: TableMeta {
3256        //         schema: Arc::new(new_test_schema()),
3257        //         primary_key_indices: vec![0],
3258        //         value_indices: vec![1, 2],
3259        //         engine: "mito".to_string(),
3260        //         next_column_id: 3,
3261        //         options: TableOptions {
3262        //             ttl: Some(common_time::TimeToLive::Duration(
3263        //                 std::time::Duration::from_secs(3600),
3264        //             )),
3265        //             ..Default::default()
3266        //         },
3267        //         created_on: DateTime::<Utc>::MIN_UTC,
3268        //         updated_on: DateTime::<Utc>::MAX_UTC,
3269        //         partition_key_indices: vec![2],
3270        //         column_ids: vec![0, 1, 2],
3271        //     },
3272        //     table_type: TableType::Base,
3273        // }))
3274        // ```
3275        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"}"#;
3276
3277        let actual: TableInfo = serde_json::from_str(serialized).unwrap();
3278        let expected = TableInfo {
3279            ident: TableIdent {
3280                table_id: 1024,
3281                version: 1,
3282            },
3283            name: "foo".to_string(),
3284            desc: Some("my table".to_string()),
3285            catalog_name: "greptime".to_string(),
3286            schema_name: "public".to_string(),
3287            meta: TableMeta {
3288                schema: Arc::new(new_test_schema()),
3289                primary_key_indices: vec![0],
3290                value_indices: vec![1, 2],
3291                engine: "mito".to_string(),
3292                next_column_id: 3,
3293                options: TableOptions {
3294                    ttl: Some(common_time::TimeToLive::Duration(
3295                        std::time::Duration::from_secs(3600),
3296                    )),
3297                    ..Default::default()
3298                },
3299                created_on: DateTime::<Utc>::MIN_UTC,
3300                updated_on: DateTime::<Utc>::MAX_UTC,
3301                partition_key_indices: vec![2],
3302                column_ids: vec![0, 1, 2],
3303            },
3304            table_type: TableType::Base,
3305        };
3306        assert_eq!(actual, expected);
3307    }
3308
3309    use crate::requests::{SEMANTIC_METRIC_UNIT, SEMANTIC_SIGNAL_TYPE};
3310
3311    /// `host` is a tag, `service` and `note` are string fields, `payload` is a
3312    /// binary field.
3313    fn semantic_test_meta() -> TableMeta {
3314        let column_schemas = vec![
3315            ColumnSchema::new("host", ConcreteDataType::string_datatype(), true),
3316            ColumnSchema::new(
3317                "ts",
3318                ConcreteDataType::timestamp_millisecond_datatype(),
3319                false,
3320            )
3321            .with_time_index(true),
3322            ColumnSchema::new("payload", ConcreteDataType::binary_datatype(), true),
3323            ColumnSchema::new("service", ConcreteDataType::string_datatype(), true),
3324            ColumnSchema::new("note", ConcreteDataType::string_datatype(), true),
3325        ];
3326        let schema = Arc::new(
3327            SchemaBuilder::try_from(column_schemas)
3328                .unwrap()
3329                .build()
3330                .unwrap(),
3331        );
3332        TableMetaBuilder::empty()
3333            .schema(schema)
3334            .primary_key_indices(vec![0])
3335            .engine("engine")
3336            .next_column_id(5)
3337            .build()
3338            .unwrap()
3339    }
3340
3341    #[test]
3342    fn test_set_semantic_annotations() {
3343        let meta = semantic_test_meta();
3344        // `service` is a plain field: entity columns may be tags or fields.
3345        let alter_kind = AlterKind::SetAnnotations {
3346            family: AnnotationFamily::Semantic,
3347            options: vec![
3348                (SEMANTIC_SIGNAL_TYPE.to_string(), "trace".to_string()),
3349                (
3350                    "greptime.semantic.entity.service.id".to_string(),
3351                    "service".to_string(),
3352                ),
3353            ],
3354        };
3355        let new_meta = meta
3356            .builder_with_alter_kind("my_table", &alter_kind)
3357            .unwrap()
3358            .build()
3359            .unwrap();
3360        assert_eq!(
3361            new_meta.options.extra_options.get(SEMANTIC_SIGNAL_TYPE),
3362            Some(&"trace".to_string())
3363        );
3364        assert_eq!(
3365            new_meta
3366                .options
3367                .extra_options
3368                .get("greptime.semantic.entity.service.id"),
3369            Some(&"service".to_string())
3370        );
3371    }
3372
3373    #[test]
3374    fn test_repartition_hint_batch_rejects_duplicate_keys() {
3375        let meta = TableMetaBuilder::empty()
3376            .schema(Arc::new(new_test_schema()))
3377            .primary_key_indices(vec![0])
3378            .engine("engine")
3379            .next_column_id(3)
3380            .build()
3381            .unwrap();
3382
3383        // Direct gRPC can hand the mutation layer a duplicated batch the
3384        // converter never saw; last-write-wins must not silently apply.
3385        let dup_set = AlterKind::SetAnnotations {
3386            family: AnnotationFamily::RepartitionHint,
3387            options: vec![
3388                (REPARTITION_COLUMN_HINT_KEY.to_string(), "col1".to_string()),
3389                (REPARTITION_COLUMN_HINT_KEY.to_string(), "col2".to_string()),
3390            ],
3391        };
3392        let err = meta
3393            .builder_with_alter_kind("my_table", &dup_set)
3394            .err()
3395            .unwrap();
3396        assert!(
3397            err.to_string().contains("duplicate repartition hint keys"),
3398            "{err}"
3399        );
3400
3401        let dup_unset = AlterKind::UnsetAnnotations {
3402            family: AnnotationFamily::RepartitionHint,
3403            keys: vec![
3404                REPARTITION_COLUMN_HINT_KEY.to_string(),
3405                REPARTITION_COLUMN_HINT_KEY.to_string(),
3406            ],
3407        };
3408        let err = meta
3409            .builder_with_alter_kind("my_table", &dup_unset)
3410            .err()
3411            .unwrap();
3412        assert!(
3413            err.to_string().contains("duplicate repartition hint keys"),
3414            "{err}"
3415        );
3416    }
3417
3418    #[test]
3419    fn test_set_semantic_annotations_rejects_invalid() {
3420        let meta = semantic_test_meta();
3421        let cases = [
3422            (
3423                "greptime.semantic.unknown_key",
3424                "x",
3425                "unknown semantic option",
3426            ),
3427            (SEMANTIC_SIGNAL_TYPE, "garbage", "invalid value"),
3428            (
3429                "greptime.semantic.entity.host.id",
3430                "no_such_column",
3431                "no_such_column",
3432            ),
3433            (
3434                "greptime.semantic.entity.host.id",
3435                "payload",
3436                "cannot render as a string",
3437            ),
3438        ];
3439        for (key, value, needle) in cases {
3440            let alter_kind = AlterKind::SetAnnotations {
3441                family: AnnotationFamily::Semantic,
3442                options: vec![(key.to_string(), value.to_string())],
3443            };
3444            let err = meta
3445                .builder_with_alter_kind("my_table", &alter_kind)
3446                .err()
3447                .unwrap();
3448            assert!(
3449                err.to_string().contains(needle),
3450                "key `{key}`: unexpected error `{err}`"
3451            );
3452        }
3453
3454        // ALTER keeps missing columns on the 4002 contract (pinned by sqlness);
3455        // the shared validator must not collapse it into InvalidArguments.
3456        let missing = meta
3457            .builder_with_alter_kind(
3458                "my_table",
3459                &AlterKind::SetAnnotations {
3460                    family: AnnotationFamily::Semantic,
3461                    options: vec![(
3462                        "greptime.semantic.entity.host.id".to_string(),
3463                        "no_such_column".to_string(),
3464                    )],
3465                },
3466            )
3467            .err()
3468            .unwrap();
3469        assert_eq!(
3470            common_error::status_code::StatusCode::TableColumnNotFound,
3471            common_error::ext::ErrorExt::status_code(&missing)
3472        );
3473    }
3474
3475    #[test]
3476    fn test_unset_semantic_annotations() {
3477        let mut meta = semantic_test_meta();
3478        meta.options
3479            .extra_options
3480            .insert(SEMANTIC_SIGNAL_TYPE.to_string(), "trace".to_string());
3481        // A key this version does not recognise, still inside the namespace.
3482        meta.options
3483            .extra_options
3484            .insert("greptime.semantic.future_key".to_string(), "x".to_string());
3485
3486        let alter_kind = AlterKind::UnsetAnnotations {
3487            family: AnnotationFamily::Semantic,
3488            keys: vec![
3489                SEMANTIC_SIGNAL_TYPE.to_string(),
3490                "greptime.semantic.future_key".to_string(),
3491                // Absent key: removal is a no-op, not an error.
3492                SEMANTIC_METRIC_UNIT.to_string(),
3493            ],
3494        };
3495        let new_meta = meta
3496            .builder_with_alter_kind("my_table", &alter_kind)
3497            .unwrap()
3498            .build()
3499            .unwrap();
3500        assert!(
3501            !new_meta
3502                .options
3503                .extra_options
3504                .contains_key(SEMANTIC_SIGNAL_TYPE)
3505        );
3506        assert!(
3507            !new_meta
3508                .options
3509                .extra_options
3510                .contains_key("greptime.semantic.future_key")
3511        );
3512
3513        let outside = AlterKind::UnsetAnnotations {
3514            family: AnnotationFamily::Semantic,
3515            keys: vec!["ttl".to_string()],
3516        };
3517        let err = meta
3518            .builder_with_alter_kind("my_table", &outside)
3519            .err()
3520            .unwrap();
3521        assert!(err.to_string().contains("annotation namespace"), "{err}");
3522    }
3523
3524    #[test]
3525    fn test_modify_entity_column_type_keeps_string_form() {
3526        let mut meta = semantic_test_meta();
3527        meta.options.extra_options.insert(
3528            "greptime.semantic.entity.service.id".to_string(),
3529            "service".to_string(),
3530        );
3531
3532        let alter_kind = AlterKind::ModifyColumnTypes {
3533            columns: vec![ModifyColumnTypeRequest {
3534                column_name: "service".to_string(),
3535                target_type: ConcreteDataType::binary_datatype(),
3536            }],
3537        };
3538        let err = meta
3539            .builder_with_alter_kind("my_table", &alter_kind)
3540            .err()
3541            .unwrap();
3542        assert!(
3543            err.to_string()
3544                .contains("must keep a type that renders as a string"),
3545            "{err}"
3546        );
3547
3548        // An unreferenced column may still change to a non-string form.
3549        let alter_kind = AlterKind::ModifyColumnTypes {
3550            columns: vec![ModifyColumnTypeRequest {
3551                column_name: "note".to_string(),
3552                target_type: ConcreteDataType::binary_datatype(),
3553            }],
3554        };
3555        meta.builder_with_alter_kind("my_table", &alter_kind)
3556            .unwrap();
3557    }
3558
3559    #[test]
3560    fn test_stale_entity_declaration_guards_readd_and_reports_missing_column() {
3561        // A stale declaration: `gone` was dropped after being declared.
3562        let mut meta = semantic_test_meta();
3563        meta.options.extra_options.insert(
3564            "greptime.semantic.entity.service.id".to_string(),
3565            "gone".to_string(),
3566        );
3567
3568        // MODIFY on the missing column keeps the ColumnNotExists contract
3569        // (4002), stale declaration or not.
3570        let modify = AlterKind::ModifyColumnTypes {
3571            columns: vec![ModifyColumnTypeRequest {
3572                column_name: "gone".to_string(),
3573                target_type: ConcreteDataType::binary_datatype(),
3574            }],
3575        };
3576        let err = meta
3577            .builder_with_alter_kind("my_table", &modify)
3578            .err()
3579            .unwrap();
3580        assert_eq!(
3581            common_error::status_code::StatusCode::TableColumnNotFound,
3582            common_error::ext::ErrorExt::status_code(&err)
3583        );
3584
3585        // Re-adding the declared column must not hand the declaration a
3586        // non-string type...
3587        let add = |ty: ConcreteDataType| AlterKind::AddColumns {
3588            columns: vec![AddColumnRequest {
3589                column_schema: ColumnSchema::new("gone", ty, true),
3590                is_key: false,
3591                location: None,
3592                add_if_not_exists: false,
3593            }],
3594        };
3595        let err = meta
3596            .builder_with_alter_kind("my_table", &add(ConcreteDataType::binary_datatype()))
3597            .err()
3598            .unwrap();
3599        assert!(
3600            err.to_string()
3601                .contains("must keep a type that renders as a string"),
3602            "{err}"
3603        );
3604
3605        // ...while a string re-add re-satisfies it.
3606        meta.builder_with_alter_kind("my_table", &add(ConcreteDataType::string_datatype()))
3607            .unwrap();
3608    }
3609}