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