1use 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
100pub 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 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}