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