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::json::{JsonSettings, JsonTypeHint};
28use datatypes::prelude::ConcreteDataType;
29use datatypes::schema::{ColumnSchema, FulltextOptions, Schema, SkippingIndexOptions};
30use snafu::{OptionExt, ResultExt, ensure};
31use store_api::region_request::{SetRegionOption, UnsetRegionOption};
32use table::metadata::{TableId, TableMeta};
33use table::requests::{
34 AddColumnRequest, AlterKind, AlterTableRequest, AnnotationFamily, ModifyColumnTypeRequest,
35 SetDefaultRequest, SetIndexOption, SetJsonSettingsRequest, UnsetIndexOption,
36};
37
38use crate::error::{
39 self, ColumnNotFoundSnafu, InvalidColumnDefSnafu, InvalidIndexOptionSnafu,
40 InvalidJsonSettingsSnafu, InvalidSetFulltextOptionRequestSnafu,
41 InvalidSetSkippingIndexOptionRequestSnafu, InvalidSetTableOptionRequestSnafu,
42 InvalidUnsetTableOptionRequestSnafu, MissingAlterIndexOptionSnafu, MissingFieldSnafu,
43 MissingTableMetaSnafu, MissingTimestampColumnSnafu, Result, UnknownLocationTypeSnafu,
44};
45
46const LOCATION_TYPE_FIRST: i32 = LocationType::First as i32;
47const LOCATION_TYPE_AFTER: i32 = LocationType::After as i32;
48
49fn annotation_family_of_keys<'a>(
54 keys: impl Iterator<Item = &'a str>,
55) -> Result<Option<AnnotationFamily>> {
56 table::requests::validate_annotation_keys(keys).map_err(|err| {
57 error::InvalidTableOptionRequestSnafu {
58 err_msg: err.to_string(),
59 }
60 .build()
61 })
62}
63
64fn json_settings_from_proto(settings: api::v1::JsonSettings) -> Result<JsonSettings> {
65 let type_hints = settings
66 .type_hints
67 .into_iter()
68 .map(|hint| {
69 let data_type = ConcreteDataType::from(
70 ColumnDataTypeWrapper::try_new(hint.data_type, hint.datatype_extension)
71 .context(error::ColumnDataTypeSnafu)?,
72 );
73
74 Ok(JsonTypeHint {
75 path: hint.path,
76 data_type,
77 inverted_index: false,
80 })
81 })
82 .collect::<Result<Vec<_>>>()?;
83
84 JsonSettings::try_new(type_hints, settings.max_auto_expanded_paths).map_err(|err| {
85 InvalidJsonSettingsSnafu {
86 err: err.to_string(),
87 }
88 .build()
89 })
90}
91
92pub fn annotation_alter_family(kind: &Kind) -> Result<Option<AnnotationFamily>> {
98 match kind {
99 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
100 annotation_family_of_keys(table_options.iter().map(|option| option.key.as_str()))
101 }
102 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
103 annotation_family_of_keys(keys.iter().map(|key| key.as_str()))
104 }
105 _ => Ok(None),
106 }
107}
108
109fn set_index_option_from_proto(set_index: api::v1::SetIndex) -> Result<SetIndexOption> {
110 let options = set_index.options.context(MissingAlterIndexOptionSnafu)?;
111 Ok(match options {
112 api::v1::set_index::Options::Fulltext(f) => SetIndexOption::Fulltext {
113 column_name: f.column_name.clone(),
114 options: FulltextOptions::new(
115 f.enable,
116 as_fulltext_option_analyzer(
117 Analyzer::try_from(f.analyzer).context(InvalidSetFulltextOptionRequestSnafu)?,
118 ),
119 f.case_sensitive,
120 as_fulltext_option_backend(
121 PbFulltextBackend::try_from(f.backend)
122 .context(InvalidSetFulltextOptionRequestSnafu)?,
123 ),
124 f.granularity as u32,
125 f.false_positive_rate,
126 )
127 .context(InvalidIndexOptionSnafu)?,
128 },
129 api::v1::set_index::Options::Inverted(i) => SetIndexOption::Inverted {
130 column_name: i.column_name,
131 },
132 api::v1::set_index::Options::Skipping(s) => SetIndexOption::Skipping {
133 column_name: s.column_name,
134 options: SkippingIndexOptions::new(
135 s.granularity as u32,
136 s.false_positive_rate,
137 as_skipping_index_type(
138 PbSkippingIndexType::try_from(s.skipping_index_type)
139 .context(InvalidSetSkippingIndexOptionRequestSnafu)?,
140 ),
141 )
142 .context(InvalidIndexOptionSnafu)?,
143 },
144 })
145}
146
147fn unset_index_option_from_proto(unset_index: api::v1::UnsetIndex) -> Result<UnsetIndexOption> {
148 let options = unset_index.options.context(MissingAlterIndexOptionSnafu)?;
149 Ok(match options {
150 api::v1::unset_index::Options::Fulltext(f) => UnsetIndexOption::Fulltext {
151 column_name: f.column_name,
152 },
153 api::v1::unset_index::Options::Inverted(i) => UnsetIndexOption::Inverted {
154 column_name: i.column_name,
155 },
156 api::v1::unset_index::Options::Skipping(s) => UnsetIndexOption::Skipping {
157 column_name: s.column_name,
158 },
159 })
160}
161
162pub fn alter_expr_to_request(
166 table_id: TableId,
167 expr: AlterTableExpr,
168 table_meta: Option<&TableMeta>,
169) -> Result<AlterTableRequest> {
170 let catalog_name = expr.catalog_name;
171 let schema_name = expr.schema_name;
172 let kind = expr.kind.context(MissingFieldSnafu { field: "kind" })?;
173 let alter_kind = match kind {
174 Kind::AddColumns(add_columns) => {
175 let add_column_requests = add_columns
176 .add_columns
177 .into_iter()
178 .map(|ac| {
179 let column_def = ac.column_def.context(MissingFieldSnafu {
180 field: "column_def",
181 })?;
182
183 let schema = column_def::try_as_column_schema(&column_def).context(
184 InvalidColumnDefSnafu {
185 column: &column_def.name,
186 },
187 )?;
188 Ok(AddColumnRequest {
189 column_schema: schema,
190 is_key: column_def.semantic_type == SemanticType::Tag as i32,
191 location: parse_location(ac.location)?,
192 add_if_not_exists: ac.add_if_not_exists,
193 })
194 })
195 .collect::<Result<Vec<_>>>()?;
196
197 AlterKind::AddColumns {
198 columns: add_column_requests,
199 }
200 }
201 Kind::ModifyColumnTypes(ModifyColumnTypes {
202 modify_column_types,
203 }) => {
204 let modify_column_type_requests = modify_column_types
205 .into_iter()
206 .map(|cct| {
207 let target_type =
208 ColumnDataTypeWrapper::new(cct.target_type(), cct.target_type_extension)
209 .into();
210
211 Ok(ModifyColumnTypeRequest {
212 column_name: cct.column_name,
213 target_type,
214 })
215 })
216 .collect::<Result<Vec<_>>>()?;
217
218 AlterKind::ModifyColumnTypes {
219 columns: modify_column_type_requests,
220 }
221 }
222 Kind::SetJsonSettings(set_json_settings) => {
223 let settings = set_json_settings
224 .settings
225 .context(MissingFieldSnafu { field: "settings" })?;
226 AlterKind::SetJsonSettings {
227 request: SetJsonSettingsRequest {
228 column_name: set_json_settings.column_name,
229 settings: json_settings_from_proto(settings)?,
230 },
231 }
232 }
233 Kind::DropColumns(DropColumns { drop_columns }) => AlterKind::DropColumns {
234 names: drop_columns.into_iter().map(|c| c.name).collect(),
235 },
236 Kind::RenameTable(RenameTable { new_table_name }) => {
237 AlterKind::RenameTable { new_table_name }
238 }
239 Kind::SetTableOptions(api::v1::SetTableOptions { table_options }) => {
240 if let Some(family) = annotation_family_of_keys(
241 table_options.iter().map(|option| option.key.as_str()),
242 )? {
243 AlterKind::SetAnnotations {
244 family,
245 options: table_options
246 .into_iter()
247 .map(|option| (option.key, option.value))
248 .collect(),
249 }
250 } else {
251 AlterKind::SetTableOptions {
252 options: table_options
253 .iter()
254 .map(SetRegionOption::try_from)
255 .collect::<std::result::Result<Vec<_>, _>>()
256 .context(InvalidSetTableOptionRequestSnafu)?,
257 }
258 }
259 }
260 Kind::UnsetTableOptions(api::v1::UnsetTableOptions { keys }) => {
261 if let Some(family) = annotation_family_of_keys(keys.iter().map(|key| key.as_str()))? {
262 AlterKind::UnsetAnnotations { family, keys }
263 } else {
264 AlterKind::UnsetTableOptions {
265 keys: keys
266 .iter()
267 .map(|key| UnsetRegionOption::try_from(key.as_str()))
268 .collect::<std::result::Result<Vec<_>, _>>()
269 .context(InvalidUnsetTableOptionRequestSnafu)?,
270 }
271 }
272 }
273 Kind::SetIndex(o) => {
274 let option = set_index_option_from_proto(o)?;
275 AlterKind::SetIndexes {
276 options: vec![option],
277 }
278 }
279 Kind::UnsetIndex(o) => {
280 let option = unset_index_option_from_proto(o)?;
281 AlterKind::UnsetIndexes {
282 options: vec![option],
283 }
284 }
285 Kind::SetIndexes(o) => {
286 let options = o
287 .set_indexes
288 .into_iter()
289 .map(set_index_option_from_proto)
290 .collect::<Result<Vec<_>>>()?;
291 AlterKind::SetIndexes { options }
292 }
293 Kind::UnsetIndexes(o) => {
294 let options = o
295 .unset_indexes
296 .into_iter()
297 .map(unset_index_option_from_proto)
298 .collect::<Result<Vec<_>>>()?;
299 AlterKind::UnsetIndexes { options }
300 }
301 Kind::DropDefaults(o) => {
302 let names = o
303 .drop_defaults
304 .into_iter()
305 .map(|col| {
306 ensure!(
307 !col.column_name.is_empty(),
308 MissingFieldSnafu {
309 field: "column_name"
310 }
311 );
312 Ok(col.column_name)
313 })
314 .collect::<Result<Vec<_>>>()?;
315 AlterKind::DropDefaults { names }
316 }
317 Kind::SetDefaults(o) => {
318 let table_meta = table_meta.context(MissingTableMetaSnafu { table_id })?;
319 let defaults = o
320 .set_defaults
321 .into_iter()
322 .map(|col| {
323 let column_scheme = table_meta
324 .schema
325 .column_schema_by_name(&col.column_name)
326 .context(ColumnNotFoundSnafu {
327 column_name: &col.column_name,
328 })?;
329 let default_constraint = common_sql::convert::deserialize_default_constraint(
330 col.default_constraint.as_slice(),
331 &col.column_name,
332 &column_scheme.data_type,
333 )
334 .context(crate::error::SqlCommonSnafu)?;
335 Ok(SetDefaultRequest {
336 column_name: col.column_name,
337 default_constraint,
338 })
339 })
340 .collect::<Result<Vec<_>>>()?;
341 AlterKind::SetDefaults { defaults }
342 }
343 Kind::Repartition(_) => error::UnexpectedSnafu {
344 err_msg: "Repartition operation should be handled through DdlManager and not converted to AlterTableRequest",
345 }
346 .fail()?,
347 };
348
349 let request = AlterTableRequest {
350 catalog_name,
351 schema_name,
352 table_name: expr.table_name,
353 table_id,
354 alter_kind,
355 table_version: None,
356 };
357 Ok(request)
358}
359
360pub fn create_table_schema(expr: &CreateTableExpr, require_time_index: bool) -> Result<Schema> {
361 let column_schemas = expr
362 .column_defs
363 .iter()
364 .map(|x| {
365 column_def::try_as_column_schema(x).context(InvalidColumnDefSnafu { column: &x.name })
366 })
367 .collect::<Result<Vec<ColumnSchema>>>()?;
368
369 if require_time_index {
371 ensure!(
372 column_schemas
373 .iter()
374 .any(|column| column.name == expr.time_index),
375 MissingTimestampColumnSnafu {
376 msg: format!("CreateExpr: {expr:?}")
377 }
378 );
379 }
380
381 let column_schemas = column_schemas
382 .into_iter()
383 .map(|column_schema| {
384 if column_schema.name == expr.time_index {
385 column_schema.with_time_index(true)
386 } else {
387 column_schema
388 }
389 })
390 .collect::<Vec<_>>();
391
392 Ok(Schema::new(column_schemas))
393}
394
395fn parse_location(location: Option<Location>) -> Result<Option<AddColumnLocation>> {
396 match location {
397 Some(Location {
398 location_type: LOCATION_TYPE_FIRST,
399 ..
400 }) => Ok(Some(AddColumnLocation::First)),
401 Some(Location {
402 location_type: LOCATION_TYPE_AFTER,
403 after_column_name,
404 }) => Ok(Some(AddColumnLocation::After {
405 column_name: after_column_name,
406 })),
407 Some(Location { location_type, .. }) => UnknownLocationTypeSnafu { location_type }.fail(),
408 None => Ok(None),
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use api::v1::{
415 AddColumn, AddColumns, ColumnDataType, ColumnDef, DropColumn, ModifyColumnType,
416 Option as PbOption, SemanticType, SetTableOptions, UnsetTableOptions,
417 };
418 use datatypes::prelude::ConcreteDataType;
419 use table::requests::REPARTITION_COLUMN_HINT_KEY;
420
421 use super::*;
422
423 #[test]
424 fn test_alter_expr_to_request() {
425 let expr = AlterTableExpr {
426 catalog_name: String::default(),
427 schema_name: String::default(),
428 table_name: "monitor".to_string(),
429
430 kind: Some(Kind::AddColumns(AddColumns {
431 add_columns: vec![AddColumn {
432 column_def: Some(ColumnDef {
433 name: "mem_usage".to_string(),
434 data_type: ColumnDataType::Float64 as i32,
435 is_nullable: false,
436 default_constraint: vec![],
437 semantic_type: SemanticType::Field as i32,
438 comment: String::new(),
439 ..Default::default()
440 }),
441 location: None,
442 add_if_not_exists: true,
443 }],
444 })),
445 };
446
447 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
448 assert_eq!(alter_request.catalog_name, "");
449 assert_eq!(alter_request.schema_name, "");
450 assert_eq!("monitor".to_string(), alter_request.table_name);
451 let add_column = match alter_request.alter_kind {
452 AlterKind::AddColumns { mut columns } => columns.pop().unwrap(),
453 _ => unreachable!(),
454 };
455
456 assert!(!add_column.is_key);
457 assert_eq!("mem_usage", add_column.column_schema.name);
458 assert_eq!(
459 ConcreteDataType::float64_datatype(),
460 add_column.column_schema.data_type
461 );
462 assert_eq!(None, add_column.location);
463 assert!(add_column.add_if_not_exists);
464 }
465
466 #[test]
467 fn test_alter_expr_with_location_to_request() {
468 let expr = AlterTableExpr {
469 catalog_name: String::default(),
470 schema_name: String::default(),
471 table_name: "monitor".to_string(),
472
473 kind: Some(Kind::AddColumns(AddColumns {
474 add_columns: vec![
475 AddColumn {
476 column_def: Some(ColumnDef {
477 name: "mem_usage".to_string(),
478 data_type: ColumnDataType::Float64 as i32,
479 is_nullable: false,
480 default_constraint: vec![],
481 semantic_type: SemanticType::Field as i32,
482 comment: String::new(),
483 ..Default::default()
484 }),
485 location: Some(Location {
486 location_type: LocationType::First.into(),
487 after_column_name: String::default(),
488 }),
489 add_if_not_exists: false,
490 },
491 AddColumn {
492 column_def: Some(ColumnDef {
493 name: "cpu_usage".to_string(),
494 data_type: ColumnDataType::Float64 as i32,
495 is_nullable: false,
496 default_constraint: vec![],
497 semantic_type: SemanticType::Field as i32,
498 comment: String::new(),
499 ..Default::default()
500 }),
501 location: Some(Location {
502 location_type: LocationType::After.into(),
503 after_column_name: "ts".to_string(),
504 }),
505 add_if_not_exists: true,
506 },
507 ],
508 })),
509 };
510
511 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
512 assert_eq!(alter_request.catalog_name, "");
513 assert_eq!(alter_request.schema_name, "");
514 assert_eq!("monitor".to_string(), alter_request.table_name);
515
516 let mut add_columns = match alter_request.alter_kind {
517 AlterKind::AddColumns { columns } => columns,
518 _ => unreachable!(),
519 };
520
521 let add_column = add_columns.pop().unwrap();
522 assert!(!add_column.is_key);
523 assert_eq!("cpu_usage", add_column.column_schema.name);
524 assert_eq!(
525 ConcreteDataType::float64_datatype(),
526 add_column.column_schema.data_type
527 );
528 assert_eq!(
529 Some(AddColumnLocation::After {
530 column_name: "ts".to_string()
531 }),
532 add_column.location
533 );
534 assert!(add_column.add_if_not_exists);
535
536 let add_column = add_columns.pop().unwrap();
537 assert!(!add_column.is_key);
538 assert_eq!("mem_usage", add_column.column_schema.name);
539 assert_eq!(
540 ConcreteDataType::float64_datatype(),
541 add_column.column_schema.data_type
542 );
543 assert_eq!(Some(AddColumnLocation::First), add_column.location);
544 assert!(!add_column.add_if_not_exists);
545 }
546
547 #[test]
548 fn test_modify_column_type_expr() {
549 let expr = AlterTableExpr {
550 catalog_name: "test_catalog".to_string(),
551 schema_name: "test_schema".to_string(),
552 table_name: "monitor".to_string(),
553
554 kind: Some(Kind::ModifyColumnTypes(ModifyColumnTypes {
555 modify_column_types: vec![ModifyColumnType {
556 column_name: "mem_usage".to_string(),
557 target_type: ColumnDataType::String as i32,
558 target_type_extension: None,
559 }],
560 })),
561 };
562
563 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
564 assert_eq!(alter_request.catalog_name, "test_catalog");
565 assert_eq!(alter_request.schema_name, "test_schema");
566 assert_eq!("monitor".to_string(), alter_request.table_name);
567
568 let mut modify_column_types = match alter_request.alter_kind {
569 AlterKind::ModifyColumnTypes { columns } => columns,
570 _ => unreachable!(),
571 };
572
573 let modify_column_type = modify_column_types.pop().unwrap();
574 assert_eq!("mem_usage", modify_column_type.column_name);
575 assert_eq!(
576 ConcreteDataType::string_datatype(),
577 modify_column_type.target_type
578 );
579 }
580
581 #[test]
582 fn test_drop_column_expr() {
583 let expr = AlterTableExpr {
584 catalog_name: "test_catalog".to_string(),
585 schema_name: "test_schema".to_string(),
586 table_name: "monitor".to_string(),
587
588 kind: Some(Kind::DropColumns(DropColumns {
589 drop_columns: vec![DropColumn {
590 name: "mem_usage".to_string(),
591 }],
592 })),
593 };
594
595 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
596 assert_eq!(alter_request.catalog_name, "test_catalog");
597 assert_eq!(alter_request.schema_name, "test_schema");
598 assert_eq!("monitor".to_string(), alter_request.table_name);
599
600 let mut drop_names = match alter_request.alter_kind {
601 AlterKind::DropColumns { names } => names,
602 _ => unreachable!(),
603 };
604 assert_eq!(1, drop_names.len());
605 assert_eq!("mem_usage".to_string(), drop_names.pop().unwrap());
606 }
607
608 #[test]
609 fn test_repartition_hints_together() {
610 let keys = [
611 REPARTITION_COLUMN_HINT_KEY,
612 table::requests::REPARTITION_PARTITION_NUM_HINT_KEY,
613 ];
614 let options = vec![
615 (keys[0].to_string(), "host".to_string()),
616 (keys[1].to_string(), "10".to_string()),
617 ];
618 let set = Kind::SetTableOptions(SetTableOptions {
619 table_options: options
620 .iter()
621 .map(|(key, value)| PbOption {
622 key: key.clone(),
623 value: value.clone(),
624 })
625 .collect(),
626 });
627 let unset = Kind::UnsetTableOptions(UnsetTableOptions {
628 keys: keys.map(str::to_string).to_vec(),
629 });
630 for kind in [set, unset] {
631 assert_eq!(
632 annotation_alter_family(&kind).unwrap(),
633 Some(AnnotationFamily::RepartitionHint)
634 );
635 let request = alter_expr_to_request(
636 1,
637 AlterTableExpr {
638 kind: Some(kind),
639 ..Default::default()
640 },
641 None,
642 )
643 .unwrap();
644 match request.alter_kind {
645 AlterKind::SetAnnotations {
646 family,
647 options: actual,
648 } => {
649 assert_eq!(family, AnnotationFamily::RepartitionHint);
650 assert_eq!(actual, options);
651 }
652 AlterKind::UnsetAnnotations {
653 family,
654 keys: actual,
655 } => {
656 assert_eq!(family, AnnotationFamily::RepartitionHint);
657 assert_eq!(actual, keys);
658 }
659 other => panic!("unexpected alter kind: {other:?}"),
660 }
661 }
662 }
663
664 #[test]
665 fn test_repartition_partition_num_hint_expr() {
666 let key = table::requests::REPARTITION_PARTITION_NUM_HINT_KEY;
667 let set = Kind::SetTableOptions(SetTableOptions {
668 table_options: vec![PbOption {
669 key: key.to_string(),
670 value: "8".to_string(),
671 }],
672 });
673 let unset = Kind::UnsetTableOptions(UnsetTableOptions {
674 keys: vec![key.to_string()],
675 });
676 for kind in [set, unset] {
677 assert_eq!(
678 annotation_alter_family(&kind).unwrap(),
679 Some(AnnotationFamily::RepartitionHint)
680 );
681 let request = alter_expr_to_request(
682 1,
683 AlterTableExpr {
684 kind: Some(kind),
685 ..Default::default()
686 },
687 None,
688 )
689 .unwrap();
690 match request.alter_kind {
691 AlterKind::SetAnnotations { family, options } => {
692 assert_eq!(family, AnnotationFamily::RepartitionHint);
693 assert_eq!(options, vec![(key.to_string(), "8".to_string())]);
694 }
695 AlterKind::UnsetAnnotations { family, keys } => {
696 assert_eq!(family, AnnotationFamily::RepartitionHint);
697 assert_eq!(keys, vec![key.to_string()]);
698 }
699 other => panic!("unexpected alter kind: {other:?}"),
700 }
701 }
702 for other in [key, table::requests::TTL_KEY] {
703 for kind in [
704 Kind::SetTableOptions(SetTableOptions {
705 table_options: [key, other]
706 .into_iter()
707 .map(|key| PbOption {
708 key: key.to_string(),
709 value: "8".to_string(),
710 })
711 .collect(),
712 }),
713 Kind::UnsetTableOptions(UnsetTableOptions {
714 keys: vec![key.to_string(), other.to_string()],
715 }),
716 ] {
717 assert!(annotation_alter_family(&kind).is_err());
718 assert!(
719 alter_expr_to_request(
720 1,
721 AlterTableExpr {
722 kind: Some(kind),
723 ..Default::default()
724 },
725 None
726 )
727 .is_err()
728 );
729 }
730 }
731 }
732
733 #[test]
734 fn test_set_repartition_column_hint_expr() {
735 let expr = AlterTableExpr {
736 catalog_name: "test_catalog".to_string(),
737 schema_name: "test_schema".to_string(),
738 table_name: "monitor".to_string(),
739 kind: Some(Kind::SetTableOptions(SetTableOptions {
740 table_options: vec![PbOption {
741 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
742 value: "host".to_string(),
743 }],
744 })),
745 };
746
747 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
748 match alter_request.alter_kind {
749 AlterKind::SetAnnotations { family, options } => {
750 assert_eq!(AnnotationFamily::RepartitionHint, family);
751 assert_eq!(
752 vec![(REPARTITION_COLUMN_HINT_KEY.to_string(), "host".to_string())],
753 options
754 );
755 }
756 _ => unreachable!(),
757 }
758 }
759
760 #[test]
761 fn test_set_repartition_column_hint_rejects_mixed_options() {
762 let expr = AlterTableExpr {
763 catalog_name: "test_catalog".to_string(),
764 schema_name: "test_schema".to_string(),
765 table_name: "monitor".to_string(),
766 kind: Some(Kind::SetTableOptions(SetTableOptions {
767 table_options: vec![
768 PbOption {
769 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
770 value: "host".to_string(),
771 },
772 PbOption {
773 key: table::requests::TTL_KEY.to_string(),
774 value: "7d".to_string(),
775 },
776 ],
777 })),
778 };
779
780 let err = alter_expr_to_request(1, expr, None).unwrap_err();
781 assert!(
782 err.to_string()
783 .contains("repartition hints must be altered separately")
784 );
785
786 let dup = AlterTableExpr {
788 catalog_name: "test_catalog".to_string(),
789 schema_name: "test_schema".to_string(),
790 table_name: "monitor".to_string(),
791 kind: Some(Kind::SetTableOptions(SetTableOptions {
792 table_options: vec![
793 PbOption {
794 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
795 value: "host".to_string(),
796 },
797 PbOption {
798 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
799 value: "region".to_string(),
800 },
801 ],
802 })),
803 };
804 let err = alter_expr_to_request(1, dup, None).unwrap_err();
805 assert!(err.to_string().contains("duplicate repartition hint keys"));
806 }
807
808 #[test]
809 fn test_unset_repartition_column_hint_expr() {
810 let expr = AlterTableExpr {
811 catalog_name: "test_catalog".to_string(),
812 schema_name: "test_schema".to_string(),
813 table_name: "monitor".to_string(),
814 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
815 keys: vec![REPARTITION_COLUMN_HINT_KEY.to_string()],
816 })),
817 };
818
819 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
820 match alter_request.alter_kind {
821 AlterKind::UnsetAnnotations { family, keys } => {
822 assert_eq!(AnnotationFamily::RepartitionHint, family);
823 assert_eq!(vec![REPARTITION_COLUMN_HINT_KEY.to_string()], keys);
824 }
825 _ => unreachable!(),
826 }
827 }
828
829 #[test]
830 fn test_semantic_options_classified_as_annotations() {
831 let expr = AlterTableExpr {
832 catalog_name: "test_catalog".to_string(),
833 schema_name: "test_schema".to_string(),
834 table_name: "monitor".to_string(),
835 kind: Some(Kind::SetTableOptions(SetTableOptions {
836 table_options: vec![
837 PbOption {
838 key: "greptime.semantic.signal_type".to_string(),
839 value: "trace".to_string(),
840 },
841 PbOption {
842 key: "greptime.semantic.entity.host.id".to_string(),
843 value: "host".to_string(),
844 },
845 ],
846 })),
847 };
848
849 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
850 let AlterKind::SetAnnotations { family, options } = alter_request.alter_kind else {
851 panic!(
852 "expected SetAnnotations, got {:?}",
853 alter_request.alter_kind
854 );
855 };
856 assert_eq!(family, AnnotationFamily::Semantic);
857 assert_eq!(options.len(), 2);
858
859 let expr = AlterTableExpr {
860 catalog_name: "test_catalog".to_string(),
861 schema_name: "test_schema".to_string(),
862 table_name: "monitor".to_string(),
863 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
864 keys: vec!["greptime.semantic.signal_type".to_string()],
865 })),
866 };
867 let alter_request = alter_expr_to_request(1, expr, None).unwrap();
868 assert!(matches!(
869 alter_request.alter_kind,
870 AlterKind::UnsetAnnotations {
871 family: AnnotationFamily::Semantic,
872 ..
873 }
874 ));
875 }
876
877 #[test]
878 fn test_semantic_options_reject_mixed_batch() {
879 let mixed_set = AlterTableExpr {
880 catalog_name: "test_catalog".to_string(),
881 schema_name: "test_schema".to_string(),
882 table_name: "monitor".to_string(),
883 kind: Some(Kind::SetTableOptions(SetTableOptions {
884 table_options: vec![
885 PbOption {
886 key: "greptime.semantic.signal_type".to_string(),
887 value: "trace".to_string(),
888 },
889 PbOption {
890 key: table::requests::TTL_KEY.to_string(),
891 value: "7d".to_string(),
892 },
893 ],
894 })),
895 };
896 let err = alter_expr_to_request(1, mixed_set, None).unwrap_err();
897 assert!(err.to_string().contains("altered separately"), "{err}");
898
899 let mixed_unset = AlterTableExpr {
900 catalog_name: "test_catalog".to_string(),
901 schema_name: "test_schema".to_string(),
902 table_name: "monitor".to_string(),
903 kind: Some(Kind::UnsetTableOptions(UnsetTableOptions {
904 keys: vec![
905 "ttl".to_string(),
906 "greptime.semantic.signal_type".to_string(),
907 ],
908 })),
909 };
910 let err = alter_expr_to_request(1, mixed_unset, None).unwrap_err();
911 assert!(err.to_string().contains("altered separately"), "{err}");
912 }
913
914 #[test]
915 fn test_annotation_alter_family() {
916 let mixed = Kind::SetTableOptions(SetTableOptions {
917 table_options: vec![
918 PbOption {
919 key: "greptime.semantic.signal_type".to_string(),
920 value: "trace".to_string(),
921 },
922 PbOption {
923 key: "ttl".to_string(),
924 value: "7d".to_string(),
925 },
926 ],
927 });
928 let err = annotation_alter_family(&mixed).unwrap_err();
929 assert!(
930 err.to_string().contains("must be altered separately"),
931 "{err}"
932 );
933
934 let cross = Kind::SetTableOptions(SetTableOptions {
936 table_options: vec![
937 PbOption {
938 key: "greptime.semantic.signal_type".to_string(),
939 value: "trace".to_string(),
940 },
941 PbOption {
942 key: REPARTITION_COLUMN_HINT_KEY.to_string(),
943 value: "host".to_string(),
944 },
945 ],
946 });
947 let err = annotation_alter_family(&cross).unwrap_err();
948 assert!(
949 err.to_string().contains("must be altered separately"),
950 "{err}"
951 );
952 }
953}