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