Skip to main content

common_grpc_expr/
alter.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 api::helper::ColumnDataTypeWrapper;
16use api::v1::add_column_location::LocationType;
17use api::v1::alter_table_expr::Kind;
18use api::v1::column_def::{
19    as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type,
20};
21use api::v1::{
22    AddColumnLocation as Location, AlterTableExpr, Analyzer, CreateTableExpr, DropColumns,
23    FulltextBackend as PbFulltextBackend, ModifyColumnTypes, RenameTable, SemanticType,
24    SkippingIndexType as PbSkippingIndexType, column_def,
25};
26use common_query::AddColumnLocation;
27use datatypes::schema::{ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions};
28use snafu::{OptionExt, ResultExt, ensure};
29use store_api::region_request::{SetRegionOption, UnsetRegionOption};
30use table::metadata::{TableId, TableMeta};
31use table::requests::{
32    AddColumnRequest, AlterKind, AlterTableRequest, ModifyColumnTypeRequest,
33    REPARTITION_COLUMN_HINT_KEY, SetDefaultRequest, SetIndexOption, UnsetIndexOption,
34};
35
36use crate::error::{
37    self, ColumnNotFoundSnafu, InvalidColumnDefSnafu, InvalidIndexOptionSnafu,
38    InvalidSetFulltextOptionRequestSnafu, InvalidSetSkippingIndexOptionRequestSnafu,
39    InvalidSetTableOptionRequestSnafu, InvalidUnsetTableOptionRequestSnafu,
40    MissingAlterIndexOptionSnafu, MissingFieldSnafu, MissingTableMetaSnafu,
41    MissingTimestampColumnSnafu, Result, UnknownLocationTypeSnafu,
42};
43
44const LOCATION_TYPE_FIRST: i32 = LocationType::First as i32;
45const LOCATION_TYPE_AFTER: i32 = LocationType::After as i32;
46
47fn set_index_option_from_proto(set_index: api::v1::SetIndex) -> Result<SetIndexOption> {
48    let options = set_index.options.context(MissingAlterIndexOptionSnafu)?;
49    Ok(match options {
50        api::v1::set_index::Options::Fulltext(f) => SetIndexOption::Fulltext {
51            column_name: f.column_name.clone(),
52            options: FulltextOptions::new(
53                f.enable,
54                as_fulltext_option_analyzer(
55                    Analyzer::try_from(f.analyzer).context(InvalidSetFulltextOptionRequestSnafu)?,
56                ),
57                f.case_sensitive,
58                as_fulltext_option_backend(
59                    PbFulltextBackend::try_from(f.backend)
60                        .context(InvalidSetFulltextOptionRequestSnafu)?,
61                ),
62                f.granularity as u32,
63                f.false_positive_rate,
64            )
65            .context(InvalidIndexOptionSnafu)?,
66        },
67        api::v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
68            column_name: i.column_name,
69        },
70        api::v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
71            column_name: s.column_name,
72            options: SkippingIndexOptions::new(
73                s.granularity as u32,
74                s.false_positive_rate,
75                as_skipping_index_type(
76                    PbSkippingIndexType::try_from(s.skipping_index_type)
77                        .context(InvalidSetSkippingIndexOptionRequestSnafu)?,
78                ),
79            )
80            .context(InvalidIndexOptionSnafu)?,
81        },
82    })
83}
84
85fn unset_index_option_from_proto(unset_index: api::v1::UnsetIndex) -> Result<UnsetIndexOption> {
86    let options = unset_index.options.context(MissingAlterIndexOptionSnafu)?;
87    Ok(match options {
88        api::v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
89            column_name: f.column_name,
90        },
91        api::v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
92            column_name: i.column_name,
93        },
94        api::v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
95            column_name: s.column_name,
96        },
97    })
98}
99
100/// Convert an [`AlterTableExpr`] to an [`AlterTableRequest`]
101///
102/// note: `table_meta` must not be None if [`AlterTableExpr`] is `SetDefault`
103pub fn alter_expr_to_request(
104    table_id: TableId,
105    expr: AlterTableExpr,
106    table_meta: Option<&TableMeta>,
107) -> Result<AlterTableRequest> {
108    let catalog_name = expr.catalog_name;
109    let schema_name = expr.schema_name;
110    let kind = expr.kind.context(MissingFieldSnafu { field: "kind" })?;
111    let alter_kind = match kind {
112        Kind::AddColumns(add_columns) => {
113            let add_column_requests = add_columns
114                .add_columns
115                .into_iter()
116                .map(|ac| {
117                    let column_def = ac.column_def.context(MissingFieldSnafu {
118                        field: "column_def",
119                    })?;
120
121                    let schema = column_def::try_as_column_schema(&column_def).context(
122                        InvalidColumnDefSnafu {
123                            column: &column_def.name,
124                        },
125                    )?;
126                    Ok(AddColumnRequest {
127                        column_schema: schema,
128                        is_key: column_def.semantic_type == SemanticType::Tag as i32,
129                        location: parse_location(ac.location)?,
130                        add_if_not_exists: ac.add_if_not_exists,
131                    })
132                })
133                .collect::<Result<Vec<_>>>()?;
134
135            AlterKind::AddColumns {
136                columns: add_column_requests,
137            }
138        }
139        Kind::ModifyColumnTypes(ModifyColumnTypes {
140            modify_column_types,
141        }) => {
142            let modify_column_type_requests = modify_column_types
143                .into_iter()
144                .map(|cct| {
145                    let target_type =
146                        ColumnDataTypeWrapper::new(cct.target_type(), cct.target_type_extension)
147                            .into();
148
149                    Ok(ModifyColumnTypeRequest {
150                        column_name: cct.column_name,
151                        target_type,
152                    })
153                })
154                .collect::<Result<Vec<_>>>()?;
155
156            AlterKind::ModifyColumnTypes {
157                columns: modify_column_type_requests,
158            }
159        }
160        Kind::DropColumns(DropColumns { drop_columns }) => AlterKind::DropColumns {
161            names: drop_columns.into_iter().map(|c| c.name).collect(),
162        },
163        Kind::RenameTable(RenameTable { new_table_name }) => {
164            AlterKind::RenameTable { new_table_name }
165        }
166        Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
167            let repartition_column_hint = table_options
168                .iter()
169                .find(|option| option.key == REPARTITION_COLUMN_HINT_KEY);
170            if let Some(option) = repartition_column_hint {
171                ensure!(
172                    table_options.len() == 1,
173                    error::InvalidTableOptionRequestSnafu {
174                        err_msg: format!(
175                            "{REPARTITION_COLUMN_HINT_KEY} must be altered separately"
176                        ),
177                    }
178                );
179                AlterKind::SetRepartitionColumnHint {
180                    column_name: option.value.clone(),
181                }
182            } else {
183                AlterKind::SetTableOptions {
184                    options: table_options
185                        .iter()
186                        .map(SetRegionOption::try_from)
187                        .collect::<std::result::Result<Vec<_>, _>>()
188                        .context(InvalidSetTableOptionRequestSnafu)?,
189                }
190            }
191        }
192        Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
193            let unset_repartition_column_hint = keys
194                .iter()
195                .any(|key| key.as_str() == REPARTITION_COLUMN_HINT_KEY);
196            if unset_repartition_column_hint {
197                ensure!(
198                    keys.len() == 1,
199                    error::InvalidTableOptionRequestSnafu {
200                        err_msg: format!(
201                            "{REPARTITION_COLUMN_HINT_KEY} must be altered separately"
202                        ),
203                    }
204                );
205                AlterKind::UnsetRepartitionColumnHint
206            } else {
207                AlterKind::UnsetTableOptions {
208                    keys: keys
209                        .iter()
210                        .map(|key| UnsetRegionOption::try_from(key.as_str()))
211                        .collect::<std::result::Result<Vec<_>, _>>()
212                        .context(InvalidUnsetTableOptionRequestSnafu)?,
213                }
214            }
215        }
216        Kind::SetIndex(o) => {
217            let option = set_index_option_from_proto(o)?;
218            AlterKind::SetIndexes {
219                options: vec![option],
220            }
221        }
222        Kind::UnsetIndex(o) => {
223            let option = unset_index_option_from_proto(o)?;
224            AlterKind::UnsetIndexes {
225                options: vec![option],
226            }
227        }
228        Kind::SetIndexes(o) => {
229            let options = o
230                .set_indexes
231                .into_iter()
232                .map(set_index_option_from_proto)
233                .collect::<Result<Vec<_>>>()?;
234            AlterKind::SetIndexes { options }
235        }
236        Kind::UnsetIndexes(o) => {
237            let options = o
238                .unset_indexes
239                .into_iter()
240                .map(unset_index_option_from_proto)
241                .collect::<Result<Vec<_>>>()?;
242            AlterKind::UnsetIndexes { options }
243        }
244        Kind::DropDefaults(o) => {
245            let names = o
246                .drop_defaults
247                .into_iter()
248                .map(|col| {
249                    ensure!(
250                        !col.column_name.is_empty(),
251                        MissingFieldSnafu {
252                            field: "column_name"
253                        }
254                    );
255                    Ok(col.column_name)
256                })
257                .collect::<Result<Vec<_>>>()?;
258            AlterKind::DropDefaults { names }
259        }
260        Kind::SetDefaults(o) => {
261            let table_meta = table_meta.context(MissingTableMetaSnafu { table_id })?;
262            let defaults = o
263                .set_defaults
264                .into_iter()
265                .map(|col| {
266                    let column_scheme = table_meta
267                        .schema
268                        .column_schema_by_name(&col.column_name)
269                        .context(ColumnNotFoundSnafu {
270                        column_name: &col.column_name,
271                    })?;
272                    let default_constraint = common_sql::convert::deserialize_default_constraint(
273                        col.default_constraint.as_slice(),
274                        &col.column_name,
275                        &column_scheme.data_type,
276                    )
277                    .context(crate::error::SqlCommonSnafu)?;
278                    Ok(SetDefaultRequest {
279                        column_name: col.column_name,
280                        default_constraint,
281                    })
282                })
283                .collect::<Result<Vec<_>>>()?;
284            AlterKind::SetDefaults { defaults }
285        }
286        Kind::Repartition(_) => error::UnexpectedSnafu {
287            err_msg: "Repartition operation should be handled through DdlManager and not converted to AlterTableRequest",
288        }
289        .fail()?,
290    };
291
292    let request = AlterTableRequest {
293        catalog_name,
294        schema_name,
295        table_name: expr.table_name,
296        table_id,
297        alter_kind,
298        table_version: None,
299    };
300    Ok(request)
301}
302
303pub fn create_table_schema(expr: &CreateTableExpr, require_time_index: bool) -> Result<Schema> {
304    let column_schemas = expr
305        .column_defs
306        .iter()
307        .map(|x| {
308            column_def::try_as_column_schema(x).context(InvalidColumnDefSnafu { column: &x.name })
309        })
310        .collect::<Result<Vec<ColumnSchema>>>()?;
311
312    // allow external table schema without the time index
313    if require_time_index {
314        ensure!(
315            column_schemas
316                .iter()
317                .any(|column| column.name == expr.time_index),
318            MissingTimestampColumnSnafu {
319                msg: format!("CreateExpr: {expr:?}")
320            }
321        );
322    }
323
324    let column_schemas = column_schemas
325        .into_iter()
326        .map(|column_schema| {
327            if column_schema.name == expr.time_index {
328                column_schema.with_time_index(true)
329            } else {
330                column_schema
331            }
332        })
333        .collect::<Vec<_>>();
334
335    Ok(Schema::new(column_schemas))
336}
337
338fn parse_location(location: Option<Location>) -> Result<Option<AddColumnLocation>> {
339    match location {
340        Some(Location {
341            location_type: LOCATION_TYPE_FIRST,
342            ..
343        }) => Ok(Some(AddColumnLocation::First)),
344        Some(Location {
345            location_type: LOCATION_TYPE_AFTER,
346            after_column_name,
347        }) => Ok(Some(AddColumnLocation::After {
348            column_name: after_column_name,
349        })),
350        Some(Location { location_type, .. }) => UnknownLocationTypeSnafu { location_type }.fail(),
351        None => Ok(None),
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use api::v1::{
358        AddColumn, AddColumns, ColumnDataType, ColumnDef, DropColumn, ModifyColumnType,
359        Option as PbOption, SemanticType, SetTableOptions, UnsetTableOptions,
360    };
361    use datatypes::prelude::ConcreteDataType;
362
363    use super::*;
364
365    #[test]
366    fn test_alter_expr_to_request() {
367        let expr = AlterTableExpr {
368            catalog_name: String::default(),
369            schema_name: String::default(),
370            table_name: "monitor".to_string(),
371
372            kind: Some(Kind::AddColumns(AddColumns {
373                add_columns: vec![AddColumn {
374                    column_def: Some(ColumnDef {
375                        name: "mem_usage".to_string(),
376                        data_type: ColumnDataType::Float64 as i32,
377                        is_nullable: false,
378                        default_constraint: vec![],
379                        semantic_type: SemanticType::Field as i32,
380                        comment: String::new(),
381                        ..Default::default()
382                    }),
383                    location: None,
384                    add_if_not_exists: true,
385                }],
386            })),
387        };
388
389        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
390        assert_eq!(alter_request.catalog_name, "");
391        assert_eq!(alter_request.schema_name, "");
392        assert_eq!("monitor".to_string(), alter_request.table_name);
393        let add_column = match alter_request.alter_kind {
394            AlterKind::AddColumns { mut columns } => columns.pop().unwrap(),
395            _ => unreachable!(),
396        };
397
398        assert!(!add_column.is_key);
399        assert_eq!("mem_usage", add_column.column_schema.name);
400        assert_eq!(
401            ConcreteDataType::float64_datatype(),
402            add_column.column_schema.data_type
403        );
404        assert_eq!(None, add_column.location);
405        assert!(add_column.add_if_not_exists);
406    }
407
408    #[test]
409    fn test_alter_expr_with_location_to_request() {
410        let expr = AlterTableExpr {
411            catalog_name: String::default(),
412            schema_name: String::default(),
413            table_name: "monitor".to_string(),
414
415            kind: Some(Kind::AddColumns(AddColumns {
416                add_columns: vec![
417                    AddColumn {
418                        column_def: Some(ColumnDef {
419                            name: "mem_usage".to_string(),
420                            data_type: ColumnDataType::Float64 as i32,
421                            is_nullable: false,
422                            default_constraint: vec![],
423                            semantic_type: SemanticType::Field as i32,
424                            comment: String::new(),
425                            ..Default::default()
426                        }),
427                        location: Some(Location {
428                            location_type: LocationType::First.into(),
429                            after_column_name: String::default(),
430                        }),
431                        add_if_not_exists: false,
432                    },
433                    AddColumn {
434                        column_def: Some(ColumnDef {
435                            name: "cpu_usage".to_string(),
436                            data_type: ColumnDataType::Float64 as i32,
437                            is_nullable: false,
438                            default_constraint: vec![],
439                            semantic_type: SemanticType::Field as i32,
440                            comment: String::new(),
441                            ..Default::default()
442                        }),
443                        location: Some(Location {
444                            location_type: LocationType::After.into(),
445                            after_column_name: "ts".to_string(),
446                        }),
447                        add_if_not_exists: true,
448                    },
449                ],
450            })),
451        };
452
453        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
454        assert_eq!(alter_request.catalog_name, "");
455        assert_eq!(alter_request.schema_name, "");
456        assert_eq!("monitor".to_string(), alter_request.table_name);
457
458        let mut add_columns = match alter_request.alter_kind {
459            AlterKind::AddColumns { columns } => columns,
460            _ => unreachable!(),
461        };
462
463        let add_column = add_columns.pop().unwrap();
464        assert!(!add_column.is_key);
465        assert_eq!("cpu_usage", add_column.column_schema.name);
466        assert_eq!(
467            ConcreteDataType::float64_datatype(),
468            add_column.column_schema.data_type
469        );
470        assert_eq!(
471            Some(AddColumnLocation::After {
472                column_name: "ts".to_string()
473            }),
474            add_column.location
475        );
476        assert!(add_column.add_if_not_exists);
477
478        let add_column = add_columns.pop().unwrap();
479        assert!(!add_column.is_key);
480        assert_eq!("mem_usage", add_column.column_schema.name);
481        assert_eq!(
482            ConcreteDataType::float64_datatype(),
483            add_column.column_schema.data_type
484        );
485        assert_eq!(Some(AddColumnLocation::First), add_column.location);
486        assert!(!add_column.add_if_not_exists);
487    }
488
489    #[test]
490    fn test_modify_column_type_expr() {
491        let expr = AlterTableExpr {
492            catalog_name: "test_catalog".to_string(),
493            schema_name: "test_schema".to_string(),
494            table_name: "monitor".to_string(),
495
496            kind: Some(Kind::ModifyColumnTypes(ModifyColumnTypes {
497                modify_column_types: vec![ModifyColumnType {
498                    column_name: "mem_usage".to_string(),
499                    target_type: ColumnDataType::String as i32,
500                    target_type_extension: None,
501                }],
502            })),
503        };
504
505        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
506        assert_eq!(alter_request.catalog_name, "test_catalog");
507        assert_eq!(alter_request.schema_name, "test_schema");
508        assert_eq!("monitor".to_string(), alter_request.table_name);
509
510        let mut modify_column_types = match alter_request.alter_kind {
511            AlterKind::ModifyColumnTypes { columns } => columns,
512            _ => unreachable!(),
513        };
514
515        let modify_column_type = modify_column_types.pop().unwrap();
516        assert_eq!("mem_usage", modify_column_type.column_name);
517        assert_eq!(
518            ConcreteDataType::string_datatype(),
519            modify_column_type.target_type
520        );
521    }
522
523    #[test]
524    fn test_drop_column_expr() {
525        let expr = AlterTableExpr {
526            catalog_name: "test_catalog".to_string(),
527            schema_name: "test_schema".to_string(),
528            table_name: "monitor".to_string(),
529
530            kind: Some(Kind::DropColumns(DropColumns {
531                drop_columns: vec![DropColumn {
532                    name: "mem_usage".to_string(),
533                }],
534            })),
535        };
536
537        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
538        assert_eq!(alter_request.catalog_name, "test_catalog");
539        assert_eq!(alter_request.schema_name, "test_schema");
540        assert_eq!("monitor".to_string(), alter_request.table_name);
541
542        let mut drop_names = match alter_request.alter_kind {
543            AlterKind::DropColumns { names } => names,
544            _ => unreachable!(),
545        };
546        assert_eq!(1, drop_names.len());
547        assert_eq!("mem_usage".to_string(), drop_names.pop().unwrap());
548    }
549
550    #[test]
551    fn test_set_repartition_column_hint_expr() {
552        let expr = AlterTableExpr {
553            catalog_name: "test_catalog".to_string(),
554            schema_name: "test_schema".to_string(),
555            table_name: "monitor".to_string(),
556            kind: Some(Kind::SetTableOptions(SetTableOptions {
557                table_options: vec![PbOption {
558                    key: REPARTITION_COLUMN_HINT_KEY.to_string(),
559                    value: "host".to_string(),
560                }],
561            })),
562        };
563
564        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
565        match alter_request.alter_kind {
566            AlterKind::SetRepartitionColumnHint { column_name } => {
567                assert_eq!("host", column_name);
568            }
569            _ => unreachable!(),
570        }
571    }
572
573    #[test]
574    fn test_set_repartition_column_hint_rejects_mixed_options() {
575        let expr = AlterTableExpr {
576            catalog_name: "test_catalog".to_string(),
577            schema_name: "test_schema".to_string(),
578            table_name: "monitor".to_string(),
579            kind: Some(Kind::SetTableOptions(SetTableOptions {
580                table_options: vec![
581                    PbOption {
582                        key: REPARTITION_COLUMN_HINT_KEY.to_string(),
583                        value: "host".to_string(),
584                    },
585                    PbOption {
586                        key: table::requests::TTL_KEY.to_string(),
587                        value: "7d".to_string(),
588                    },
589                ],
590            })),
591        };
592
593        let err = alter_expr_to_request(1, expr, None).unwrap_err();
594        assert!(
595            err.to_string()
596                .contains("repartition.column.hint must be altered separately")
597        );
598    }
599
600    #[test]
601    fn test_unset_repartition_column_hint_expr() {
602        let expr = AlterTableExpr {
603            catalog_name: "test_catalog".to_string(),
604            schema_name: "test_schema".to_string(),
605            table_name: "monitor".to_string(),
606            kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
607                keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
608            })),
609        };
610
611        let alter_request = alter_expr_to_request(1, expr, None).unwrap();
612        assert!(matches!(
613            alter_request.alter_kind,
614            AlterKind::UnsetRepartitionColumnHint
615        ));
616    }
617}