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