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